d765e81f700be906710738405d55c6b31832194e
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating(str)
647   char *str;
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine(ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions(ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine(ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine(ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
1796     if (appData.debugMode) {
1797         fprintf(debugFP, ">ICS: ");
1798         show_bytes(debugFP, buf, newcount);
1799         fprintf(debugFP, "\n");
1800     }
1801     outcount = OutputToProcess(pr, buf, newcount, outError);
1802     if (outcount < newcount) return -1; /* to be sure */
1803     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             // [HGM] chat: intercept tells by users for which we have an open chat window
3130             channel = -1;
3131             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3132                                            looking_at(buf, &i, "* whispers:") ||
3133                                            looking_at(buf, &i, "* kibitzes:") ||
3134                                            looking_at(buf, &i, "* shouts:") ||
3135                                            looking_at(buf, &i, "* c-shouts:") ||
3136                                            looking_at(buf, &i, "--> * ") ||
3137                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3141                 int p;
3142                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3143                 chattingPartner = -1;
3144
3145                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3148                     talker[0] = '['; strcat(talker, "] ");
3149                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3150                     chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("kibitzes", chatPartner[p])) {
3156                         talker[0] = '['; strcat(talker, "] ");
3157                         chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("whispers", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3168                   if(buf[i-8] == '-' && buf[i-3] == 't')
3169                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3170                     if(!strcmp("c-shouts", chatPartner[p])) {
3171                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3172                         chattingPartner = p; break;
3173                     }
3174                   }
3175                   if(chattingPartner < 0)
3176                   for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("shouts", chatPartner[p])) {
3178                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3179                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3180                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                 }
3185                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3186                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3187                     talker[0] = 0; Colorize(ColorTell, FALSE);
3188                     chattingPartner = p; break;
3189                 }
3190                 if(chattingPartner<0) i = oldi; else {
3191                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3192                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     started = STARTED_COMMENT;
3195                     parse_pos = 0; parse[0] = NULLCHAR;
3196                     savingComment = 3 + chattingPartner; // counts as TRUE
3197                     suppressKibitz = TRUE;
3198                     continue;
3199                 }
3200             } // [HGM] chat: end of patch
3201
3202           backup = i;
3203             if (appData.zippyTalk || appData.zippyPlay) {
3204                 /* [DM] Backup address for color zippy lines */
3205 #if ZIPPY
3206                if (loggedOn == TRUE)
3207                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3208                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3209 #endif
3210             } // [DM] 'else { ' deleted
3211                 if (
3212                     /* Regular tells and says */
3213                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3214                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3215                     looking_at(buf, &i, "* says: ") ||
3216                     /* Don't color "message" or "messages" output */
3217                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3218                     looking_at(buf, &i, "*. * at *:*: ") ||
3219                     looking_at(buf, &i, "--* (*:*): ") ||
3220                     /* Message notifications (same color as tells) */
3221                     looking_at(buf, &i, "* has left a message ") ||
3222                     looking_at(buf, &i, "* just sent you a message:\n") ||
3223                     /* Whispers and kibitzes */
3224                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3225                     looking_at(buf, &i, "* kibitzes: ") ||
3226                     /* Channel tells */
3227                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3228
3229                   if (tkind == 1 && strchr(star_match[0], ':')) {
3230                       /* Avoid "tells you:" spoofs in channels */
3231                      tkind = 3;
3232                   }
3233                   if (star_match[0][0] == NULLCHAR ||
3234                       strchr(star_match[0], ' ') ||
3235                       (tkind == 3 && strchr(star_match[1], ' '))) {
3236                     /* Reject bogus matches */
3237                     i = oldi;
3238                   } else {
3239                     if (appData.colorize) {
3240                       if (oldi > next_out) {
3241                         SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = oldi;
3243                       }
3244                       switch (tkind) {
3245                       case 1:
3246                         Colorize(ColorTell, FALSE);
3247                         curColor = ColorTell;
3248                         break;
3249                       case 2:
3250                         Colorize(ColorKibitz, FALSE);
3251                         curColor = ColorKibitz;
3252                         break;
3253                       case 3:
3254                         p = strrchr(star_match[1], '(');
3255                         if (p == NULL) {
3256                           p = star_match[1];
3257                         } else {
3258                           p++;
3259                         }
3260                         if (atoi(p) == 1) {
3261                           Colorize(ColorChannel1, FALSE);
3262                           curColor = ColorChannel1;
3263                         } else {
3264                           Colorize(ColorChannel, FALSE);
3265                           curColor = ColorChannel;
3266                         }
3267                         break;
3268                       case 5:
3269                         curColor = ColorNormal;
3270                         break;
3271                       }
3272                     }
3273                     if (started == STARTED_NONE && appData.autoComment &&
3274                         (gameMode == IcsObserving ||
3275                          gameMode == IcsPlayingWhite ||
3276                          gameMode == IcsPlayingBlack)) {
3277                       parse_pos = i - oldi;
3278                       memcpy(parse, &buf[oldi], parse_pos);
3279                       parse[parse_pos] = NULLCHAR;
3280                       started = STARTED_COMMENT;
3281                       savingComment = TRUE;
3282                     } else {
3283                       started = STARTED_CHATTER;
3284                       savingComment = FALSE;
3285                     }
3286                     loggedOn = TRUE;
3287                     continue;
3288                   }
3289                 }
3290
3291                 if (looking_at(buf, &i, "* s-shouts: ") ||
3292                     looking_at(buf, &i, "* c-shouts: ")) {
3293                     if (appData.colorize) {
3294                         if (oldi > next_out) {
3295                             SendToPlayer(&buf[next_out], oldi - next_out);
3296                             next_out = oldi;
3297                         }
3298                         Colorize(ColorSShout, FALSE);
3299                         curColor = ColorSShout;
3300                     }
3301                     loggedOn = TRUE;
3302                     started = STARTED_CHATTER;
3303                     continue;
3304                 }
3305
3306                 if (looking_at(buf, &i, "--->")) {
3307                     loggedOn = TRUE;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* shouts: ") ||
3312                     looking_at(buf, &i, "--> ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorShout, FALSE);
3319                         curColor = ColorShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at( buf, &i, "Challenge:")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorChallenge, FALSE);
3333                         curColor = ColorChallenge;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* offers you") ||
3340                     looking_at(buf, &i, "* offers to be") ||
3341                     looking_at(buf, &i, "* would like to") ||
3342                     looking_at(buf, &i, "* requests to") ||
3343                     looking_at(buf, &i, "Your opponent offers") ||
3344                     looking_at(buf, &i, "Your opponent requests")) {
3345
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorRequest, FALSE);
3352                         curColor = ColorRequest;
3353                     }
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* (*) seeking")) {
3358                     if (appData.colorize) {
3359                         if (oldi > next_out) {
3360                             SendToPlayer(&buf[next_out], oldi - next_out);
3361                             next_out = oldi;
3362                         }
3363                         Colorize(ColorSeek, FALSE);
3364                         curColor = ColorSeek;
3365                     }
3366                     continue;
3367             }
3368
3369           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3370
3371             if (looking_at(buf, &i, "\\   ")) {
3372                 if (prevColor != ColorNormal) {
3373                     if (oldi > next_out) {
3374                         SendToPlayer(&buf[next_out], oldi - next_out);
3375                         next_out = oldi;
3376                     }
3377                     Colorize(prevColor, TRUE);
3378                     curColor = prevColor;
3379                 }
3380                 if (savingComment) {
3381                     parse_pos = i - oldi;
3382                     memcpy(parse, &buf[oldi], parse_pos);
3383                     parse[parse_pos] = NULLCHAR;
3384                     started = STARTED_COMMENT;
3385                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3386                         chattingPartner = savingComment - 3; // kludge to remember the box
3387                 } else {
3388                     started = STARTED_CHATTER;
3389                 }
3390                 continue;
3391             }
3392
3393             if (looking_at(buf, &i, "Black Strength :") ||
3394                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3395                 looking_at(buf, &i, "<10>") ||
3396                 looking_at(buf, &i, "#@#")) {
3397                 /* Wrong board style */
3398                 loggedOn = TRUE;
3399                 SendToICS(ics_prefix);
3400                 SendToICS("set style 12\n");
3401                 SendToICS(ics_prefix);
3402                 SendToICS("refresh\n");
3403                 continue;
3404             }
3405
3406             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3407                 ICSInitScript();
3408                 have_sent_ICS_logon = 1;
3409                 continue;
3410             }
3411
3412             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3413                 (looking_at(buf, &i, "\n<12> ") ||
3414                  looking_at(buf, &i, "<12> "))) {
3415                 loggedOn = TRUE;
3416                 if (oldi > next_out) {
3417                     SendToPlayer(&buf[next_out], oldi - next_out);
3418                 }
3419                 next_out = i;
3420                 started = STARTED_BOARD;
3421                 parse_pos = 0;
3422                 continue;
3423             }
3424
3425             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3426                 looking_at(buf, &i, "<b1> ")) {
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_HOLDINGS;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3437                 loggedOn = TRUE;
3438                 /* Header for a move list -- first line */
3439
3440                 switch (ics_getting_history) {
3441                   case H_FALSE:
3442                     switch (gameMode) {
3443                       case IcsIdle:
3444                       case BeginningOfGame:
3445                         /* User typed "moves" or "oldmoves" while we
3446                            were idle.  Pretend we asked for these
3447                            moves and soak them up so user can step
3448                            through them and/or save them.
3449                            */
3450                         Reset(FALSE, TRUE);
3451                         gameMode = IcsObserving;
3452                         ModeHighlight();
3453                         ics_gamenum = -1;
3454                         ics_getting_history = H_GOT_UNREQ_HEADER;
3455                         break;
3456                       case EditGame: /*?*/
3457                       case EditPosition: /*?*/
3458                         /* Should above feature work in these modes too? */
3459                         /* For now it doesn't */
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                       default:
3463                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3464                         break;
3465                     }
3466                     break;
3467                   case H_REQUESTED:
3468                     /* Is this the right one? */
3469                     if (gameInfo.white && gameInfo.black &&
3470                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3471                         strcmp(gameInfo.black, star_match[2]) == 0) {
3472                         /* All is well */
3473                         ics_getting_history = H_GOT_REQ_HEADER;
3474                     }
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                   case H_GOT_UNREQ_HEADER:
3478                   case H_GOT_UNWANTED_HEADER:
3479                   case H_GETTING_MOVES:
3480                     /* Should not happen */
3481                     DisplayError(_("Error gathering move list: two headers"), 0);
3482                     ics_getting_history = H_FALSE;
3483                     break;
3484                 }
3485
3486                 /* Save player ratings into gameInfo if needed */
3487                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3488                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3489                     (gameInfo.whiteRating == -1 ||
3490                      gameInfo.blackRating == -1)) {
3491
3492                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3493                     gameInfo.blackRating = string_to_rating(star_match[3]);
3494                     if (appData.debugMode)
3495                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3496                               gameInfo.whiteRating, gameInfo.blackRating);
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i,
3502               "* * match, initial time: * minute*, increment: * second")) {
3503                 /* Header for a move list -- second line */
3504                 /* Initial board will follow if this is a wild game */
3505                 if (gameInfo.event != NULL) free(gameInfo.event);
3506                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3507                 gameInfo.event = StrSave(str);
3508                 /* [HGM] we switched variant. Translate boards if needed. */
3509                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Move  ")) {
3514                 /* Beginning of a move list */
3515                 switch (ics_getting_history) {
3516                   case H_FALSE:
3517                     /* Normally should not happen */
3518                     /* Maybe user hit reset while we were parsing */
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Happens if we are ignoring a move list that is not
3522                      * the one we just requested.  Common if the user
3523                      * tries to observe two games without turning off
3524                      * getMoveList */
3525                     break;
3526                   case H_GETTING_MOVES:
3527                     /* Should not happen */
3528                     DisplayError(_("Error gathering move list: nested"), 0);
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                     ics_getting_history = H_GETTING_MOVES;
3533                     started = STARTED_MOVES;
3534                     parse_pos = 0;
3535                     if (oldi > next_out) {
3536                         SendToPlayer(&buf[next_out], oldi - next_out);
3537                     }
3538                     break;
3539                   case H_GOT_UNREQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES_NOHIDE;
3542                     parse_pos = 0;
3543                     break;
3544                   case H_GOT_UNWANTED_HEADER:
3545                     ics_getting_history = H_FALSE;
3546                     break;
3547                 }
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "% ") ||
3552                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3553                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3554                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3555                     soughtPending = FALSE;
3556                     seekGraphUp = TRUE;
3557                     DrawSeekGraph();
3558                 }
3559                 if(suppressKibitz) next_out = i;
3560                 savingComment = FALSE;
3561                 suppressKibitz = 0;
3562                 switch (started) {
3563                   case STARTED_MOVES:
3564                   case STARTED_MOVES_NOHIDE:
3565                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3566                     parse[parse_pos + i - oldi] = NULLCHAR;
3567                     ParseGameHistory(parse);
3568 #if ZIPPY
3569                     if (appData.zippyPlay && first.initDone) {
3570                         FeedMovesToProgram(&first, forwardMostMove);
3571                         if (gameMode == IcsPlayingWhite) {
3572                             if (WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("black\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, TRUE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, TRUE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         } else if (gameMode == IcsPlayingBlack) {
3596                             if (!WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("white\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, FALSE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("black\n", &first);
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, FALSE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         }
3620                     }
3621 #endif
3622                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3623                         /* Moves came from oldmoves or moves command
3624                            while we weren't doing anything else.
3625                            */
3626                         currentMove = forwardMostMove;
3627                         ClearHighlights();/*!!could figure this out*/
3628                         flipView = appData.flipView;
3629                         DrawPosition(TRUE, boards[currentMove]);
3630                         DisplayBothClocks();
3631                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3632                                 gameInfo.white, gameInfo.black);
3633                         DisplayTitle(str);
3634                         gameMode = IcsIdle;
3635                     } else {
3636                         /* Moves were history of an active game */
3637                         if (gameInfo.resultDetails != NULL) {
3638                             free(gameInfo.resultDetails);
3639                             gameInfo.resultDetails = NULL;
3640                         }
3641                     }
3642                     HistorySet(parseList, backwardMostMove,
3643                                forwardMostMove, currentMove-1);
3644                     DisplayMove(currentMove - 1);
3645                     if (started == STARTED_MOVES) next_out = i;
3646                     started = STARTED_NONE;
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649
3650                   case STARTED_OBSERVE:
3651                     started = STARTED_NONE;
3652                     SendToICS(ics_prefix);
3653                     SendToICS("refresh\n");
3654                     break;
3655
3656                   default:
3657                     break;
3658                 }
3659                 if(bookHit) { // [HGM] book: simulate book reply
3660                     static char bookMove[MSG_SIZ]; // a bit generous?
3661
3662                     programStats.nodes = programStats.depth = programStats.time =
3663                     programStats.score = programStats.got_only_move = 0;
3664                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3665
3666                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3667                     strcat(bookMove, bookHit);
3668                     HandleMachineMove(bookMove, &first);
3669                 }
3670                 continue;
3671             }
3672
3673             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3674                  started == STARTED_HOLDINGS ||
3675                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3676                 /* Accumulate characters in move list or board */
3677                 parse[parse_pos++] = buf[i];
3678             }
3679
3680             /* Start of game messages.  Mostly we detect start of game
3681                when the first board image arrives.  On some versions
3682                of the ICS, though, we need to do a "refresh" after starting
3683                to observe in order to get the current board right away. */
3684             if (looking_at(buf, &i, "Adding game * to observation list")) {
3685                 started = STARTED_OBSERVE;
3686                 continue;
3687             }
3688
3689             /* Handle auto-observe */
3690             if (appData.autoObserve &&
3691                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3692                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3693                 char *player;
3694                 /* Choose the player that was highlighted, if any. */
3695                 if (star_match[0][0] == '\033' ||
3696                     star_match[1][0] != '\033') {
3697                     player = star_match[0];
3698                 } else {
3699                     player = star_match[2];
3700                 }
3701                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3702                         ics_prefix, StripHighlightAndTitle(player));
3703                 SendToICS(str);
3704
3705                 /* Save ratings from notify string */
3706                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3707                 player1Rating = string_to_rating(star_match[1]);
3708                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3709                 player2Rating = string_to_rating(star_match[3]);
3710
3711                 if (appData.debugMode)
3712                   fprintf(debugFP,
3713                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3714                           player1Name, player1Rating,
3715                           player2Name, player2Rating);
3716
3717                 continue;
3718             }
3719
3720             /* Deal with automatic examine mode after a game,
3721                and with IcsObserving -> IcsExamining transition */
3722             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3723                 looking_at(buf, &i, "has made you an examiner of game *")) {
3724
3725                 int gamenum = atoi(star_match[0]);
3726                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3727                     gamenum == ics_gamenum) {
3728                     /* We were already playing or observing this game;
3729                        no need to refetch history */
3730                     gameMode = IcsExamining;
3731                     if (pausing) {
3732                         pauseExamForwardMostMove = forwardMostMove;
3733                     } else if (currentMove < forwardMostMove) {
3734                         ForwardInner(forwardMostMove);
3735                     }
3736                 } else {
3737                     /* I don't think this case really can happen */
3738                     SendToICS(ics_prefix);
3739                     SendToICS("refresh\n");
3740                 }
3741                 continue;
3742             }
3743
3744             /* Error messages */
3745 //          if (ics_user_moved) {
3746             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3747                 if (looking_at(buf, &i, "Illegal move") ||
3748                     looking_at(buf, &i, "Not a legal move") ||
3749                     looking_at(buf, &i, "Your king is in check") ||
3750                     looking_at(buf, &i, "It isn't your turn") ||
3751                     looking_at(buf, &i, "It is not your move")) {
3752                     /* Illegal move */
3753                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3754                         currentMove = forwardMostMove-1;
3755                         DisplayMove(currentMove - 1); /* before DMError */
3756                         DrawPosition(FALSE, boards[currentMove]);
3757                         SwitchClocks(forwardMostMove-1); // [HGM] race
3758                         DisplayBothClocks();
3759                     }
3760                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3761                     ics_user_moved = 0;
3762                     continue;
3763                 }
3764             }
3765
3766             if (looking_at(buf, &i, "still have time") ||
3767                 looking_at(buf, &i, "not out of time") ||
3768                 looking_at(buf, &i, "either player is out of time") ||
3769                 looking_at(buf, &i, "has timeseal; checking")) {
3770                 /* We must have called his flag a little too soon */
3771                 whiteFlag = blackFlag = FALSE;
3772                 continue;
3773             }
3774
3775             if (looking_at(buf, &i, "added * seconds to") ||
3776                 looking_at(buf, &i, "seconds were added to")) {
3777                 /* Update the clocks */
3778                 SendToICS(ics_prefix);
3779                 SendToICS("refresh\n");
3780                 continue;
3781             }
3782
3783             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3784                 ics_clock_paused = TRUE;
3785                 StopClocks();
3786                 continue;
3787             }
3788
3789             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3790                 ics_clock_paused = FALSE;
3791                 StartClocks();
3792                 continue;
3793             }
3794
3795             /* Grab player ratings from the Creating: message.
3796                Note we have to check for the special case when
3797                the ICS inserts things like [white] or [black]. */
3798             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3799                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3800                 /* star_matches:
3801                    0    player 1 name (not necessarily white)
3802                    1    player 1 rating
3803                    2    empty, white, or black (IGNORED)
3804                    3    player 2 name (not necessarily black)
3805                    4    player 2 rating
3806
3807                    The names/ratings are sorted out when the game
3808                    actually starts (below).
3809                 */
3810                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3811                 player1Rating = string_to_rating(star_match[1]);
3812                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3813                 player2Rating = string_to_rating(star_match[4]);
3814
3815                 if (appData.debugMode)
3816                   fprintf(debugFP,
3817                           "Ratings from 'Creating:' %s %d, %s %d\n",
3818                           player1Name, player1Rating,
3819                           player2Name, player2Rating);
3820
3821                 continue;
3822             }
3823
3824             /* Improved generic start/end-of-game messages */
3825             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3826                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3827                 /* If tkind == 0: */
3828                 /* star_match[0] is the game number */
3829                 /*           [1] is the white player's name */
3830                 /*           [2] is the black player's name */
3831                 /* For end-of-game: */
3832                 /*           [3] is the reason for the game end */
3833                 /*           [4] is a PGN end game-token, preceded by " " */
3834                 /* For start-of-game: */
3835                 /*           [3] begins with "Creating" or "Continuing" */
3836                 /*           [4] is " *" or empty (don't care). */
3837                 int gamenum = atoi(star_match[0]);
3838                 char *whitename, *blackname, *why, *endtoken;
3839                 ChessMove endtype = EndOfFile;
3840
3841                 if (tkind == 0) {
3842                   whitename = star_match[1];
3843                   blackname = star_match[2];
3844                   why = star_match[3];
3845                   endtoken = star_match[4];
3846                 } else {
3847                   whitename = star_match[1];
3848                   blackname = star_match[3];
3849                   why = star_match[5];
3850                   endtoken = star_match[6];
3851                 }
3852
3853                 /* Game start messages */
3854                 if (strncmp(why, "Creating ", 9) == 0 ||
3855                     strncmp(why, "Continuing ", 11) == 0) {
3856                     gs_gamenum = gamenum;
3857                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3858                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3859                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3860 #if ZIPPY
3861                     if (appData.zippyPlay) {
3862                         ZippyGameStart(whitename, blackname);
3863                     }
3864 #endif /*ZIPPY*/
3865                     partnerBoardValid = FALSE; // [HGM] bughouse
3866                     continue;
3867                 }
3868
3869                 /* Game end messages */
3870                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3871                     ics_gamenum != gamenum) {
3872                     continue;
3873                 }
3874                 while (endtoken[0] == ' ') endtoken++;
3875                 switch (endtoken[0]) {
3876                   case '*':
3877                   default:
3878                     endtype = GameUnfinished;
3879                     break;
3880                   case '0':
3881                     endtype = BlackWins;
3882                     break;
3883                   case '1':
3884                     if (endtoken[1] == '/')
3885                       endtype = GameIsDrawn;
3886                     else
3887                       endtype = WhiteWins;
3888                     break;
3889                 }
3890                 GameEnds(endtype, why, GE_ICS);
3891 #if ZIPPY
3892                 if (appData.zippyPlay && first.initDone) {
3893                     ZippyGameEnd(endtype, why);
3894                     if (first.pr == NoProc) {
3895                       /* Start the next process early so that we'll
3896                          be ready for the next challenge */
3897                       StartChessProgram(&first);
3898                     }
3899                     /* Send "new" early, in case this command takes
3900                        a long time to finish, so that we'll be ready
3901                        for the next challenge. */
3902                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903                     Reset(TRUE, TRUE);
3904                 }
3905 #endif /*ZIPPY*/
3906                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "Removing game * from observation") ||
3911                 looking_at(buf, &i, "no longer observing game *") ||
3912                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3913                 if (gameMode == IcsObserving &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       /* icsEngineAnalyze */
3917                       if (appData.icsEngineAnalyze) {
3918                             ExitAnalyzeMode();
3919                             ModeHighlight();
3920                       }
3921                       StopClocks();
3922                       gameMode = IcsIdle;
3923                       ics_gamenum = -1;
3924                       ics_user_moved = FALSE;
3925                   }
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "no longer examining game *")) {
3930                 if (gameMode == IcsExamining &&
3931                     atoi(star_match[0]) == ics_gamenum)
3932                   {
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             /* Advance leftover_start past any newlines we find,
3941                so only partial lines can get reparsed */
3942             if (looking_at(buf, &i, "\n")) {
3943                 prevColor = curColor;
3944                 if (curColor != ColorNormal) {
3945                     if (oldi > next_out) {
3946                         SendToPlayer(&buf[next_out], oldi - next_out);
3947                         next_out = oldi;
3948                     }
3949                     Colorize(ColorNormal, FALSE);
3950                     curColor = ColorNormal;
3951                 }
3952                 if (started == STARTED_BOARD) {
3953                     started = STARTED_NONE;
3954                     parse[parse_pos] = NULLCHAR;
3955                     ParseBoard12(parse);
3956                     ics_user_moved = 0;
3957
3958                     /* Send premove here */
3959                     if (appData.premove) {
3960                       char str[MSG_SIZ];
3961                       if (currentMove == 0 &&
3962                           gameMode == IcsPlayingWhite &&
3963                           appData.premoveWhite) {
3964                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                         SendToICS(str);
3968                       } else if (currentMove == 1 &&
3969                                  gameMode == IcsPlayingBlack &&
3970                                  appData.premoveBlack) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (gotPremove) {
3976                         gotPremove = 0;
3977                         ClearPremoveHighlights();
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                           UserMoveEvent(premoveFromX, premoveFromY,
3981                                         premoveToX, premoveToY,
3982                                         premovePromoChar);
3983                       }
3984                     }
3985
3986                     /* Usually suppress following prompt */
3987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3988                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3989                         if (looking_at(buf, &i, "*% ")) {
3990                             savingComment = FALSE;
3991                             suppressKibitz = 0;
3992                         }
3993                     }
3994                     next_out = i;
3995                 } else if (started == STARTED_HOLDINGS) {
3996                     int gamenum;
3997                     char new_piece[MSG_SIZ];
3998                     started = STARTED_NONE;
3999                     parse[parse_pos] = NULLCHAR;
4000                     if (appData.debugMode)
4001                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4002                                                         parse, currentMove);
4003                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4004                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4005                         if (gameInfo.variant == VariantNormal) {
4006                           /* [HGM] We seem to switch variant during a game!
4007                            * Presumably no holdings were displayed, so we have
4008                            * to move the position two files to the right to
4009                            * create room for them!
4010                            */
4011                           VariantClass newVariant;
4012                           switch(gameInfo.boardWidth) { // base guess on board width
4013                                 case 9:  newVariant = VariantShogi; break;
4014                                 case 10: newVariant = VariantGreat; break;
4015                                 default: newVariant = VariantCrazyhouse; break;
4016                           }
4017                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018                           /* Get a move list just to see the header, which
4019                              will tell us whether this is really bug or zh */
4020                           if (ics_getting_history == H_FALSE) {
4021                             ics_getting_history = H_REQUESTED;
4022                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023                             SendToICS(str);
4024                           }
4025                         }
4026                         new_piece[0] = NULLCHAR;
4027                         sscanf(parse, "game %d white [%s black [%s <- %s",
4028                                &gamenum, white_holding, black_holding,
4029                                new_piece);
4030                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4031                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4032                         /* [HGM] copy holdings to board holdings area */
4033                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4034                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4035                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4036 #if ZIPPY
4037                         if (appData.zippyPlay && first.initDone) {
4038                             ZippyHoldings(white_holding, black_holding,
4039                                           new_piece);
4040                         }
4041 #endif /*ZIPPY*/
4042                         if (tinyLayout || smallLayout) {
4043                             char wh[16], bh[16];
4044                             PackHolding(wh, white_holding);
4045                             PackHolding(bh, black_holding);
4046                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4047                                     gameInfo.white, gameInfo.black);
4048                         } else {
4049                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4050                                     gameInfo.white, white_holding,
4051                                     gameInfo.black, black_holding);
4052                         }
4053                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4054                         DrawPosition(FALSE, boards[currentMove]);
4055                         DisplayTitle(str);
4056                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4057                         sscanf(parse, "game %d white [%s black [%s <- %s",
4058                                &gamenum, white_holding, black_holding,
4059                                new_piece);
4060                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4061                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4062                         /* [HGM] copy holdings to partner-board holdings area */
4063                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4064                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4065                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4066                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4067                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4068                       }
4069                     }
4070                     /* Suppress following prompt */
4071                     if (looking_at(buf, &i, "*% ")) {
4072                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4073                         savingComment = FALSE;
4074                         suppressKibitz = 0;
4075                     }
4076                     next_out = i;
4077                 }
4078                 continue;
4079             }
4080
4081             i++;                /* skip unparsed character and loop back */
4082         }
4083
4084         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4085 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4086 //          SendToPlayer(&buf[next_out], i - next_out);
4087             started != STARTED_HOLDINGS && leftover_start > next_out) {
4088             SendToPlayer(&buf[next_out], leftover_start - next_out);
4089             next_out = i;
4090         }
4091
4092         leftover_len = buf_len - leftover_start;
4093         /* if buffer ends with something we couldn't parse,
4094            reparse it after appending the next read */
4095
4096     } else if (count == 0) {
4097         RemoveInputSource(isr);
4098         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4099     } else {
4100         DisplayFatalError(_("Error reading from ICS"), error, 1);
4101     }
4102 }
4103
4104
4105 /* Board style 12 looks like this:
4106
4107    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4108
4109  * The "<12> " is stripped before it gets to this routine.  The two
4110  * trailing 0's (flip state and clock ticking) are later addition, and
4111  * some chess servers may not have them, or may have only the first.
4112  * Additional trailing fields may be added in the future.
4113  */
4114
4115 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4116
4117 #define RELATION_OBSERVING_PLAYED    0
4118 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4119 #define RELATION_PLAYING_MYMOVE      1
4120 #define RELATION_PLAYING_NOTMYMOVE  -1
4121 #define RELATION_EXAMINING           2
4122 #define RELATION_ISOLATED_BOARD     -3
4123 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4124
4125 void
4126 ParseBoard12(string)
4127      char *string;
4128 {
4129     GameMode newGameMode;
4130     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4131     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4132     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4133     char to_play, board_chars[200];
4134     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4135     char black[32], white[32];
4136     Board board;
4137     int prevMove = currentMove;
4138     int ticking = 2;
4139     ChessMove moveType;
4140     int fromX, fromY, toX, toY;
4141     char promoChar;
4142     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4143     char *bookHit = NULL; // [HGM] book
4144     Boolean weird = FALSE, reqFlag = FALSE;
4145
4146     fromX = fromY = toX = toY = -1;
4147
4148     newGame = FALSE;
4149
4150     if (appData.debugMode)
4151       fprintf(debugFP, _("Parsing board: %s\n"), string);
4152
4153     move_str[0] = NULLCHAR;
4154     elapsed_time[0] = NULLCHAR;
4155     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4156         int  i = 0, j;
4157         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4158             if(string[i] == ' ') { ranks++; files = 0; }
4159             else files++;
4160             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4161             i++;
4162         }
4163         for(j = 0; j <i; j++) board_chars[j] = string[j];
4164         board_chars[i] = '\0';
4165         string += i + 1;
4166     }
4167     n = sscanf(string, PATTERN, &to_play, &double_push,
4168                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4169                &gamenum, white, black, &relation, &basetime, &increment,
4170                &white_stren, &black_stren, &white_time, &black_time,
4171                &moveNum, str, elapsed_time, move_str, &ics_flip,
4172                &ticking);
4173
4174     if (n < 21) {
4175         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4176         DisplayError(str, 0);
4177         return;
4178     }
4179
4180     /* Convert the move number to internal form */
4181     moveNum = (moveNum - 1) * 2;
4182     if (to_play == 'B') moveNum++;
4183     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4184       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185                         0, 1);
4186       return;
4187     }
4188
4189     switch (relation) {
4190       case RELATION_OBSERVING_PLAYED:
4191       case RELATION_OBSERVING_STATIC:
4192         if (gamenum == -1) {
4193             /* Old ICC buglet */
4194             relation = RELATION_OBSERVING_STATIC;
4195         }
4196         newGameMode = IcsObserving;
4197         break;
4198       case RELATION_PLAYING_MYMOVE:
4199       case RELATION_PLAYING_NOTMYMOVE:
4200         newGameMode =
4201           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4202             IcsPlayingWhite : IcsPlayingBlack;
4203         break;
4204       case RELATION_EXAMINING:
4205         newGameMode = IcsExamining;
4206         break;
4207       case RELATION_ISOLATED_BOARD:
4208       default:
4209         /* Just display this board.  If user was doing something else,
4210            we will forget about it until the next board comes. */
4211         newGameMode = IcsIdle;
4212         break;
4213       case RELATION_STARTING_POSITION:
4214         newGameMode = gameMode;
4215         break;
4216     }
4217
4218     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4219          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4220       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4221       char *toSqr;
4222       for (k = 0; k < ranks; k++) {
4223         for (j = 0; j < files; j++)
4224           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4225         if(gameInfo.holdingsWidth > 1) {
4226              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4227              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4228         }
4229       }
4230       CopyBoard(partnerBoard, board);
4231       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4232         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4233         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4235       if(toSqr = strchr(str, '-')) {
4236         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4237         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4239       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4240       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4241       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4243       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4244                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4245       DisplayMessage(partnerStatus, "");
4246         partnerBoardValid = TRUE;
4247       return;
4248     }
4249
4250     /* Modify behavior for initial board display on move listing
4251        of wild games.
4252        */
4253     switch (ics_getting_history) {
4254       case H_FALSE:
4255       case H_REQUESTED:
4256         break;
4257       case H_GOT_REQ_HEADER:
4258       case H_GOT_UNREQ_HEADER:
4259         /* This is the initial position of the current game */
4260         gamenum = ics_gamenum;
4261         moveNum = 0;            /* old ICS bug workaround */
4262         if (to_play == 'B') {
4263           startedFromSetupPosition = TRUE;
4264           blackPlaysFirst = TRUE;
4265           moveNum = 1;
4266           if (forwardMostMove == 0) forwardMostMove = 1;
4267           if (backwardMostMove == 0) backwardMostMove = 1;
4268           if (currentMove == 0) currentMove = 1;
4269         }
4270         newGameMode = gameMode;
4271         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4272         break;
4273       case H_GOT_UNWANTED_HEADER:
4274         /* This is an initial board that we don't want */
4275         return;
4276       case H_GETTING_MOVES:
4277         /* Should not happen */
4278         DisplayError(_("Error gathering move list: extra board"), 0);
4279         ics_getting_history = H_FALSE;
4280         return;
4281     }
4282
4283    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4284                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4285      /* [HGM] We seem to have switched variant unexpectedly
4286       * Try to guess new variant from board size
4287       */
4288           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4289           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4290           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4291           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4292           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4293           if(!weird) newVariant = VariantNormal;
4294           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4295           /* Get a move list just to see the header, which
4296              will tell us whether this is really bug or zh */
4297           if (ics_getting_history == H_FALSE) {
4298             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301           }
4302     }
4303
4304     /* Take action if this is the first board of a new game, or of a
4305        different game than is currently being displayed.  */
4306     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4307         relation == RELATION_ISOLATED_BOARD) {
4308
4309         /* Forget the old game and get the history (if any) of the new one */
4310         if (gameMode != BeginningOfGame) {
4311           Reset(TRUE, TRUE);
4312         }
4313         newGame = TRUE;
4314         if (appData.autoRaiseBoard) BoardToTop();
4315         prevMove = -3;
4316         if (gamenum == -1) {
4317             newGameMode = IcsIdle;
4318         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4319                    appData.getMoveList && !reqFlag) {
4320             /* Need to get game history */
4321             ics_getting_history = H_REQUESTED;
4322             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323             SendToICS(str);
4324         }
4325
4326         /* Initially flip the board to have black on the bottom if playing
4327            black or if the ICS flip flag is set, but let the user change
4328            it with the Flip View button. */
4329         flipView = appData.autoFlipView ?
4330           (newGameMode == IcsPlayingBlack) || ics_flip :
4331           appData.flipView;
4332
4333         /* Done with values from previous mode; copy in new ones */
4334         gameMode = newGameMode;
4335         ModeHighlight();
4336         ics_gamenum = gamenum;
4337         if (gamenum == gs_gamenum) {
4338             int klen = strlen(gs_kind);
4339             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4340             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4341             gameInfo.event = StrSave(str);
4342         } else {
4343             gameInfo.event = StrSave("ICS game");
4344         }
4345         gameInfo.site = StrSave(appData.icsHost);
4346         gameInfo.date = PGNDate();
4347         gameInfo.round = StrSave("-");
4348         gameInfo.white = StrSave(white);
4349         gameInfo.black = StrSave(black);
4350         timeControl = basetime * 60 * 1000;
4351         timeControl_2 = 0;
4352         timeIncrement = increment * 1000;
4353         movesPerSession = 0;
4354         gameInfo.timeControl = TimeControlTagValue();
4355         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4358     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4359     setbuf(debugFP, NULL);
4360   }
4361
4362         gameInfo.outOfBook = NULL;
4363
4364         /* Do we have the ratings? */
4365         if (strcmp(player1Name, white) == 0 &&
4366             strcmp(player2Name, black) == 0) {
4367             if (appData.debugMode)
4368               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4369                       player1Rating, player2Rating);
4370             gameInfo.whiteRating = player1Rating;
4371             gameInfo.blackRating = player2Rating;
4372         } else if (strcmp(player2Name, white) == 0 &&
4373                    strcmp(player1Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player2Rating, player1Rating);
4377             gameInfo.whiteRating = player2Rating;
4378             gameInfo.blackRating = player1Rating;
4379         }
4380         player1Name[0] = player2Name[0] = NULLCHAR;
4381
4382         /* Silence shouts if requested */
4383         if (appData.quietPlay &&
4384             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4385             SendToICS(ics_prefix);
4386             SendToICS("set shout 0\n");
4387         }
4388     }
4389
4390     /* Deal with midgame name changes */
4391     if (!newGame) {
4392         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4393             if (gameInfo.white) free(gameInfo.white);
4394             gameInfo.white = StrSave(white);
4395         }
4396         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4397             if (gameInfo.black) free(gameInfo.black);
4398             gameInfo.black = StrSave(black);
4399         }
4400     }
4401
4402     /* Throw away game result if anything actually changes in examine mode */
4403     if (gameMode == IcsExamining && !newGame) {
4404         gameInfo.result = GameUnfinished;
4405         if (gameInfo.resultDetails != NULL) {
4406             free(gameInfo.resultDetails);
4407             gameInfo.resultDetails = NULL;
4408         }
4409     }
4410
4411     /* In pausing && IcsExamining mode, we ignore boards coming
4412        in if they are in a different variation than we are. */
4413     if (pauseExamInvalid) return;
4414     if (pausing && gameMode == IcsExamining) {
4415         if (moveNum <= pauseExamForwardMostMove) {
4416             pauseExamInvalid = TRUE;
4417             forwardMostMove = pauseExamForwardMostMove;
4418             return;
4419         }
4420     }
4421
4422   if (appData.debugMode) {
4423     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4424   }
4425     /* Parse the board */
4426     for (k = 0; k < ranks; k++) {
4427       for (j = 0; j < files; j++)
4428         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429       if(gameInfo.holdingsWidth > 1) {
4430            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432       }
4433     }
4434     CopyBoard(boards[moveNum], board);
4435     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4436     if (moveNum == 0) {
4437         startedFromSetupPosition =
4438           !CompareBoards(board, initialPosition);
4439         if(startedFromSetupPosition)
4440             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4441     }
4442
4443     /* [HGM] Set castling rights. Take the outermost Rooks,
4444        to make it also work for FRC opening positions. Note that board12
4445        is really defective for later FRC positions, as it has no way to
4446        indicate which Rook can castle if they are on the same side of King.
4447        For the initial position we grant rights to the outermost Rooks,
4448        and remember thos rights, and we then copy them on positions
4449        later in an FRC game. This means WB might not recognize castlings with
4450        Rooks that have moved back to their original position as illegal,
4451        but in ICS mode that is not its job anyway.
4452     */
4453     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4454     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4455
4456         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4460             if(board[0][i] == WhiteRook) j = i;
4461         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4466             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4467         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468
4469         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4472         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4473             if(board[BOARD_HEIGHT-1][k] == bKing)
4474                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4475         if(gameInfo.variant == VariantTwoKings) {
4476             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4477             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4478             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4479         }
4480     } else { int r;
4481         r = boards[moveNum][CASTLING][0] = initialRights[0];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4483         r = boards[moveNum][CASTLING][1] = initialRights[1];
4484         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4485         r = boards[moveNum][CASTLING][3] = initialRights[3];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4487         r = boards[moveNum][CASTLING][4] = initialRights[4];
4488         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4489         /* wildcastle kludge: always assume King has rights */
4490         r = boards[moveNum][CASTLING][2] = initialRights[2];
4491         r = boards[moveNum][CASTLING][5] = initialRights[5];
4492     }
4493     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4494     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4495
4496
4497     if (ics_getting_history == H_GOT_REQ_HEADER ||
4498         ics_getting_history == H_GOT_UNREQ_HEADER) {
4499         /* This was an initial position from a move list, not
4500            the current position */
4501         return;
4502     }
4503
4504     /* Update currentMove and known move number limits */
4505     newMove = newGame || moveNum > forwardMostMove;
4506
4507     if (newGame) {
4508         forwardMostMove = backwardMostMove = currentMove = moveNum;
4509         if (gameMode == IcsExamining && moveNum == 0) {
4510           /* Workaround for ICS limitation: we are not told the wild
4511              type when starting to examine a game.  But if we ask for
4512              the move list, the move list header will tell us */
4513             ics_getting_history = H_REQUESTED;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516         }
4517     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4518                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4519 #if ZIPPY
4520         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4521         /* [HGM] applied this also to an engine that is silently watching        */
4522         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4523             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4524             gameInfo.variant == currentlyInitializedVariant) {
4525           takeback = forwardMostMove - moveNum;
4526           for (i = 0; i < takeback; i++) {
4527             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4528             SendToProgram("undo\n", &first);
4529           }
4530         }
4531 #endif
4532
4533         forwardMostMove = moveNum;
4534         if (!pausing || currentMove > forwardMostMove)
4535           currentMove = forwardMostMove;
4536     } else {
4537         /* New part of history that is not contiguous with old part */
4538         if (pausing && gameMode == IcsExamining) {
4539             pauseExamInvalid = TRUE;
4540             forwardMostMove = pauseExamForwardMostMove;
4541             return;
4542         }
4543         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4544 #if ZIPPY
4545             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4546                 // [HGM] when we will receive the move list we now request, it will be
4547                 // fed to the engine from the first move on. So if the engine is not
4548                 // in the initial position now, bring it there.
4549                 InitChessProgram(&first, 0);
4550             }
4551 #endif
4552             ics_getting_history = H_REQUESTED;
4553             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4554             SendToICS(str);
4555         }
4556         forwardMostMove = backwardMostMove = currentMove = moveNum;
4557     }
4558
4559     /* Update the clocks */
4560     if (strchr(elapsed_time, '.')) {
4561       /* Time is in ms */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4564     } else {
4565       /* Time is in seconds */
4566       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4567       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4568     }
4569
4570
4571 #if ZIPPY
4572     if (appData.zippyPlay && newGame &&
4573         gameMode != IcsObserving && gameMode != IcsIdle &&
4574         gameMode != IcsExamining)
4575       ZippyFirstBoard(moveNum, basetime, increment);
4576 #endif
4577
4578     /* Put the move on the move list, first converting
4579        to canonical algebraic form. */
4580     if (moveNum > 0) {
4581   if (appData.debugMode) {
4582     if (appData.debugMode) { int f = forwardMostMove;
4583         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4584                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4585                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4586     }
4587     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4588     fprintf(debugFP, "moveNum = %d\n", moveNum);
4589     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4590     setbuf(debugFP, NULL);
4591   }
4592         if (moveNum <= backwardMostMove) {
4593             /* We don't know what the board looked like before
4594                this move.  Punt. */
4595           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4596             strcat(parseList[moveNum - 1], " ");
4597             strcat(parseList[moveNum - 1], elapsed_time);
4598             moveList[moveNum - 1][0] = NULLCHAR;
4599         } else if (strcmp(move_str, "none") == 0) {
4600             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4601             /* Again, we don't know what the board looked like;
4602                this is really the start of the game. */
4603             parseList[moveNum - 1][0] = NULLCHAR;
4604             moveList[moveNum - 1][0] = NULLCHAR;
4605             backwardMostMove = moveNum;
4606             startedFromSetupPosition = TRUE;
4607             fromX = fromY = toX = toY = -1;
4608         } else {
4609           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4610           //                 So we parse the long-algebraic move string in stead of the SAN move
4611           int valid; char buf[MSG_SIZ], *prom;
4612
4613           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4614                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4615           // str looks something like "Q/a1-a2"; kill the slash
4616           if(str[1] == '/')
4617             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4618           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4619           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4620                 strcat(buf, prom); // long move lacks promo specification!
4621           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4622                 if(appData.debugMode)
4623                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4624                 safeStrCpy(move_str, buf, MSG_SIZ);
4625           }
4626           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar)
4628                || ParseOneMove(buf, moveNum - 1, &moveType,
4629                                 &fromX, &fromY, &toX, &toY, &promoChar);
4630           // end of long SAN patch
4631           if (valid) {
4632             (void) CoordsToAlgebraic(boards[moveNum - 1],
4633                                      PosFlags(moveNum - 1),
4634                                      fromY, fromX, toY, toX, promoChar,
4635                                      parseList[moveNum-1]);
4636             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4637               case MT_NONE:
4638               case MT_STALEMATE:
4639               default:
4640                 break;
4641               case MT_CHECK:
4642                 if(gameInfo.variant != VariantShogi)
4643                     strcat(parseList[moveNum - 1], "+");
4644                 break;
4645               case MT_CHECKMATE:
4646               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4647                 strcat(parseList[moveNum - 1], "#");
4648                 break;
4649             }
4650             strcat(parseList[moveNum - 1], " ");
4651             strcat(parseList[moveNum - 1], elapsed_time);
4652             /* currentMoveString is set as a side-effect of ParseOneMove */
4653             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4654             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4655             strcat(moveList[moveNum - 1], "\n");
4656
4657             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4658                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4659               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4660                 ChessSquare old, new = boards[moveNum][k][j];
4661                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4662                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4663                   if(old == new) continue;
4664                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4665                   else if(new == WhiteWazir || new == BlackWazir) {
4666                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4667                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4668                       else boards[moveNum][k][j] = old; // preserve type of Gold
4669                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4670                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4671               }
4672           } else {
4673             /* Move from ICS was illegal!?  Punt. */
4674             if (appData.debugMode) {
4675               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4676               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4677             }
4678             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4679             strcat(parseList[moveNum - 1], " ");
4680             strcat(parseList[moveNum - 1], elapsed_time);
4681             moveList[moveNum - 1][0] = NULLCHAR;
4682             fromX = fromY = toX = toY = -1;
4683           }
4684         }
4685   if (appData.debugMode) {
4686     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4687     setbuf(debugFP, NULL);
4688   }
4689
4690 #if ZIPPY
4691         /* Send move to chess program (BEFORE animating it). */
4692         if (appData.zippyPlay && !newGame && newMove &&
4693            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4694
4695             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4696                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4697                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4698                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4699                             move_str);
4700                     DisplayError(str, 0);
4701                 } else {
4702                     if (first.sendTime) {
4703                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4704                     }
4705                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4706                     if (firstMove && !bookHit) {
4707                         firstMove = FALSE;
4708                         if (first.useColors) {
4709                           SendToProgram(gameMode == IcsPlayingWhite ?
4710                                         "white\ngo\n" :
4711                                         "black\ngo\n", &first);
4712                         } else {
4713                           SendToProgram("go\n", &first);
4714                         }
4715                         first.maybeThinking = TRUE;
4716                     }
4717                 }
4718             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4719               if (moveList[moveNum - 1][0] == NULLCHAR) {
4720                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4721                 DisplayError(str, 0);
4722               } else {
4723                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4724                 SendMoveToProgram(moveNum - 1, &first);
4725               }
4726             }
4727         }
4728 #endif
4729     }
4730
4731     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4732         /* If move comes from a remote source, animate it.  If it
4733            isn't remote, it will have already been animated. */
4734         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4735             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4736         }
4737         if (!pausing && appData.highlightLastMove) {
4738             SetHighlights(fromX, fromY, toX, toY);
4739         }
4740     }
4741
4742     /* Start the clocks */
4743     whiteFlag = blackFlag = FALSE;
4744     appData.clockMode = !(basetime == 0 && increment == 0);
4745     if (ticking == 0) {
4746       ics_clock_paused = TRUE;
4747       StopClocks();
4748     } else if (ticking == 1) {
4749       ics_clock_paused = FALSE;
4750     }
4751     if (gameMode == IcsIdle ||
4752         relation == RELATION_OBSERVING_STATIC ||
4753         relation == RELATION_EXAMINING ||
4754         ics_clock_paused)
4755       DisplayBothClocks();
4756     else
4757       StartClocks();
4758
4759     /* Display opponents and material strengths */
4760     if (gameInfo.variant != VariantBughouse &&
4761         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4762         if (tinyLayout || smallLayout) {
4763             if(gameInfo.variant == VariantNormal)
4764               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment);
4767             else
4768               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4769                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4770                     basetime, increment, (int) gameInfo.variant);
4771         } else {
4772             if(gameInfo.variant == VariantNormal)
4773               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment);
4776             else
4777               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4778                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4779                     basetime, increment, VariantName(gameInfo.variant));
4780         }
4781         DisplayTitle(str);
4782   if (appData.debugMode) {
4783     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4784   }
4785     }
4786
4787
4788     /* Display the board */
4789     if (!pausing && !appData.noGUI) {
4790
4791       if (appData.premove)
4792           if (!gotPremove ||
4793              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4794              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4795               ClearPremoveHighlights();
4796
4797       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4798         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4799       DrawPosition(j, boards[currentMove]);
4800
4801       DisplayMove(moveNum - 1);
4802       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4803             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4804               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4805         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4806       }
4807     }
4808
4809     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4810 #if ZIPPY
4811     if(bookHit) { // [HGM] book: simulate book reply
4812         static char bookMove[MSG_SIZ]; // a bit generous?
4813
4814         programStats.nodes = programStats.depth = programStats.time =
4815         programStats.score = programStats.got_only_move = 0;
4816         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4817
4818         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4819         strcat(bookMove, bookHit);
4820         HandleMachineMove(bookMove, &first);
4821     }
4822 #endif
4823 }
4824
4825 void
4826 GetMoveListEvent()
4827 {
4828     char buf[MSG_SIZ];
4829     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4830         ics_getting_history = H_REQUESTED;
4831         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4832         SendToICS(buf);
4833     }
4834 }
4835
4836 void
4837 AnalysisPeriodicEvent(force)
4838      int force;
4839 {
4840     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4841          && !force) || !appData.periodicUpdates)
4842       return;
4843
4844     /* Send . command to Crafty to collect stats */
4845     SendToProgram(".\n", &first);
4846
4847     /* Don't send another until we get a response (this makes
4848        us stop sending to old Crafty's which don't understand
4849        the "." command (sending illegal cmds resets node count & time,
4850        which looks bad)) */
4851     programStats.ok_to_send = 0;
4852 }
4853
4854 void ics_update_width(new_width)
4855         int new_width;
4856 {
4857         ics_printf("set width %d\n", new_width);
4858 }
4859
4860 void
4861 SendMoveToProgram(moveNum, cps)
4862      int moveNum;
4863      ChessProgramState *cps;
4864 {
4865     char buf[MSG_SIZ];
4866
4867     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4868         // null move in variant where engine does not understand it (for analysis purposes)
4869         SendBoard(cps, moveNum + 1); // send position after move in stead.
4870         return;
4871     }
4872     if (cps->useUsermove) {
4873       SendToProgram("usermove ", cps);
4874     }
4875     if (cps->useSAN) {
4876       char *space;
4877       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4878         int len = space - parseList[moveNum];
4879         memcpy(buf, parseList[moveNum], len);
4880         buf[len++] = '\n';
4881         buf[len] = NULLCHAR;
4882       } else {
4883         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4884       }
4885       SendToProgram(buf, cps);
4886     } else {
4887       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4888         AlphaRank(moveList[moveNum], 4);
4889         SendToProgram(moveList[moveNum], cps);
4890         AlphaRank(moveList[moveNum], 4); // and back
4891       } else
4892       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4893        * the engine. It would be nice to have a better way to identify castle
4894        * moves here. */
4895       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4896                                                                          && cps->useOOCastle) {
4897         int fromX = moveList[moveNum][0] - AAA;
4898         int fromY = moveList[moveNum][1] - ONE;
4899         int toX = moveList[moveNum][2] - AAA;
4900         int toY = moveList[moveNum][3] - ONE;
4901         if((boards[moveNum][fromY][fromX] == WhiteKing
4902             && boards[moveNum][toY][toX] == WhiteRook)
4903            || (boards[moveNum][fromY][fromX] == BlackKing
4904                && boards[moveNum][toY][toX] == BlackRook)) {
4905           if(toX > fromX) SendToProgram("O-O\n", cps);
4906           else SendToProgram("O-O-O\n", cps);
4907         }
4908         else SendToProgram(moveList[moveNum], cps);
4909       } else
4910       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4911         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4912           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4913           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4914                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         } else
4916           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4917                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4918         SendToProgram(buf, cps);
4919       }
4920       else SendToProgram(moveList[moveNum], cps);
4921       /* End of additions by Tord */
4922     }
4923
4924     /* [HGM] setting up the opening has brought engine in force mode! */
4925     /*       Send 'go' if we are in a mode where machine should play. */
4926     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4927         (gameMode == TwoMachinesPlay   ||
4928 #if ZIPPY
4929          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4930 #endif
4931          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4932         SendToProgram("go\n", cps);
4933   if (appData.debugMode) {
4934     fprintf(debugFP, "(extra)\n");
4935   }
4936     }
4937     setboardSpoiledMachineBlack = 0;
4938 }
4939
4940 void
4941 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4942      ChessMove moveType;
4943      int fromX, fromY, toX, toY;
4944      char promoChar;
4945 {
4946     char user_move[MSG_SIZ];
4947     char suffix[4];
4948
4949     if(gameInfo.variant == VariantSChess && promoChar) {
4950         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4951         if(toX == BOARD_WIDTH>>1) moveType = WhitePromotion; // kludge to do gating at Rook
4952     } else suffix[0] = NULLCHAR;
4953
4954     switch (moveType) {
4955       default:
4956         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4957                 (int)moveType, fromX, fromY, toX, toY);
4958         DisplayError(user_move + strlen("say "), 0);
4959         break;
4960       case WhiteKingSideCastle:
4961       case BlackKingSideCastle:
4962       case WhiteQueenSideCastleWild:
4963       case BlackQueenSideCastleWild:
4964       /* PUSH Fabien */
4965       case WhiteHSideCastleFR:
4966       case BlackHSideCastleFR:
4967       /* POP Fabien */
4968         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4969         break;
4970       case WhiteQueenSideCastle:
4971       case BlackQueenSideCastle:
4972       case WhiteKingSideCastleWild:
4973       case BlackKingSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteASideCastleFR:
4976       case BlackASideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4979         break;
4980       case WhiteNonPromotion:
4981       case BlackNonPromotion:
4982         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4983         break;
4984       case WhitePromotion:
4985       case BlackPromotion:
4986         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 PieceToChar(WhiteFerz));
4990         else if(gameInfo.variant == VariantGreat)
4991           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4993                 PieceToChar(WhiteMan));
4994         else
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 promoChar);
4998         break;
4999       case WhiteDrop:
5000       case BlackDrop:
5001       drop:
5002         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5003                  ToUpper(PieceToChar((ChessSquare) fromX)),
5004                  AAA + toX, ONE + toY);
5005         break;
5006       case IllegalMove:  /* could be a variant we don't quite understand */
5007         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5008       case NormalMove:
5009       case WhiteCapturesEnPassant:
5010       case BlackCapturesEnPassant:
5011         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5013         break;
5014     }
5015     SendToICS(user_move);
5016     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5017         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5018 }
5019
5020 void
5021 UploadGameEvent()
5022 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5023     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5024     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5025     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5026       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5027       return;
5028     }
5029     if(gameMode != IcsExamining) { // is this ever not the case?
5030         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5031
5032         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5033           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5034         } else { // on FICS we must first go to general examine mode
5035           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5036         }
5037         if(gameInfo.variant != VariantNormal) {
5038             // try figure out wild number, as xboard names are not always valid on ICS
5039             for(i=1; i<=36; i++) {
5040               snprintf(buf, MSG_SIZ, "wild/%d", i);
5041                 if(StringToVariant(buf) == gameInfo.variant) break;
5042             }
5043             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5044             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5045             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5046         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5047         SendToICS(ics_prefix);
5048         SendToICS(buf);
5049         if(startedFromSetupPosition || backwardMostMove != 0) {
5050           fen = PositionToFEN(backwardMostMove, NULL);
5051           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5052             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5053             SendToICS(buf);
5054           } else { // FICS: everything has to set by separate bsetup commands
5055             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5056             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5057             SendToICS(buf);
5058             if(!WhiteOnMove(backwardMostMove)) {
5059                 SendToICS("bsetup tomove black\n");
5060             }
5061             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5062             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5063             SendToICS(buf);
5064             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5065             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5066             SendToICS(buf);
5067             i = boards[backwardMostMove][EP_STATUS];
5068             if(i >= 0) { // set e.p.
5069               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5070                 SendToICS(buf);
5071             }
5072             bsetup++;
5073           }
5074         }
5075       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5076             SendToICS("bsetup done\n"); // switch to normal examining.
5077     }
5078     for(i = backwardMostMove; i<last; i++) {
5079         char buf[20];
5080         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5081         SendToICS(buf);
5082     }
5083     SendToICS(ics_prefix);
5084     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5085 }
5086
5087 void
5088 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5089      int rf, ff, rt, ft;
5090      char promoChar;
5091      char move[7];
5092 {
5093     if (rf == DROP_RANK) {
5094       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5095       sprintf(move, "%c@%c%c\n",
5096                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5097     } else {
5098         if (promoChar == 'x' || promoChar == NULLCHAR) {
5099           sprintf(move, "%c%c%c%c\n",
5100                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5101         } else {
5102             sprintf(move, "%c%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5104         }
5105     }
5106 }
5107
5108 void
5109 ProcessICSInitScript(f)
5110      FILE *f;
5111 {
5112     char buf[MSG_SIZ];
5113
5114     while (fgets(buf, MSG_SIZ, f)) {
5115         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5116     }
5117
5118     fclose(f);
5119 }
5120
5121
5122 static int lastX, lastY, selectFlag, dragging;
5123
5124 void
5125 Sweep(int step)
5126 {
5127     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5128     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5129     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5130     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5131     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5132     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5133     do {
5134         promoSweep -= step;
5135         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5136         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5137         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5138         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5139         if(!step) step = -1;
5140     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5141             appData.testLegality && (promoSweep == king ||
5142             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5143     ChangeDragPiece(promoSweep);
5144 }
5145
5146 int PromoScroll(int x, int y)
5147 {
5148   int step = 0;
5149
5150   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5151   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5152   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5153   if(!step) return FALSE;
5154   lastX = x; lastY = y;
5155   if((promoSweep < BlackPawn) == flipView) step = -step;
5156   if(step > 0) selectFlag = 1;
5157   if(!selectFlag) Sweep(step);
5158   return FALSE;
5159 }
5160
5161 void
5162 NextPiece(int step)
5163 {
5164     ChessSquare piece = boards[currentMove][toY][toX];
5165     do {
5166         pieceSweep -= step;
5167         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5168         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(pieceSweep) == '.');
5171     boards[currentMove][toY][toX] = pieceSweep;
5172     DrawPosition(FALSE, boards[currentMove]);
5173     boards[currentMove][toY][toX] = piece;
5174 }
5175 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5176 void
5177 AlphaRank(char *move, int n)
5178 {
5179 //    char *p = move, c; int x, y;
5180
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5183     }
5184
5185     if(move[1]=='*' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         move[1] = '@';
5189         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5191     } else
5192     if(move[0]>='0' && move[0]<='9' &&
5193        move[1]>='a' && move[1]<='x' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         /* input move, Shogi -> normal */
5197         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201     } else
5202     if(move[1]=='@' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205         move[1] = '*';
5206         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5208     } else
5209     if(
5210        move[0]>='a' && move[0]<='x' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213          /* output move, normal -> Shogi */
5214         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5215         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5219     }
5220     if (appData.debugMode) {
5221         fprintf(debugFP, "   out = '%s'\n", move);
5222     }
5223 }
5224
5225 char yy_textstr[8000];
5226
5227 /* Parser for moves from gnuchess, ICS, or user typein box */
5228 Boolean
5229 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5230      char *move;
5231      int moveNum;
5232      ChessMove *moveType;
5233      int *fromX, *fromY, *toX, *toY;
5234      char *promoChar;
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV(ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN(char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV(int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV(int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void GetPositionNumber()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int put(Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5543 // calculate where the next piece goes, (any empty square), and put it there
5544 {
5545         int i;
5546
5547         i = seed % squaresLeft[shade];
5548         nrOfShuffles *= squaresLeft[shade];
5549         seed /= squaresLeft[shade];
5550         put(board, pieceType, rank, i, shade);
5551 }
5552
5553 void AddTwoPieces(Board board, int pieceType, int rank)
5554 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5555 {
5556         int i, n=squaresLeft[ANY], j=n-1, k;
5557
5558         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5559         i = seed % k;  // pick one
5560         nrOfShuffles *= k;
5561         seed /= k;
5562         while(i >= j) i -= j--;
5563         j = n - 1 - j; i += j;
5564         put(board, pieceType, rank, j, ANY);
5565         put(board, pieceType, rank, i, ANY);
5566 }
5567
5568 void SetUpShuffle(Board board, int number)
5569 {
5570         int i, p, first=1;
5571
5572         GetPositionNumber(); nrOfShuffles = 1;
5573
5574         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5575         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5576         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5577
5578         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5579
5580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5581             p = (int) board[0][i];
5582             if(p < (int) BlackPawn) piecesLeft[p] ++;
5583             board[0][i] = EmptySquare;
5584         }
5585
5586         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5587             // shuffles restricted to allow normal castling put KRR first
5588             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5589                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5590             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5591                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5593                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5595                 put(board, WhiteRook, 0, 0, ANY);
5596             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5597         }
5598
5599         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5600             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5601             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5602                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5603                 while(piecesLeft[p] >= 2) {
5604                     AddOnePiece(board, p, 0, LITE);
5605                     AddOnePiece(board, p, 0, DARK);
5606                 }
5607                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5608             }
5609
5610         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5611             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5612             // but we leave King and Rooks for last, to possibly obey FRC restriction
5613             if(p == (int)WhiteRook) continue;
5614             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5615             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5616         }
5617
5618         // now everything is placed, except perhaps King (Unicorn) and Rooks
5619
5620         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5621             // Last King gets castling rights
5622             while(piecesLeft[(int)WhiteUnicorn]) {
5623                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5624                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5625             }
5626
5627             while(piecesLeft[(int)WhiteKing]) {
5628                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632
5633         } else {
5634             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5635             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5636         }
5637
5638         // Only Rooks can be left; simply place them all
5639         while(piecesLeft[(int)WhiteRook]) {
5640                 i = put(board, WhiteRook, 0, 0, ANY);
5641                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5642                         if(first) {
5643                                 first=0;
5644                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5645                         }
5646                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5647                 }
5648         }
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5650             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5651         }
5652
5653         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5654 }
5655
5656 int SetCharTable( char *table, const char * map )
5657 /* [HGM] moved here from winboard.c because of its general usefulness */
5658 /*       Basically a safe strcpy that uses the last character as King */
5659 {
5660     int result = FALSE; int NrPieces;
5661
5662     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5663                     && NrPieces >= 12 && !(NrPieces&1)) {
5664         int i; /* [HGM] Accept even length from 12 to 34 */
5665
5666         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5667         for( i=0; i<NrPieces/2-1; i++ ) {
5668             table[i] = map[i];
5669             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5670         }
5671         table[(int) WhiteKing]  = map[NrPieces/2-1];
5672         table[(int) BlackKing]  = map[NrPieces-1];
5673
5674         result = TRUE;
5675     }
5676
5677     return result;
5678 }
5679
5680 void Prelude(Board board)
5681 {       // [HGM] superchess: random selection of exo-pieces
5682         int i, j, k; ChessSquare p;
5683         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5684
5685         GetPositionNumber(); // use FRC position number
5686
5687         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5688             SetCharTable(pieceToChar, appData.pieceToCharTable);
5689             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5690                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5691         }
5692
5693         j = seed%4;                 seed /= 4;
5694         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%3 + (seed%3 >= j); seed /= 3;
5698         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%3;                 seed /= 3;
5702         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%2 + (seed%2 >= j); seed /= 2;
5706         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5710         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5711         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5712         put(board, exoPieces[0],    0, 0, ANY);
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5714 }
5715
5716 void
5717 InitPosition(redraw)
5718      int redraw;
5719 {
5720     ChessSquare (* pieces)[BOARD_FILES];
5721     int i, j, pawnRow, overrule,
5722     oldx = gameInfo.boardWidth,
5723     oldy = gameInfo.boardHeight,
5724     oldh = gameInfo.holdingsWidth;
5725     static int oldv;
5726
5727     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5728
5729     /* [AS] Initialize pv info list [HGM] and game status */
5730     {
5731         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5732             pvInfoList[i].depth = 0;
5733             boards[i][EP_STATUS] = EP_NONE;
5734             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5735         }
5736
5737         initialRulePlies = 0; /* 50-move counter start */
5738
5739         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5740         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5741     }
5742
5743
5744     /* [HGM] logic here is completely changed. In stead of full positions */
5745     /* the initialized data only consist of the two backranks. The switch */
5746     /* selects which one we will use, which is than copied to the Board   */
5747     /* initialPosition, which for the rest is initialized by Pawns and    */
5748     /* empty squares. This initial position is then copied to boards[0],  */
5749     /* possibly after shuffling, so that it remains available.            */
5750
5751     gameInfo.holdingsWidth = 0; /* default board sizes */
5752     gameInfo.boardWidth    = 8;
5753     gameInfo.boardHeight   = 8;
5754     gameInfo.holdingsSize  = 0;
5755     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5756     for(i=0; i<BOARD_FILES-2; i++)
5757       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5758     initialPosition[EP_STATUS] = EP_NONE;
5759     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5760     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5761          SetCharTable(pieceNickName, appData.pieceNickNames);
5762     else SetCharTable(pieceNickName, "............");
5763     pieces = FIDEArray;
5764
5765     switch (gameInfo.variant) {
5766     case VariantFischeRandom:
5767       shuffleOpenings = TRUE;
5768     default:
5769       break;
5770     case VariantShatranj:
5771       pieces = ShatranjArray;
5772       nrCastlingRights = 0;
5773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5774       break;
5775     case VariantMakruk:
5776       pieces = makrukArray;
5777       nrCastlingRights = 0;
5778       startedFromSetupPosition = TRUE;
5779       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5780       break;
5781     case VariantTwoKings:
5782       pieces = twoKingsArray;
5783       break;
5784     case VariantGrand:
5785       pieces = GrandArray;
5786       nrCastlingRights = 0;
5787       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5788       gameInfo.boardWidth = 10;
5789       gameInfo.boardHeight = 10;
5790       gameInfo.holdingsSize = 7;
5791       break;
5792     case VariantCapaRandom:
5793       shuffleOpenings = TRUE;
5794     case VariantCapablanca:
5795       pieces = CapablancaArray;
5796       gameInfo.boardWidth = 10;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       break;
5799     case VariantGothic:
5800       pieces = GothicArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantSChess:
5805       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5806       gameInfo.holdingsSize = 7;
5807       break;
5808     case VariantJanus:
5809       pieces = JanusArray;
5810       gameInfo.boardWidth = 10;
5811       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5812       nrCastlingRights = 6;
5813         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5816         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5819       break;
5820     case VariantFalcon:
5821       pieces = FalconArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5824       break;
5825     case VariantXiangqi:
5826       pieces = XiangqiArray;
5827       gameInfo.boardWidth  = 9;
5828       gameInfo.boardHeight = 10;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5831       break;
5832     case VariantShogi:
5833       pieces = ShogiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 9;
5836       gameInfo.holdingsSize = 7;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5839       break;
5840     case VariantCourier:
5841       pieces = CourierArray;
5842       gameInfo.boardWidth  = 12;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5845       break;
5846     case VariantKnightmate:
5847       pieces = KnightmateArray;
5848       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5849       break;
5850     case VariantSpartan:
5851       pieces = SpartanArray;
5852       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5853       break;
5854     case VariantFairy:
5855       pieces = fairyArray;
5856       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5857       break;
5858     case VariantGreat:
5859       pieces = GreatArray;
5860       gameInfo.boardWidth = 10;
5861       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5862       gameInfo.holdingsSize = 8;
5863       break;
5864     case VariantSuper:
5865       pieces = FIDEArray;
5866       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5867       gameInfo.holdingsSize = 8;
5868       startedFromSetupPosition = TRUE;
5869       break;
5870     case VariantCrazyhouse:
5871     case VariantBughouse:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5874       gameInfo.holdingsSize = 5;
5875       break;
5876     case VariantWildCastle:
5877       pieces = FIDEArray;
5878       /* !!?shuffle with kings guaranteed to be on d or e file */
5879       shuffleOpenings = 1;
5880       break;
5881     case VariantNoCastle:
5882       pieces = FIDEArray;
5883       nrCastlingRights = 0;
5884       /* !!?unconstrained back-rank shuffle */
5885       shuffleOpenings = 1;
5886       break;
5887     }
5888
5889     overrule = 0;
5890     if(appData.NrFiles >= 0) {
5891         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5892         gameInfo.boardWidth = appData.NrFiles;
5893     }
5894     if(appData.NrRanks >= 0) {
5895         gameInfo.boardHeight = appData.NrRanks;
5896     }
5897     if(appData.holdingsSize >= 0) {
5898         i = appData.holdingsSize;
5899         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5900         gameInfo.holdingsSize = i;
5901     }
5902     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5903     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5904         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5905
5906     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5907     if(pawnRow < 1) pawnRow = 1;
5908     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5909
5910     /* User pieceToChar list overrules defaults */
5911     if(appData.pieceToCharTable != NULL)
5912         SetCharTable(pieceToChar, appData.pieceToCharTable);
5913
5914     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5915
5916         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5917             s = (ChessSquare) 0; /* account holding counts in guard band */
5918         for( i=0; i<BOARD_HEIGHT; i++ )
5919             initialPosition[i][j] = s;
5920
5921         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5922         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5923         initialPosition[pawnRow][j] = WhitePawn;
5924         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5925         if(gameInfo.variant == VariantXiangqi) {
5926             if(j&1) {
5927                 initialPosition[pawnRow][j] =
5928                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5929                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5930                    initialPosition[2][j] = WhiteCannon;
5931                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5932                 }
5933             }
5934         }
5935         if(gameInfo.variant == VariantGrand) {
5936             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5937                initialPosition[0][j] = WhiteRook;
5938                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5939             }
5940         }
5941         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5942     }
5943     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5944
5945             j=BOARD_LEFT+1;
5946             initialPosition[1][j] = WhiteBishop;
5947             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5948             j=BOARD_RGHT-2;
5949             initialPosition[1][j] = WhiteRook;
5950             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5951     }
5952
5953     if( nrCastlingRights == -1) {
5954         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5955         /*       This sets default castling rights from none to normal corners   */
5956         /* Variants with other castling rights must set them themselves above    */
5957         nrCastlingRights = 6;
5958
5959         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5962         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5965      }
5966
5967      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5968      if(gameInfo.variant == VariantGreat) { // promotion commoners
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5973      }
5974      if( gameInfo.variant == VariantSChess ) {
5975       initialPosition[1][0] = BlackMarshall;
5976       initialPosition[2][0] = BlackAngel;
5977       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5978       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5979       initialPosition[1][1] = initialPosition[2][1] = 
5980       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5981      }
5982   if (appData.debugMode) {
5983     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5984   }
5985     if(shuffleOpenings) {
5986         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5987         startedFromSetupPosition = TRUE;
5988     }
5989     if(startedFromPositionFile) {
5990       /* [HGM] loadPos: use PositionFile for every new game */
5991       CopyBoard(initialPosition, filePosition);
5992       for(i=0; i<nrCastlingRights; i++)
5993           initialRights[i] = filePosition[CASTLING][i];
5994       startedFromSetupPosition = TRUE;
5995     }
5996
5997     CopyBoard(boards[0], initialPosition);
5998
5999     if(oldx != gameInfo.boardWidth ||
6000        oldy != gameInfo.boardHeight ||
6001        oldv != gameInfo.variant ||
6002        oldh != gameInfo.holdingsWidth
6003                                          )
6004             InitDrawingSizes(-2 ,0);
6005
6006     oldv = gameInfo.variant;
6007     if (redraw)
6008       DrawPosition(TRUE, boards[currentMove]);
6009 }
6010
6011 void
6012 SendBoard(cps, moveNum)
6013      ChessProgramState *cps;
6014      int moveNum;
6015 {
6016     char message[MSG_SIZ];
6017
6018     if (cps->useSetboard) {
6019       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6020       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6021       SendToProgram(message, cps);
6022       free(fen);
6023
6024     } else {
6025       ChessSquare *bp;
6026       int i, j, left=0, right=BOARD_WIDTH;
6027       /* Kludge to set black to move, avoiding the troublesome and now
6028        * deprecated "black" command.
6029        */
6030       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6031         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6032
6033       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6034
6035       SendToProgram("edit\n", cps);
6036       SendToProgram("#\n", cps);
6037       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6038         bp = &boards[moveNum][i][left];
6039         for (j = left; j < right; j++, bp++) {
6040           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6041           if ((int) *bp < (int) BlackPawn) {
6042             if(j == BOARD_RGHT+1)
6043                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6044             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6045             if(message[0] == '+' || message[0] == '~') {
6046               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6047                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6048                         AAA + j, ONE + i);
6049             }
6050             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6051                 message[1] = BOARD_RGHT   - 1 - j + '1';
6052                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6053             }
6054             SendToProgram(message, cps);
6055           }
6056         }
6057       }
6058
6059       SendToProgram("c\n", cps);
6060       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6061         bp = &boards[moveNum][i][left];
6062         for (j = left; j < right; j++, bp++) {
6063           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6064           if (((int) *bp != (int) EmptySquare)
6065               && ((int) *bp >= (int) BlackPawn)) {
6066             if(j == BOARD_LEFT-2)
6067                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6068             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6069                     AAA + j, ONE + i);
6070             if(message[0] == '+' || message[0] == '~') {
6071               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6072                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6073                         AAA + j, ONE + i);
6074             }
6075             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6076                 message[1] = BOARD_RGHT   - 1 - j + '1';
6077                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6078             }
6079             SendToProgram(message, cps);
6080           }
6081         }
6082       }
6083
6084       SendToProgram(".\n", cps);
6085     }
6086     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6087 }
6088
6089 ChessSquare
6090 DefaultPromoChoice(int white)
6091 {
6092     ChessSquare result;
6093     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6094         result = WhiteFerz; // no choice
6095     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6096         result= WhiteKing; // in Suicide Q is the last thing we want
6097     else if(gameInfo.variant == VariantSpartan)
6098         result = white ? WhiteQueen : WhiteAngel;
6099     else result = WhiteQueen;
6100     if(!white) result = WHITE_TO_BLACK result;
6101     return result;
6102 }
6103
6104 static int autoQueen; // [HGM] oneclick
6105
6106 int
6107 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6108 {
6109     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6110     /* [HGM] add Shogi promotions */
6111     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6112     ChessSquare piece;
6113     ChessMove moveType;
6114     Boolean premove;
6115
6116     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6117     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6118
6119     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6120       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6121         return FALSE;
6122
6123     piece = boards[currentMove][fromY][fromX];
6124     if(gameInfo.variant == VariantShogi) {
6125         promotionZoneSize = BOARD_HEIGHT/3;
6126         highestPromotingPiece = (int)WhiteFerz;
6127     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6128         promotionZoneSize = 3;
6129     }
6130
6131     // Treat Lance as Pawn when it is not representing Amazon
6132     if(gameInfo.variant != VariantSuper) {
6133         if(piece == WhiteLance) piece = WhitePawn; else
6134         if(piece == BlackLance) piece = BlackPawn;
6135     }
6136
6137     // next weed out all moves that do not touch the promotion zone at all
6138     if((int)piece >= BlackPawn) {
6139         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6140              return FALSE;
6141         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6142     } else {
6143         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6144            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6145     }
6146
6147     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6148
6149     // weed out mandatory Shogi promotions
6150     if(gameInfo.variant == VariantShogi) {
6151         if(piece >= BlackPawn) {
6152             if(toY == 0 && piece == BlackPawn ||
6153                toY == 0 && piece == BlackQueen ||
6154                toY <= 1 && piece == BlackKnight) {
6155                 *promoChoice = '+';
6156                 return FALSE;
6157             }
6158         } else {
6159             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6160                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6161                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6162                 *promoChoice = '+';
6163                 return FALSE;
6164             }
6165         }
6166     }
6167
6168     // weed out obviously illegal Pawn moves
6169     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6170         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6171         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6172         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6173         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6174         // note we are not allowed to test for valid (non-)capture, due to premove
6175     }
6176
6177     // we either have a choice what to promote to, or (in Shogi) whether to promote
6178     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6179         *promoChoice = PieceToChar(BlackFerz);  // no choice
6180         return FALSE;
6181     }
6182     // no sense asking what we must promote to if it is going to explode...
6183     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6184         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6185         return FALSE;
6186     }
6187     // give caller the default choice even if we will not make it
6188     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6189     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6190     if(        sweepSelect && gameInfo.variant != VariantGreat
6191                            && gameInfo.variant != VariantGrand
6192                            && gameInfo.variant != VariantSuper) return FALSE;
6193     if(autoQueen) return FALSE; // predetermined
6194
6195     // suppress promotion popup on illegal moves that are not premoves
6196     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6197               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6198     if(appData.testLegality && !premove) {
6199         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6200                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6201         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6202             return FALSE;
6203     }
6204
6205     return TRUE;
6206 }
6207
6208 int
6209 InPalace(row, column)
6210      int row, column;
6211 {   /* [HGM] for Xiangqi */
6212     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6213          column < (BOARD_WIDTH + 4)/2 &&
6214          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6215     return FALSE;
6216 }
6217
6218 int
6219 PieceForSquare (x, y)
6220      int x;
6221      int y;
6222 {
6223   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6224      return -1;
6225   else
6226      return boards[currentMove][y][x];
6227 }
6228
6229 int
6230 OKToStartUserMove(x, y)
6231      int x, y;
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove(int *x, int *y, Boolean captures) {
6332     DisambiguateClosure cl;
6333     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6334     switch(gameMode) {
6335       case MachinePlaysBlack:
6336       case IcsPlayingWhite:
6337       case BeginningOfGame:
6338         if(!WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case MachinePlaysWhite:
6341       case IcsPlayingBlack:
6342         if(WhiteOnMove(currentMove)) return FALSE;
6343         break;
6344       case EditGame:
6345         break;
6346       default:
6347         return FALSE;
6348     }
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = *y;
6351     cl.ffIn = *x;
6352     cl.rtIn = -1;
6353     cl.ftIn = -1;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       return TRUE;
6365     }
6366     if(cl.kind != ImpossibleMove) return FALSE;
6367     cl.pieceIn = EmptySquare;
6368     cl.rfIn = -1;
6369     cl.ffIn = -1;
6370     cl.rtIn = *y;
6371     cl.ftIn = *x;
6372     cl.promoCharIn = NULLCHAR;
6373     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6374     if( cl.kind == NormalMove ||
6375         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6376         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6377         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378       fromX = cl.ff;
6379       fromY = cl.rf;
6380       *x = cl.ft;
6381       *y = cl.rt;
6382       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6383       return TRUE;
6384     }
6385     return FALSE;
6386 }
6387
6388 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6389 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6390 int lastLoadGameUseList = FALSE;
6391 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6392 ChessMove lastLoadGameStart = EndOfFile;
6393
6394 void
6395 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6396      int fromX, fromY, toX, toY;
6397      int promoChar;
6398 {
6399     ChessMove moveType;
6400     ChessSquare pdown, pup;
6401
6402     /* Check if the user is playing in turn.  This is complicated because we
6403        let the user "pick up" a piece before it is his turn.  So the piece he
6404        tried to pick up may have been captured by the time he puts it down!
6405        Therefore we use the color the user is supposed to be playing in this
6406        test, not the color of the piece that is currently on the starting
6407        square---except in EditGame mode, where the user is playing both
6408        sides; fortunately there the capture race can't happen.  (It can
6409        now happen in IcsExamining mode, but that's just too bad.  The user
6410        will get a somewhat confusing message in that case.)
6411        */
6412
6413     switch (gameMode) {
6414       case AnalyzeFile:
6415       case TwoMachinesPlay:
6416       case EndOfGame:
6417       case IcsObserving:
6418       case IcsIdle:
6419         /* We switched into a game mode where moves are not accepted,
6420            perhaps while the mouse button was down. */
6421         return;
6422
6423       case MachinePlaysWhite:
6424         /* User is moving for Black */
6425         if (WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is White's turn"));
6427             return;
6428         }
6429         break;
6430
6431       case MachinePlaysBlack:
6432         /* User is moving for White */
6433         if (!WhiteOnMove(currentMove)) {
6434             DisplayMoveError(_("It is Black's turn"));
6435             return;
6436         }
6437         break;
6438
6439       case PlayFromGameFile:
6440             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6441       case EditGame:
6442       case IcsExamining:
6443       case BeginningOfGame:
6444       case AnalyzeMode:
6445       case Training:
6446         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6447         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6448             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6449             /* User is moving for Black */
6450             if (WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is White's turn"));
6452                 return;
6453             }
6454         } else {
6455             /* User is moving for White */
6456             if (!WhiteOnMove(currentMove)) {
6457                 DisplayMoveError(_("It is Black's turn"));
6458                 return;
6459             }
6460         }
6461         break;
6462
6463       case IcsPlayingBlack:
6464         /* User is moving for Black */
6465         if (WhiteOnMove(currentMove)) {
6466             if (!appData.premove) {
6467                 DisplayMoveError(_("It is White's turn"));
6468             } else if (toX >= 0 && toY >= 0) {
6469                 premoveToX = toX;
6470                 premoveToY = toY;
6471                 premoveFromX = fromX;
6472                 premoveFromY = fromY;
6473                 premovePromoChar = promoChar;
6474                 gotPremove = 1;
6475                 if (appData.debugMode)
6476                     fprintf(debugFP, "Got premove: fromX %d,"
6477                             "fromY %d, toX %d, toY %d\n",
6478                             fromX, fromY, toX, toY);
6479             }
6480             return;
6481         }
6482         break;
6483
6484       case IcsPlayingWhite:
6485         /* User is moving for White */
6486         if (!WhiteOnMove(currentMove)) {
6487             if (!appData.premove) {
6488                 DisplayMoveError(_("It is Black's turn"));
6489             } else if (toX >= 0 && toY >= 0) {
6490                 premoveToX = toX;
6491                 premoveToY = toY;
6492                 premoveFromX = fromX;
6493                 premoveFromY = fromY;
6494                 premovePromoChar = promoChar;
6495                 gotPremove = 1;
6496                 if (appData.debugMode)
6497                     fprintf(debugFP, "Got premove: fromX %d,"
6498                             "fromY %d, toX %d, toY %d\n",
6499                             fromX, fromY, toX, toY);
6500             }
6501             return;
6502         }
6503         break;
6504
6505       default:
6506         break;
6507
6508       case EditPosition:
6509         /* EditPosition, empty square, or different color piece;
6510            click-click move is possible */
6511         if (toX == -2 || toY == -2) {
6512             boards[0][fromY][fromX] = EmptySquare;
6513             DrawPosition(FALSE, boards[currentMove]);
6514             return;
6515         } else if (toX >= 0 && toY >= 0) {
6516             boards[0][toY][toX] = boards[0][fromY][fromX];
6517             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6518                 if(boards[0][fromY][0] != EmptySquare) {
6519                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6520                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6521                 }
6522             } else
6523             if(fromX == BOARD_RGHT+1) {
6524                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6525                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6526                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6527                 }
6528             } else
6529             boards[0][fromY][fromX] = EmptySquare;
6530             DrawPosition(FALSE, boards[currentMove]);
6531             return;
6532         }
6533         return;
6534     }
6535
6536     if(toX < 0 || toY < 0) return;
6537     pdown = boards[currentMove][fromY][fromX];
6538     pup = boards[currentMove][toY][toX];
6539
6540     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6541     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6542          if( pup != EmptySquare ) return;
6543          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6544            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6545                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6546            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6547            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6548            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6549            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6550          fromY = DROP_RANK;
6551     }
6552
6553     /* [HGM] always test for legality, to get promotion info */
6554     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6555                                          fromY, fromX, toY, toX, promoChar);
6556
6557     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6558
6559     /* [HGM] but possibly ignore an IllegalMove result */
6560     if (appData.testLegality) {
6561         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6562             DisplayMoveError(_("Illegal move"));
6563             return;
6564         }
6565     }
6566
6567     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6568 }
6569
6570 /* Common tail of UserMoveEvent and DropMenuEvent */
6571 int
6572 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6573      ChessMove moveType;
6574      int fromX, fromY, toX, toY;
6575      /*char*/int promoChar;
6576 {
6577     char *bookHit = 0;
6578
6579     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6580         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6581         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6582         if(WhiteOnMove(currentMove)) {
6583             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6584         } else {
6585             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6586         }
6587     }
6588
6589     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6590        move type in caller when we know the move is a legal promotion */
6591     if(moveType == NormalMove && promoChar)
6592         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6593
6594     /* [HGM] <popupFix> The following if has been moved here from
6595        UserMoveEvent(). Because it seemed to belong here (why not allow
6596        piece drops in training games?), and because it can only be
6597        performed after it is known to what we promote. */
6598     if (gameMode == Training) {
6599       /* compare the move played on the board to the next move in the
6600        * game. If they match, display the move and the opponent's response.
6601        * If they don't match, display an error message.
6602        */
6603       int saveAnimate;
6604       Board testBoard;
6605       CopyBoard(testBoard, boards[currentMove]);
6606       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6607
6608       if (CompareBoards(testBoard, boards[currentMove+1])) {
6609         ForwardInner(currentMove+1);
6610
6611         /* Autoplay the opponent's response.
6612          * if appData.animate was TRUE when Training mode was entered,
6613          * the response will be animated.
6614          */
6615         saveAnimate = appData.animate;
6616         appData.animate = animateTraining;
6617         ForwardInner(currentMove+1);
6618         appData.animate = saveAnimate;
6619
6620         /* check for the end of the game */
6621         if (currentMove >= forwardMostMove) {
6622           gameMode = PlayFromGameFile;
6623           ModeHighlight();
6624           SetTrainingModeOff();
6625           DisplayInformation(_("End of game"));
6626         }
6627       } else {
6628         DisplayError(_("Incorrect move"), 0);
6629       }
6630       return 1;
6631     }
6632
6633   /* Ok, now we know that the move is good, so we can kill
6634      the previous line in Analysis Mode */
6635   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6636                                 && currentMove < forwardMostMove) {
6637     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6638     else forwardMostMove = currentMove;
6639   }
6640
6641   /* If we need the chess program but it's dead, restart it */
6642   ResurrectChessProgram();
6643
6644   /* A user move restarts a paused game*/
6645   if (pausing)
6646     PauseEvent();
6647
6648   thinkOutput[0] = NULLCHAR;
6649
6650   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6651
6652   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6653     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6654     return 1;
6655   }
6656
6657   if (gameMode == BeginningOfGame) {
6658     if (appData.noChessProgram) {
6659       gameMode = EditGame;
6660       SetGameInfo();
6661     } else {
6662       char buf[MSG_SIZ];
6663       gameMode = MachinePlaysBlack;
6664       StartClocks();
6665       SetGameInfo();
6666       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6667       DisplayTitle(buf);
6668       if (first.sendName) {
6669         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6670         SendToProgram(buf, &first);
6671       }
6672       StartClocks();
6673     }
6674     ModeHighlight();
6675   }
6676
6677   /* Relay move to ICS or chess engine */
6678   if (appData.icsActive) {
6679     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6680         gameMode == IcsExamining) {
6681       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6682         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6683         SendToICS("draw ");
6684         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6685       }
6686       // also send plain move, in case ICS does not understand atomic claims
6687       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6688       ics_user_moved = 1;
6689     }
6690   } else {
6691     if (first.sendTime && (gameMode == BeginningOfGame ||
6692                            gameMode == MachinePlaysWhite ||
6693                            gameMode == MachinePlaysBlack)) {
6694       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6695     }
6696     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6697          // [HGM] book: if program might be playing, let it use book
6698         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6699         first.maybeThinking = TRUE;
6700     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6701         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6702         SendBoard(&first, currentMove+1);
6703     } else SendMoveToProgram(forwardMostMove-1, &first);
6704     if (currentMove == cmailOldMove + 1) {
6705       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6706     }
6707   }
6708
6709   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710
6711   switch (gameMode) {
6712   case EditGame:
6713     if(appData.testLegality)
6714     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6715     case MT_NONE:
6716     case MT_CHECK:
6717       break;
6718     case MT_CHECKMATE:
6719     case MT_STAINMATE:
6720       if (WhiteOnMove(currentMove)) {
6721         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6722       } else {
6723         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6724       }
6725       break;
6726     case MT_STALEMATE:
6727       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6728       break;
6729     }
6730     break;
6731
6732   case MachinePlaysBlack:
6733   case MachinePlaysWhite:
6734     /* disable certain menu options while machine is thinking */
6735     SetMachineThinkingEnables();
6736     break;
6737
6738   default:
6739     break;
6740   }
6741
6742   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6743   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6744
6745   if(bookHit) { // [HGM] book: simulate book reply
6746         static char bookMove[MSG_SIZ]; // a bit generous?
6747
6748         programStats.nodes = programStats.depth = programStats.time =
6749         programStats.score = programStats.got_only_move = 0;
6750         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6751
6752         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6753         strcat(bookMove, bookHit);
6754         HandleMachineMove(bookMove, &first);
6755   }
6756   return 1;
6757 }
6758
6759 void
6760 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6761      Board board;
6762      int flags;
6763      ChessMove kind;
6764      int rf, ff, rt, ft;
6765      VOIDSTAR closure;
6766 {
6767     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6768     Markers *m = (Markers *) closure;
6769     if(rf == fromY && ff == fromX)
6770         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6771                          || kind == WhiteCapturesEnPassant
6772                          || kind == BlackCapturesEnPassant);
6773     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6774 }
6775
6776 void
6777 MarkTargetSquares(int clear)
6778 {
6779   int x, y;
6780   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6781      !appData.testLegality || gameMode == EditPosition) return;
6782   if(clear) {
6783     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6784   } else {
6785     int capt = 0;
6786     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6787     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6788       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6789       if(capt)
6790       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6791     }
6792   }
6793   DrawPosition(TRUE, NULL);
6794 }
6795
6796 int
6797 Explode(Board board, int fromX, int fromY, int toX, int toY)
6798 {
6799     if(gameInfo.variant == VariantAtomic &&
6800        (board[toY][toX] != EmptySquare ||                     // capture?
6801         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6802                          board[fromY][fromX] == BlackPawn   )
6803       )) {
6804         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6805         return TRUE;
6806     }
6807     return FALSE;
6808 }
6809
6810 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6811
6812 int CanPromote(ChessSquare piece, int y)
6813 {
6814         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6815         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6816         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6817            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6818            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6819                                                   gameInfo.variant == VariantMakruk) return FALSE;
6820         return (piece == BlackPawn && y == 1 ||
6821                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6822                 piece == BlackLance && y == 1 ||
6823                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6824 }
6825
6826 void LeftClick(ClickType clickType, int xPix, int yPix)
6827 {
6828     int x, y;
6829     Boolean saveAnimate;
6830     static int second = 0, promotionChoice = 0, clearFlag = 0;
6831     char promoChoice = NULLCHAR;
6832     ChessSquare piece;
6833
6834     if(appData.seekGraph && appData.icsActive && loggedOn &&
6835         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6836         SeekGraphClick(clickType, xPix, yPix, 0);
6837         return;
6838     }
6839
6840     if (clickType == Press) ErrorPopDown();
6841
6842     x = EventToSquare(xPix, BOARD_WIDTH);
6843     y = EventToSquare(yPix, BOARD_HEIGHT);
6844     if (!flipView && y >= 0) {
6845         y = BOARD_HEIGHT - 1 - y;
6846     }
6847     if (flipView && x >= 0) {
6848         x = BOARD_WIDTH - 1 - x;
6849     }
6850
6851     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6852         defaultPromoChoice = promoSweep;
6853         promoSweep = EmptySquare;   // terminate sweep
6854         promoDefaultAltered = TRUE;
6855         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6856     }
6857
6858     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6859         if(clickType == Release) return; // ignore upclick of click-click destination
6860         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6861         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6862         if(gameInfo.holdingsWidth &&
6863                 (WhiteOnMove(currentMove)
6864                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6865                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6866             // click in right holdings, for determining promotion piece
6867             ChessSquare p = boards[currentMove][y][x];
6868             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6869             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6870             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6871                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6872                 fromX = fromY = -1;
6873                 return;
6874             }
6875         }
6876         DrawPosition(FALSE, boards[currentMove]);
6877         return;
6878     }
6879
6880     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6881     if(clickType == Press
6882             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6883               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6884               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6885         return;
6886
6887     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6888         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6889
6890     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6891         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6892                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6893         defaultPromoChoice = DefaultPromoChoice(side);
6894     }
6895
6896     autoQueen = appData.alwaysPromoteToQueen;
6897
6898     if (fromX == -1) {
6899       int originalY = y;
6900       gatingPiece = EmptySquare;
6901       if (clickType != Press) {
6902         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6903             DragPieceEnd(xPix, yPix); dragging = 0;
6904             DrawPosition(FALSE, NULL);
6905         }
6906         return;
6907       }
6908       fromX = x; fromY = y; toX = toY = -1;
6909       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6910          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6911          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6912             /* First square */
6913             if (OKToStartUserMove(fromX, fromY)) {
6914                 second = 0;
6915                 MarkTargetSquares(0);
6916                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6917                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6918                     promoSweep = defaultPromoChoice;
6919                     selectFlag = 0; lastX = xPix; lastY = yPix;
6920                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6921                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6922                 }
6923                 if (appData.highlightDragging) {
6924                     SetHighlights(fromX, fromY, -1, -1);
6925                 }
6926             } else fromX = fromY = -1;
6927             return;
6928         }
6929     }
6930
6931     /* fromX != -1 */
6932     if (clickType == Press && gameMode != EditPosition) {
6933         ChessSquare fromP;
6934         ChessSquare toP;
6935         int frc;
6936
6937         // ignore off-board to clicks
6938         if(y < 0 || x < 0) return;
6939
6940         /* Check if clicking again on the same color piece */
6941         fromP = boards[currentMove][fromY][fromX];
6942         toP = boards[currentMove][y][x];
6943         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6944         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6945              WhitePawn <= toP && toP <= WhiteKing &&
6946              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6947              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6948             (BlackPawn <= fromP && fromP <= BlackKing &&
6949              BlackPawn <= toP && toP <= BlackKing &&
6950              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6951              !(fromP == BlackKing && toP == BlackRook && frc))) {
6952             /* Clicked again on same color piece -- changed his mind */
6953             second = (x == fromX && y == fromY);
6954             promoDefaultAltered = FALSE;
6955             MarkTargetSquares(1);
6956            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6957             if (appData.highlightDragging) {
6958                 SetHighlights(x, y, -1, -1);
6959             } else {
6960                 ClearHighlights();
6961             }
6962             if (OKToStartUserMove(x, y)) {
6963                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6964                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6965                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6966                  gatingPiece = boards[currentMove][fromY][fromX];
6967                 else gatingPiece = EmptySquare;
6968                 fromX = x;
6969                 fromY = y; dragging = 1;
6970                 MarkTargetSquares(0);
6971                 DragPieceBegin(xPix, yPix, FALSE);
6972                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6973                     promoSweep = defaultPromoChoice;
6974                     selectFlag = 0; lastX = xPix; lastY = yPix;
6975                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6976                 }
6977             }
6978            }
6979            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6980            second = FALSE; 
6981         }
6982         // ignore clicks on holdings
6983         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6984     }
6985
6986     if (clickType == Release && x == fromX && y == fromY) {
6987         DragPieceEnd(xPix, yPix); dragging = 0;
6988         if(clearFlag) {
6989             // a deferred attempt to click-click move an empty square on top of a piece
6990             boards[currentMove][y][x] = EmptySquare;
6991             ClearHighlights();
6992             DrawPosition(FALSE, boards[currentMove]);
6993             fromX = fromY = -1; clearFlag = 0;
6994             return;
6995         }
6996         if (appData.animateDragging) {
6997             /* Undo animation damage if any */
6998             DrawPosition(FALSE, NULL);
6999         }
7000         if (second) {
7001             /* Second up/down in same square; just abort move */
7002             second = 0;
7003             fromX = fromY = -1;
7004             gatingPiece = EmptySquare;
7005             ClearHighlights();
7006             gotPremove = 0;
7007             ClearPremoveHighlights();
7008         } else {
7009             /* First upclick in same square; start click-click mode */
7010             SetHighlights(x, y, -1, -1);
7011         }
7012         return;
7013     }
7014
7015     clearFlag = 0;
7016
7017     /* we now have a different from- and (possibly off-board) to-square */
7018     /* Completed move */
7019     toX = x;
7020     toY = y;
7021     saveAnimate = appData.animate;
7022     MarkTargetSquares(1);
7023     if (clickType == Press) {
7024         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7025             // must be Edit Position mode with empty-square selected
7026             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7027             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7028             return;
7029         }
7030         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7031             ChessSquare piece = boards[currentMove][fromY][fromX];
7032             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7033             promoSweep = defaultPromoChoice;
7034             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7035             selectFlag = 0; lastX = xPix; lastY = yPix;
7036             Sweep(0); // Pawn that is going to promote: preview promotion piece
7037             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7038             DrawPosition(FALSE, boards[currentMove]);
7039             return;
7040         }
7041         /* Finish clickclick move */
7042         if (appData.animate || appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047     } else {
7048         /* Finish drag move */
7049         if (appData.highlightLastMove) {
7050             SetHighlights(fromX, fromY, toX, toY);
7051         } else {
7052             ClearHighlights();
7053         }
7054         DragPieceEnd(xPix, yPix); dragging = 0;
7055         /* Don't animate move and drag both */
7056         appData.animate = FALSE;
7057     }
7058
7059     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7060     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7061         ChessSquare piece = boards[currentMove][fromY][fromX];
7062         if(gameMode == EditPosition && piece != EmptySquare &&
7063            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7064             int n;
7065
7066             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7067                 n = PieceToNumber(piece - (int)BlackPawn);
7068                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7069                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7070                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7071             } else
7072             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7073                 n = PieceToNumber(piece);
7074                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7075                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7076                 boards[currentMove][n][BOARD_WIDTH-2]++;
7077             }
7078             boards[currentMove][fromY][fromX] = EmptySquare;
7079         }
7080         ClearHighlights();
7081         fromX = fromY = -1;
7082         DrawPosition(TRUE, boards[currentMove]);
7083         return;
7084     }
7085
7086     // off-board moves should not be highlighted
7087     if(x < 0 || y < 0) ClearHighlights();
7088
7089     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7090
7091     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7092         SetHighlights(fromX, fromY, toX, toY);
7093         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7094             // [HGM] super: promotion to captured piece selected from holdings
7095             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7096             promotionChoice = TRUE;
7097             // kludge follows to temporarily execute move on display, without promoting yet
7098             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7099             boards[currentMove][toY][toX] = p;
7100             DrawPosition(FALSE, boards[currentMove]);
7101             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7102             boards[currentMove][toY][toX] = q;
7103             DisplayMessage("Click in holdings to choose piece", "");
7104             return;
7105         }
7106         PromotionPopUp();
7107     } else {
7108         int oldMove = currentMove;
7109         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7110         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7111         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7112         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7113            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7114             DrawPosition(TRUE, boards[currentMove]);
7115         fromX = fromY = -1;
7116     }
7117     appData.animate = saveAnimate;
7118     if (appData.animate || appData.animateDragging) {
7119         /* Undo animation damage if needed */
7120         DrawPosition(FALSE, NULL);
7121     }
7122 }
7123
7124 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7125 {   // front-end-free part taken out of PieceMenuPopup
7126     int whichMenu; int xSqr, ySqr;
7127
7128     if(seekGraphUp) { // [HGM] seekgraph
7129         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7130         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7131         return -2;
7132     }
7133
7134     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7135          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7136         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7137         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7138         if(action == Press)   {
7139             originalFlip = flipView;
7140             flipView = !flipView; // temporarily flip board to see game from partners perspective
7141             DrawPosition(TRUE, partnerBoard);
7142             DisplayMessage(partnerStatus, "");
7143             partnerUp = TRUE;
7144         } else if(action == Release) {
7145             flipView = originalFlip;
7146             DrawPosition(TRUE, boards[currentMove]);
7147             partnerUp = FALSE;
7148         }
7149         return -2;
7150     }
7151
7152     xSqr = EventToSquare(x, BOARD_WIDTH);
7153     ySqr = EventToSquare(y, BOARD_HEIGHT);
7154     if (action == Release) {
7155         if(pieceSweep != EmptySquare) {
7156             EditPositionMenuEvent(pieceSweep, toX, toY);
7157             pieceSweep = EmptySquare;
7158         } else UnLoadPV(); // [HGM] pv
7159     }
7160     if (action != Press) return -2; // return code to be ignored
7161     switch (gameMode) {
7162       case IcsExamining:
7163         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7164       case EditPosition:
7165         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7166         if (xSqr < 0 || ySqr < 0) return -1;
7167         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7168         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7169         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7170         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7171         NextPiece(0);
7172         return 2; // grab
7173       case IcsObserving:
7174         if(!appData.icsEngineAnalyze) return -1;
7175       case IcsPlayingWhite:
7176       case IcsPlayingBlack:
7177         if(!appData.zippyPlay) goto noZip;
7178       case AnalyzeMode:
7179       case AnalyzeFile:
7180       case MachinePlaysWhite:
7181       case MachinePlaysBlack:
7182       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7183         if (!appData.dropMenu) {
7184           LoadPV(x, y);
7185           return 2; // flag front-end to grab mouse events
7186         }
7187         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7188            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7189       case EditGame:
7190       noZip:
7191         if (xSqr < 0 || ySqr < 0) return -1;
7192         if (!appData.dropMenu || appData.testLegality &&
7193             gameInfo.variant != VariantBughouse &&
7194             gameInfo.variant != VariantCrazyhouse) return -1;
7195         whichMenu = 1; // drop menu
7196         break;
7197       default:
7198         return -1;
7199     }
7200
7201     if (((*fromX = xSqr) < 0) ||
7202         ((*fromY = ySqr) < 0)) {
7203         *fromX = *fromY = -1;
7204         return -1;
7205     }
7206     if (flipView)
7207       *fromX = BOARD_WIDTH - 1 - *fromX;
7208     else
7209       *fromY = BOARD_HEIGHT - 1 - *fromY;
7210
7211     return whichMenu;
7212 }
7213
7214 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7215 {
7216 //    char * hint = lastHint;
7217     FrontEndProgramStats stats;
7218
7219     stats.which = cps == &first ? 0 : 1;
7220     stats.depth = cpstats->depth;
7221     stats.nodes = cpstats->nodes;
7222     stats.score = cpstats->score;
7223     stats.time = cpstats->time;
7224     stats.pv = cpstats->movelist;
7225     stats.hint = lastHint;
7226     stats.an_move_index = 0;
7227     stats.an_move_count = 0;
7228
7229     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7230         stats.hint = cpstats->move_name;
7231         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7232         stats.an_move_count = cpstats->nr_moves;
7233     }
7234
7235     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7236
7237     SetProgramStats( &stats );
7238 }
7239
7240 void
7241 ClearEngineOutputPane(int which)
7242 {
7243     static FrontEndProgramStats dummyStats;
7244     dummyStats.which = which;
7245     dummyStats.pv = "#";
7246     SetProgramStats( &dummyStats );
7247 }
7248
7249 #define MAXPLAYERS 500
7250
7251 char *
7252 TourneyStandings(int display)
7253 {
7254     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7255     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7256     char result, *p, *names[MAXPLAYERS];
7257
7258     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7259         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7260     names[0] = p = strdup(appData.participants);
7261     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7262
7263     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7264
7265     while(result = appData.results[nr]) {
7266         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7267         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7268         wScore = bScore = 0;
7269         switch(result) {
7270           case '+': wScore = 2; break;
7271           case '-': bScore = 2; break;
7272           case '=': wScore = bScore = 1; break;
7273           case ' ':
7274           case '*': return strdup("busy"); // tourney not finished
7275         }
7276         score[w] += wScore;
7277         score[b] += bScore;
7278         games[w]++;
7279         games[b]++;
7280         nr++;
7281     }
7282     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7283     for(w=0; w<nPlayers; w++) {
7284         bScore = -1;
7285         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7286         ranking[w] = b; points[w] = bScore; score[b] = -2;
7287     }
7288     p = malloc(nPlayers*34+1);
7289     for(w=0; w<nPlayers && w<display; w++)
7290         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7291     free(names[0]);
7292     return p;
7293 }
7294
7295 void
7296 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7297 {       // count all piece types
7298         int p, f, r;
7299         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7300         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7302                 p = board[r][f];
7303                 pCnt[p]++;
7304                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7305                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7306                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7307                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7308                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7309                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7310         }
7311 }
7312
7313 int
7314 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7315 {
7316         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7317         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7318
7319         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7320         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7321         if(myPawns == 2 && nMine == 3) // KPP
7322             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7323         if(myPawns == 1 && nMine == 2) // KP
7324             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7325         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7326             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7327         if(myPawns) return FALSE;
7328         if(pCnt[WhiteRook+side])
7329             return pCnt[BlackRook-side] ||
7330                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7331                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7332                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7333         if(pCnt[WhiteCannon+side]) {
7334             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7335             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7336         }
7337         if(pCnt[WhiteKnight+side])
7338             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7339         return FALSE;
7340 }
7341
7342 int
7343 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7344 {
7345         VariantClass v = gameInfo.variant;
7346
7347         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7348         if(v == VariantShatranj) return TRUE; // always winnable through baring
7349         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7350         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7351
7352         if(v == VariantXiangqi) {
7353                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7354
7355                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7356                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7357                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7358                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7359                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7360                 if(stale) // we have at least one last-rank P plus perhaps C
7361                     return majors // KPKX
7362                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7363                 else // KCA*E*
7364                     return pCnt[WhiteFerz+side] // KCAK
7365                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7366                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7367                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7368
7369         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7370                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7371
7372                 if(nMine == 1) return FALSE; // bare King
7373                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7374                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7375                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7376                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7377                 if(pCnt[WhiteKnight+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7379                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7380                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7381                 if(nBishops)
7382                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7383                 if(pCnt[WhiteAlfil+side])
7384                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7385                 if(pCnt[WhiteWazir+side])
7386                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7387         }
7388
7389         return TRUE;
7390 }
7391
7392 int
7393 CompareWithRights(Board b1, Board b2)
7394 {
7395     int rights = 0;
7396     if(!CompareBoards(b1, b2)) return FALSE;
7397     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7398     /* compare castling rights */
7399     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7400            rights++; /* King lost rights, while rook still had them */
7401     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7402         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7403            rights++; /* but at least one rook lost them */
7404     }
7405     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7406            rights++;
7407     if( b1[CASTLING][5] != NoRights ) {
7408         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7409            rights++;
7410     }
7411     return rights == 0;
7412 }
7413
7414 int
7415 Adjudicate(ChessProgramState *cps)
7416 {       // [HGM] some adjudications useful with buggy engines
7417         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7418         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7419         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7420         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7421         int k, count = 0; static int bare = 1;
7422         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7423         Boolean canAdjudicate = !appData.icsActive;
7424
7425         // most tests only when we understand the game, i.e. legality-checking on
7426             if( appData.testLegality )
7427             {   /* [HGM] Some more adjudications for obstinate engines */
7428                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7429                 static int moveCount = 6;
7430                 ChessMove result;
7431                 char *reason = NULL;
7432
7433                 /* Count what is on board. */
7434                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7435
7436                 /* Some material-based adjudications that have to be made before stalemate test */
7437                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7438                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7439                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7440                      if(canAdjudicate && appData.checkMates) {
7441                          if(engineOpponent)
7442                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7443                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7444                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7445                          return 1;
7446                      }
7447                 }
7448
7449                 /* Bare King in Shatranj (loses) or Losers (wins) */
7450                 if( nrW == 1 || nrB == 1) {
7451                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7452                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7453                      if(canAdjudicate && appData.checkMates) {
7454                          if(engineOpponent)
7455                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7456                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7457                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7458                          return 1;
7459                      }
7460                   } else
7461                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7462                   {    /* bare King */
7463                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7464                         if(canAdjudicate && appData.checkMates) {
7465                             /* but only adjudicate if adjudication enabled */
7466                             if(engineOpponent)
7467                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7468                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7469                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7470                             return 1;
7471                         }
7472                   }
7473                 } else bare = 1;
7474
7475
7476             // don't wait for engine to announce game end if we can judge ourselves
7477             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7478               case MT_CHECK:
7479                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7480                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7481                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7482                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7483                             checkCnt++;
7484                         if(checkCnt >= 2) {
7485                             reason = "Xboard adjudication: 3rd check";
7486                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7487                             break;
7488                         }
7489                     }
7490                 }
7491               case MT_NONE:
7492               default:
7493                 break;
7494               case MT_STALEMATE:
7495               case MT_STAINMATE:
7496                 reason = "Xboard adjudication: Stalemate";
7497                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7498                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7499                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7500                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7501                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7502                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7503                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7504                                                                         EP_CHECKMATE : EP_WINS);
7505                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7506                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7507                 }
7508                 break;
7509               case MT_CHECKMATE:
7510                 reason = "Xboard adjudication: Checkmate";
7511                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7512                 break;
7513             }
7514
7515                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7516                     case EP_STALEMATE:
7517                         result = GameIsDrawn; break;
7518                     case EP_CHECKMATE:
7519                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7520                     case EP_WINS:
7521                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7522                     default:
7523                         result = EndOfFile;
7524                 }
7525                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7526                     if(engineOpponent)
7527                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7528                     GameEnds( result, reason, GE_XBOARD );
7529                     return 1;
7530                 }
7531
7532                 /* Next absolutely insufficient mating material. */
7533                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7534                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7535                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7536
7537                      /* always flag draws, for judging claims */
7538                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7539
7540                      if(canAdjudicate && appData.materialDraws) {
7541                          /* but only adjudicate them if adjudication enabled */
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7547                          return 1;
7548                      }
7549                 }
7550
7551                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7552                 if(gameInfo.variant == VariantXiangqi ?
7553                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7554                  : nrW + nrB == 4 &&
7555                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7556                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7557                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7558                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7559                    ) ) {
7560                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7561                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7562                           if(engineOpponent) {
7563                             SendToProgram("force\n", engineOpponent); // suppress reply
7564                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7565                           }
7566                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7567                           return 1;
7568                      }
7569                 } else moveCount = 6;
7570             }
7571         if (appData.debugMode) { int i;
7572             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7573                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7574                     appData.drawRepeats);
7575             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7576               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7577
7578         }
7579
7580         // Repetition draws and 50-move rule can be applied independently of legality testing
7581
7582                 /* Check for rep-draws */
7583                 count = 0;
7584                 for(k = forwardMostMove-2;
7585                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7586                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7587                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7588                     k-=2)
7589                 {   int rights=0;
7590                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7591                         /* compare castling rights */
7592                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7593                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7594                                 rights++; /* King lost rights, while rook still had them */
7595                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7596                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7597                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7598                                    rights++; /* but at least one rook lost them */
7599                         }
7600                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7601                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7602                                 rights++;
7603                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7604                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7605                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7606                                    rights++;
7607                         }
7608                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7609                             && appData.drawRepeats > 1) {
7610                              /* adjudicate after user-specified nr of repeats */
7611                              int result = GameIsDrawn;
7612                              char *details = "XBoard adjudication: repetition draw";
7613                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7614                                 // [HGM] xiangqi: check for forbidden perpetuals
7615                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7616                                 for(m=forwardMostMove; m>k; m-=2) {
7617                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7618                                         ourPerpetual = 0; // the current mover did not always check
7619                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7620                                         hisPerpetual = 0; // the opponent did not always check
7621                                 }
7622                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7623                                                                         ourPerpetual, hisPerpetual);
7624                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7625                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7626                                     details = "Xboard adjudication: perpetual checking";
7627                                 } else
7628                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7629                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7630                                 } else
7631                                 // Now check for perpetual chases
7632                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7633                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7634                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7635                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7636                                         static char resdet[MSG_SIZ];
7637                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7638                                         details = resdet;
7639                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7640                                     } else
7641                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7642                                         break; // Abort repetition-checking loop.
7643                                 }
7644                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7645                              }
7646                              if(engineOpponent) {
7647                                SendToProgram("force\n", engineOpponent); // suppress reply
7648                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7649                              }
7650                              GameEnds( result, details, GE_XBOARD );
7651                              return 1;
7652                         }
7653                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7654                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7655                     }
7656                 }
7657
7658                 /* Now we test for 50-move draws. Determine ply count */
7659                 count = forwardMostMove;
7660                 /* look for last irreversble move */
7661                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7662                     count--;
7663                 /* if we hit starting position, add initial plies */
7664                 if( count == backwardMostMove )
7665                     count -= initialRulePlies;
7666                 count = forwardMostMove - count;
7667                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7668                         // adjust reversible move counter for checks in Xiangqi
7669                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7670                         if(i < backwardMostMove) i = backwardMostMove;
7671                         while(i <= forwardMostMove) {
7672                                 lastCheck = inCheck; // check evasion does not count
7673                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7674                                 if(inCheck || lastCheck) count--; // check does not count
7675                                 i++;
7676                         }
7677                 }
7678                 if( count >= 100)
7679                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7680                          /* this is used to judge if draw claims are legal */
7681                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7682                          if(engineOpponent) {
7683                            SendToProgram("force\n", engineOpponent); // suppress reply
7684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7685                          }
7686                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7687                          return 1;
7688                 }
7689
7690                 /* if draw offer is pending, treat it as a draw claim
7691                  * when draw condition present, to allow engines a way to
7692                  * claim draws before making their move to avoid a race
7693                  * condition occurring after their move
7694                  */
7695                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7696                          char *p = NULL;
7697                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7698                              p = "Draw claim: 50-move rule";
7699                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7700                              p = "Draw claim: 3-fold repetition";
7701                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7702                              p = "Draw claim: insufficient mating material";
7703                          if( p != NULL && canAdjudicate) {
7704                              if(engineOpponent) {
7705                                SendToProgram("force\n", engineOpponent); // suppress reply
7706                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707                              }
7708                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7709                              return 1;
7710                          }
7711                 }
7712
7713                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7714                     if(engineOpponent) {
7715                       SendToProgram("force\n", engineOpponent); // suppress reply
7716                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                     }
7718                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7719                     return 1;
7720                 }
7721         return 0;
7722 }
7723
7724 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7725 {   // [HGM] book: this routine intercepts moves to simulate book replies
7726     char *bookHit = NULL;
7727
7728     //first determine if the incoming move brings opponent into his book
7729     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7730         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7731     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7732     if(bookHit != NULL && !cps->bookSuspend) {
7733         // make sure opponent is not going to reply after receiving move to book position
7734         SendToProgram("force\n", cps);
7735         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7736     }
7737     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7738     // now arrange restart after book miss
7739     if(bookHit) {
7740         // after a book hit we never send 'go', and the code after the call to this routine
7741         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7742         char buf[MSG_SIZ], *move = bookHit;
7743         if(cps->useSAN) {
7744             int fromX, fromY, toX, toY;
7745             char promoChar;
7746             ChessMove moveType;
7747             move = buf + 30;
7748             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7749                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7750                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7751                                     PosFlags(forwardMostMove),
7752                                     fromY, fromX, toY, toX, promoChar, move);
7753             } else {
7754                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7755                 bookHit = NULL;
7756             }
7757         }
7758         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7759         SendToProgram(buf, cps);
7760         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7761     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7762         SendToProgram("go\n", cps);
7763         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7764     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7765         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7766             SendToProgram("go\n", cps);
7767         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7768     }
7769     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7770 }
7771
7772 char *savedMessage;
7773 ChessProgramState *savedState;
7774 void DeferredBookMove(void)
7775 {
7776         if(savedState->lastPing != savedState->lastPong)
7777                     ScheduleDelayedEvent(DeferredBookMove, 10);
7778         else
7779         HandleMachineMove(savedMessage, savedState);
7780 }
7781
7782 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7783
7784 void
7785 HandleMachineMove(message, cps)
7786      char *message;
7787      ChessProgramState *cps;
7788 {
7789     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7790     char realname[MSG_SIZ];
7791     int fromX, fromY, toX, toY;
7792     ChessMove moveType;
7793     char promoChar;
7794     char *p, *pv=buf1;
7795     int machineWhite;
7796     char *bookHit;
7797
7798     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7799         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7800         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7801             DisplayError(_("Invalid pairing from pairing engine"), 0);
7802             return;
7803         }
7804         pairingReceived = 1;
7805         NextMatchGame();
7806         return; // Skim the pairing messages here.
7807     }
7808
7809     cps->userError = 0;
7810
7811 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7812     /*
7813      * Kludge to ignore BEL characters
7814      */
7815     while (*message == '\007') message++;
7816
7817     /*
7818      * [HGM] engine debug message: ignore lines starting with '#' character
7819      */
7820     if(cps->debug && *message == '#') return;
7821
7822     /*
7823      * Look for book output
7824      */
7825     if (cps == &first && bookRequested) {
7826         if (message[0] == '\t' || message[0] == ' ') {
7827             /* Part of the book output is here; append it */
7828             strcat(bookOutput, message);
7829             strcat(bookOutput, "  \n");
7830             return;
7831         } else if (bookOutput[0] != NULLCHAR) {
7832             /* All of book output has arrived; display it */
7833             char *p = bookOutput;
7834             while (*p != NULLCHAR) {
7835                 if (*p == '\t') *p = ' ';
7836                 p++;
7837             }
7838             DisplayInformation(bookOutput);
7839             bookRequested = FALSE;
7840             /* Fall through to parse the current output */
7841         }
7842     }
7843
7844     /*
7845      * Look for machine move.
7846      */
7847     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7848         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7849     {
7850         /* This method is only useful on engines that support ping */
7851         if (cps->lastPing != cps->lastPong) {
7852           if (gameMode == BeginningOfGame) {
7853             /* Extra move from before last new; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857           } else {
7858             if (appData.debugMode) {
7859                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7860                         cps->which, gameMode);
7861             }
7862
7863             SendToProgram("undo\n", cps);
7864           }
7865           return;
7866         }
7867
7868         switch (gameMode) {
7869           case BeginningOfGame:
7870             /* Extra move from before last reset; ignore */
7871             if (appData.debugMode) {
7872                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7873             }
7874             return;
7875
7876           case EndOfGame:
7877           case IcsIdle:
7878           default:
7879             /* Extra move after we tried to stop.  The mode test is
7880                not a reliable way of detecting this problem, but it's
7881                the best we can do on engines that don't support ping.
7882             */
7883             if (appData.debugMode) {
7884                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7885                         cps->which, gameMode);
7886             }
7887             SendToProgram("undo\n", cps);
7888             return;
7889
7890           case MachinePlaysWhite:
7891           case IcsPlayingWhite:
7892             machineWhite = TRUE;
7893             break;
7894
7895           case MachinePlaysBlack:
7896           case IcsPlayingBlack:
7897             machineWhite = FALSE;
7898             break;
7899
7900           case TwoMachinesPlay:
7901             machineWhite = (cps->twoMachinesColor[0] == 'w');
7902             break;
7903         }
7904         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7905             if (appData.debugMode) {
7906                 fprintf(debugFP,
7907                         "Ignoring move out of turn by %s, gameMode %d"
7908                         ", forwardMost %d\n",
7909                         cps->which, gameMode, forwardMostMove);
7910             }
7911             return;
7912         }
7913
7914     if (appData.debugMode) { int f = forwardMostMove;
7915         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7916                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7917                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7918     }
7919         if(cps->alphaRank) AlphaRank(machineMove, 4);
7920         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7921                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7922             /* Machine move could not be parsed; ignore it. */
7923           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7924                     machineMove, _(cps->which));
7925             DisplayError(buf1, 0);
7926             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7927                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7928             if (gameMode == TwoMachinesPlay) {
7929               GameEnds(machineWhite ? BlackWins : WhiteWins,
7930                        buf1, GE_XBOARD);
7931             }
7932             return;
7933         }
7934
7935         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7936         /* So we have to redo legality test with true e.p. status here,  */
7937         /* to make sure an illegal e.p. capture does not slip through,   */
7938         /* to cause a forfeit on a justified illegal-move complaint      */
7939         /* of the opponent.                                              */
7940         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7941            ChessMove moveType;
7942            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7943                              fromY, fromX, toY, toX, promoChar);
7944             if (appData.debugMode) {
7945                 int i;
7946                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7947                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7948                 fprintf(debugFP, "castling rights\n");
7949             }
7950             if(moveType == IllegalMove) {
7951               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7952                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7953                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7954                            buf1, GE_XBOARD);
7955                 return;
7956            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7957            /* [HGM] Kludge to handle engines that send FRC-style castling
7958               when they shouldn't (like TSCP-Gothic) */
7959            switch(moveType) {
7960              case WhiteASideCastleFR:
7961              case BlackASideCastleFR:
7962                toX+=2;
7963                currentMoveString[2]++;
7964                break;
7965              case WhiteHSideCastleFR:
7966              case BlackHSideCastleFR:
7967                toX--;
7968                currentMoveString[2]--;
7969                break;
7970              default: ; // nothing to do, but suppresses warning of pedantic compilers
7971            }
7972         }
7973         hintRequested = FALSE;
7974         lastHint[0] = NULLCHAR;
7975         bookRequested = FALSE;
7976         /* Program may be pondering now */
7977         cps->maybeThinking = TRUE;
7978         if (cps->sendTime == 2) cps->sendTime = 1;
7979         if (cps->offeredDraw) cps->offeredDraw--;
7980
7981         /* [AS] Save move info*/
7982         pvInfoList[ forwardMostMove ].score = programStats.score;
7983         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7984         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7985
7986         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7987
7988         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7989         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7990             int count = 0;
7991
7992             while( count < adjudicateLossPlies ) {
7993                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7994
7995                 if( count & 1 ) {
7996                     score = -score; /* Flip score for winning side */
7997                 }
7998
7999                 if( score > adjudicateLossThreshold ) {
8000                     break;
8001                 }
8002
8003                 count++;
8004             }
8005
8006             if( count >= adjudicateLossPlies ) {
8007                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8008
8009                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8010                     "Xboard adjudication",
8011                     GE_XBOARD );
8012
8013                 return;
8014             }
8015         }
8016
8017         if(Adjudicate(cps)) {
8018             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8019             return; // [HGM] adjudicate: for all automatic game ends
8020         }
8021
8022 #if ZIPPY
8023         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8024             first.initDone) {
8025           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8026                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8027                 SendToICS("draw ");
8028                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8029           }
8030           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8031           ics_user_moved = 1;
8032           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8033                 char buf[3*MSG_SIZ];
8034
8035                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8036                         programStats.score / 100.,
8037                         programStats.depth,
8038                         programStats.time / 100.,
8039                         (unsigned int)programStats.nodes,
8040                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8041                         programStats.movelist);
8042                 SendToICS(buf);
8043 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8044           }
8045         }
8046 #endif
8047
8048         /* [AS] Clear stats for next move */
8049         ClearProgramStats();
8050         thinkOutput[0] = NULLCHAR;
8051         hiddenThinkOutputState = 0;
8052
8053         bookHit = NULL;
8054         if (gameMode == TwoMachinesPlay) {
8055             /* [HGM] relaying draw offers moved to after reception of move */
8056             /* and interpreting offer as claim if it brings draw condition */
8057             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8058                 SendToProgram("draw\n", cps->other);
8059             }
8060             if (cps->other->sendTime) {
8061                 SendTimeRemaining(cps->other,
8062                                   cps->other->twoMachinesColor[0] == 'w');
8063             }
8064             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8065             if (firstMove && !bookHit) {
8066                 firstMove = FALSE;
8067                 if (cps->other->useColors) {
8068                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8069                 }
8070                 SendToProgram("go\n", cps->other);
8071             }
8072             cps->other->maybeThinking = TRUE;
8073         }
8074
8075         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8076
8077         if (!pausing && appData.ringBellAfterMoves) {
8078             RingBell();
8079         }
8080
8081         /*
8082          * Reenable menu items that were disabled while
8083          * machine was thinking
8084          */
8085         if (gameMode != TwoMachinesPlay)
8086             SetUserThinkingEnables();
8087
8088         // [HGM] book: after book hit opponent has received move and is now in force mode
8089         // force the book reply into it, and then fake that it outputted this move by jumping
8090         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8091         if(bookHit) {
8092                 static char bookMove[MSG_SIZ]; // a bit generous?
8093
8094                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8095                 strcat(bookMove, bookHit);
8096                 message = bookMove;
8097                 cps = cps->other;
8098                 programStats.nodes = programStats.depth = programStats.time =
8099                 programStats.score = programStats.got_only_move = 0;
8100                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8101
8102                 if(cps->lastPing != cps->lastPong) {
8103                     savedMessage = message; // args for deferred call
8104                     savedState = cps;
8105                     ScheduleDelayedEvent(DeferredBookMove, 10);
8106                     return;
8107                 }
8108                 goto FakeBookMove;
8109         }
8110
8111         return;
8112     }
8113
8114     /* Set special modes for chess engines.  Later something general
8115      *  could be added here; for now there is just one kludge feature,
8116      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8117      *  when "xboard" is given as an interactive command.
8118      */
8119     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8120         cps->useSigint = FALSE;
8121         cps->useSigterm = FALSE;
8122     }
8123     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8124       ParseFeatures(message+8, cps);
8125       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8126     }
8127
8128     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8129                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8130       int dummy, s=6; char buf[MSG_SIZ];
8131       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8132       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8133       if(startedFromSetupPosition) return;
8134       ParseFEN(boards[0], &dummy, message+s);
8135       DrawPosition(TRUE, boards[0]);
8136       startedFromSetupPosition = TRUE;
8137       return;
8138     }
8139     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8140      * want this, I was asked to put it in, and obliged.
8141      */
8142     if (!strncmp(message, "setboard ", 9)) {
8143         Board initial_position;
8144
8145         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8146
8147         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8148             DisplayError(_("Bad FEN received from engine"), 0);
8149             return ;
8150         } else {
8151            Reset(TRUE, FALSE);
8152            CopyBoard(boards[0], initial_position);
8153            initialRulePlies = FENrulePlies;
8154            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8155            else gameMode = MachinePlaysBlack;
8156            DrawPosition(FALSE, boards[currentMove]);
8157         }
8158         return;
8159     }
8160
8161     /*
8162      * Look for communication commands
8163      */
8164     if (!strncmp(message, "telluser ", 9)) {
8165         if(message[9] == '\\' && message[10] == '\\')
8166             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8167         PlayTellSound();
8168         DisplayNote(message + 9);
8169         return;
8170     }
8171     if (!strncmp(message, "tellusererror ", 14)) {
8172         cps->userError = 1;
8173         if(message[14] == '\\' && message[15] == '\\')
8174             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8175         PlayTellSound();
8176         DisplayError(message + 14, 0);
8177         return;
8178     }
8179     if (!strncmp(message, "tellopponent ", 13)) {
8180       if (appData.icsActive) {
8181         if (loggedOn) {
8182           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8183           SendToICS(buf1);
8184         }
8185       } else {
8186         DisplayNote(message + 13);
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellothers ", 11)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8194           SendToICS(buf1);
8195         }
8196       }
8197       return;
8198     }
8199     if (!strncmp(message, "tellall ", 8)) {
8200       if (appData.icsActive) {
8201         if (loggedOn) {
8202           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8203           SendToICS(buf1);
8204         }
8205       } else {
8206         DisplayNote(message + 8);
8207       }
8208       return;
8209     }
8210     if (strncmp(message, "warning", 7) == 0) {
8211         /* Undocumented feature, use tellusererror in new code */
8212         DisplayError(message, 0);
8213         return;
8214     }
8215     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8216         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8217         strcat(realname, " query");
8218         AskQuestion(realname, buf2, buf1, cps->pr);
8219         return;
8220     }
8221     /* Commands from the engine directly to ICS.  We don't allow these to be
8222      *  sent until we are logged on. Crafty kibitzes have been known to
8223      *  interfere with the login process.
8224      */
8225     if (loggedOn) {
8226         if (!strncmp(message, "tellics ", 8)) {
8227             SendToICS(message + 8);
8228             SendToICS("\n");
8229             return;
8230         }
8231         if (!strncmp(message, "tellicsnoalias ", 15)) {
8232             SendToICS(ics_prefix);
8233             SendToICS(message + 15);
8234             SendToICS("\n");
8235             return;
8236         }
8237         /* The following are for backward compatibility only */
8238         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8239             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8240             SendToICS(ics_prefix);
8241             SendToICS(message);
8242             SendToICS("\n");
8243             return;
8244         }
8245     }
8246     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8247         return;
8248     }
8249     /*
8250      * If the move is illegal, cancel it and redraw the board.
8251      * Also deal with other error cases.  Matching is rather loose
8252      * here to accommodate engines written before the spec.
8253      */
8254     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8255         strncmp(message, "Error", 5) == 0) {
8256         if (StrStr(message, "name") ||
8257             StrStr(message, "rating") || StrStr(message, "?") ||
8258             StrStr(message, "result") || StrStr(message, "board") ||
8259             StrStr(message, "bk") || StrStr(message, "computer") ||
8260             StrStr(message, "variant") || StrStr(message, "hint") ||
8261             StrStr(message, "random") || StrStr(message, "depth") ||
8262             StrStr(message, "accepted")) {
8263             return;
8264         }
8265         if (StrStr(message, "protover")) {
8266           /* Program is responding to input, so it's apparently done
8267              initializing, and this error message indicates it is
8268              protocol version 1.  So we don't need to wait any longer
8269              for it to initialize and send feature commands. */
8270           FeatureDone(cps, 1);
8271           cps->protocolVersion = 1;
8272           return;
8273         }
8274         cps->maybeThinking = FALSE;
8275
8276         if (StrStr(message, "draw")) {
8277             /* Program doesn't have "draw" command */
8278             cps->sendDrawOffers = 0;
8279             return;
8280         }
8281         if (cps->sendTime != 1 &&
8282             (StrStr(message, "time") || StrStr(message, "otim"))) {
8283           /* Program apparently doesn't have "time" or "otim" command */
8284           cps->sendTime = 0;
8285           return;
8286         }
8287         if (StrStr(message, "analyze")) {
8288             cps->analysisSupport = FALSE;
8289             cps->analyzing = FALSE;
8290 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8291             EditGameEvent(); // [HGM] try to preserve loaded game
8292             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8293             DisplayError(buf2, 0);
8294             return;
8295         }
8296         if (StrStr(message, "(no matching move)st")) {
8297           /* Special kludge for GNU Chess 4 only */
8298           cps->stKludge = TRUE;
8299           SendTimeControl(cps, movesPerSession, timeControl,
8300                           timeIncrement, appData.searchDepth,
8301                           searchTime);
8302           return;
8303         }
8304         if (StrStr(message, "(no matching move)sd")) {
8305           /* Special kludge for GNU Chess 4 only */
8306           cps->sdKludge = TRUE;
8307           SendTimeControl(cps, movesPerSession, timeControl,
8308                           timeIncrement, appData.searchDepth,
8309                           searchTime);
8310           return;
8311         }
8312         if (!StrStr(message, "llegal")) {
8313             return;
8314         }
8315         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8316             gameMode == IcsIdle) return;
8317         if (forwardMostMove <= backwardMostMove) return;
8318         if (pausing) PauseEvent();
8319       if(appData.forceIllegal) {
8320             // [HGM] illegal: machine refused move; force position after move into it
8321           SendToProgram("force\n", cps);
8322           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8323                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8324                 // when black is to move, while there might be nothing on a2 or black
8325                 // might already have the move. So send the board as if white has the move.
8326                 // But first we must change the stm of the engine, as it refused the last move
8327                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8328                 if(WhiteOnMove(forwardMostMove)) {
8329                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8330                     SendBoard(cps, forwardMostMove); // kludgeless board
8331                 } else {
8332                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8333                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8334                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8335                 }
8336           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8337             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8338                  gameMode == TwoMachinesPlay)
8339               SendToProgram("go\n", cps);
8340             return;
8341       } else
8342         if (gameMode == PlayFromGameFile) {
8343             /* Stop reading this game file */
8344             gameMode = EditGame;
8345             ModeHighlight();
8346         }
8347         /* [HGM] illegal-move claim should forfeit game when Xboard */
8348         /* only passes fully legal moves                            */
8349         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8350             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8351                                 "False illegal-move claim", GE_XBOARD );
8352             return; // do not take back move we tested as valid
8353         }
8354         currentMove = forwardMostMove-1;
8355         DisplayMove(currentMove-1); /* before DisplayMoveError */
8356         SwitchClocks(forwardMostMove-1); // [HGM] race
8357         DisplayBothClocks();
8358         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8359                 parseList[currentMove], _(cps->which));
8360         DisplayMoveError(buf1);
8361         DrawPosition(FALSE, boards[currentMove]);
8362
8363         SetUserThinkingEnables();
8364         return;
8365     }
8366     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8367         /* Program has a broken "time" command that
8368            outputs a string not ending in newline.
8369            Don't use it. */
8370         cps->sendTime = 0;
8371     }
8372
8373     /*
8374      * If chess program startup fails, exit with an error message.
8375      * Attempts to recover here are futile.
8376      */
8377     if ((StrStr(message, "unknown host") != NULL)
8378         || (StrStr(message, "No remote directory") != NULL)
8379         || (StrStr(message, "not found") != NULL)
8380         || (StrStr(message, "No such file") != NULL)
8381         || (StrStr(message, "can't alloc") != NULL)
8382         || (StrStr(message, "Permission denied") != NULL)) {
8383
8384         cps->maybeThinking = FALSE;
8385         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8386                 _(cps->which), cps->program, cps->host, message);
8387         RemoveInputSource(cps->isr);
8388         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8389             if(cps == &first) appData.noChessProgram = TRUE;
8390             DisplayError(buf1, 0);
8391         }
8392         return;
8393     }
8394
8395     /*
8396      * Look for hint output
8397      */
8398     if (sscanf(message, "Hint: %s", buf1) == 1) {
8399         if (cps == &first && hintRequested) {
8400             hintRequested = FALSE;
8401             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8402                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8403                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8404                                     PosFlags(forwardMostMove),
8405                                     fromY, fromX, toY, toX, promoChar, buf1);
8406                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8407                 DisplayInformation(buf2);
8408             } else {
8409                 /* Hint move could not be parsed!? */
8410               snprintf(buf2, sizeof(buf2),
8411                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8412                         buf1, _(cps->which));
8413                 DisplayError(buf2, 0);
8414             }
8415         } else {
8416           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8417         }
8418         return;
8419     }
8420
8421     /*
8422      * Ignore other messages if game is not in progress
8423      */
8424     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8425         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8426
8427     /*
8428      * look for win, lose, draw, or draw offer
8429      */
8430     if (strncmp(message, "1-0", 3) == 0) {
8431         char *p, *q, *r = "";
8432         p = strchr(message, '{');
8433         if (p) {
8434             q = strchr(p, '}');
8435             if (q) {
8436                 *q = NULLCHAR;
8437                 r = p + 1;
8438             }
8439         }
8440         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8441         return;
8442     } else if (strncmp(message, "0-1", 3) == 0) {
8443         char *p, *q, *r = "";
8444         p = strchr(message, '{');
8445         if (p) {
8446             q = strchr(p, '}');
8447             if (q) {
8448                 *q = NULLCHAR;
8449                 r = p + 1;
8450             }
8451         }
8452         /* Kludge for Arasan 4.1 bug */
8453         if (strcmp(r, "Black resigns") == 0) {
8454             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8455             return;
8456         }
8457         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8458         return;
8459     } else if (strncmp(message, "1/2", 3) == 0) {
8460         char *p, *q, *r = "";
8461         p = strchr(message, '{');
8462         if (p) {
8463             q = strchr(p, '}');
8464             if (q) {
8465                 *q = NULLCHAR;
8466                 r = p + 1;
8467             }
8468         }
8469
8470         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8471         return;
8472
8473     } else if (strncmp(message, "White resign", 12) == 0) {
8474         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8475         return;
8476     } else if (strncmp(message, "Black resign", 12) == 0) {
8477         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8478         return;
8479     } else if (strncmp(message, "White matches", 13) == 0 ||
8480                strncmp(message, "Black matches", 13) == 0   ) {
8481         /* [HGM] ignore GNUShogi noises */
8482         return;
8483     } else if (strncmp(message, "White", 5) == 0 &&
8484                message[5] != '(' &&
8485                StrStr(message, "Black") == NULL) {
8486         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8487         return;
8488     } else if (strncmp(message, "Black", 5) == 0 &&
8489                message[5] != '(') {
8490         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8491         return;
8492     } else if (strcmp(message, "resign") == 0 ||
8493                strcmp(message, "computer resigns") == 0) {
8494         switch (gameMode) {
8495           case MachinePlaysBlack:
8496           case IcsPlayingBlack:
8497             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8498             break;
8499           case MachinePlaysWhite:
8500           case IcsPlayingWhite:
8501             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8502             break;
8503           case TwoMachinesPlay:
8504             if (cps->twoMachinesColor[0] == 'w')
8505               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8506             else
8507               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8508             break;
8509           default:
8510             /* can't happen */
8511             break;
8512         }
8513         return;
8514     } else if (strncmp(message, "opponent mates", 14) == 0) {
8515         switch (gameMode) {
8516           case MachinePlaysBlack:
8517           case IcsPlayingBlack:
8518             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8519             break;
8520           case MachinePlaysWhite:
8521           case IcsPlayingWhite:
8522             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8523             break;
8524           case TwoMachinesPlay:
8525             if (cps->twoMachinesColor[0] == 'w')
8526               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8527             else
8528               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8529             break;
8530           default:
8531             /* can't happen */
8532             break;
8533         }
8534         return;
8535     } else if (strncmp(message, "computer mates", 14) == 0) {
8536         switch (gameMode) {
8537           case MachinePlaysBlack:
8538           case IcsPlayingBlack:
8539             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8540             break;
8541           case MachinePlaysWhite:
8542           case IcsPlayingWhite:
8543             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8544             break;
8545           case TwoMachinesPlay:
8546             if (cps->twoMachinesColor[0] == 'w')
8547               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8548             else
8549               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8550             break;
8551           default:
8552             /* can't happen */
8553             break;
8554         }
8555         return;
8556     } else if (strncmp(message, "checkmate", 9) == 0) {
8557         if (WhiteOnMove(forwardMostMove)) {
8558             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8559         } else {
8560             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8561         }
8562         return;
8563     } else if (strstr(message, "Draw") != NULL ||
8564                strstr(message, "game is a draw") != NULL) {
8565         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8566         return;
8567     } else if (strstr(message, "offer") != NULL &&
8568                strstr(message, "draw") != NULL) {
8569 #if ZIPPY
8570         if (appData.zippyPlay && first.initDone) {
8571             /* Relay offer to ICS */
8572             SendToICS(ics_prefix);
8573             SendToICS("draw\n");
8574         }
8575 #endif
8576         cps->offeredDraw = 2; /* valid until this engine moves twice */
8577         if (gameMode == TwoMachinesPlay) {
8578             if (cps->other->offeredDraw) {
8579                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8580             /* [HGM] in two-machine mode we delay relaying draw offer      */
8581             /* until after we also have move, to see if it is really claim */
8582             }
8583         } else if (gameMode == MachinePlaysWhite ||
8584                    gameMode == MachinePlaysBlack) {
8585           if (userOfferedDraw) {
8586             DisplayInformation(_("Machine accepts your draw offer"));
8587             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8588           } else {
8589             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8590           }
8591         }
8592     }
8593
8594
8595     /*
8596      * Look for thinking output
8597      */
8598     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8599           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8600                                 ) {
8601         int plylev, mvleft, mvtot, curscore, time;
8602         char mvname[MOVE_LEN];
8603         u64 nodes; // [DM]
8604         char plyext;
8605         int ignore = FALSE;
8606         int prefixHint = FALSE;
8607         mvname[0] = NULLCHAR;
8608
8609         switch (gameMode) {
8610           case MachinePlaysBlack:
8611           case IcsPlayingBlack:
8612             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8613             break;
8614           case MachinePlaysWhite:
8615           case IcsPlayingWhite:
8616             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8617             break;
8618           case AnalyzeMode:
8619           case AnalyzeFile:
8620             break;
8621           case IcsObserving: /* [DM] icsEngineAnalyze */
8622             if (!appData.icsEngineAnalyze) ignore = TRUE;
8623             break;
8624           case TwoMachinesPlay:
8625             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8626                 ignore = TRUE;
8627             }
8628             break;
8629           default:
8630             ignore = TRUE;
8631             break;
8632         }
8633
8634         if (!ignore) {
8635             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8636             buf1[0] = NULLCHAR;
8637             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8638                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8639
8640                 if (plyext != ' ' && plyext != '\t') {
8641                     time *= 100;
8642                 }
8643
8644                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8645                 if( cps->scoreIsAbsolute &&
8646                     ( gameMode == MachinePlaysBlack ||
8647                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8648                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8649                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8650                      !WhiteOnMove(currentMove)
8651                     ) )
8652                 {
8653                     curscore = -curscore;
8654                 }
8655
8656                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8657
8658                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8659                         char buf[MSG_SIZ];
8660                         FILE *f;
8661                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8662                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8663                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8664                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8665                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8666                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8667                                 fclose(f);
8668                         } else DisplayError(_("failed writing PV"), 0);
8669                 }
8670
8671                 tempStats.depth = plylev;
8672                 tempStats.nodes = nodes;
8673                 tempStats.time = time;
8674                 tempStats.score = curscore;
8675                 tempStats.got_only_move = 0;
8676
8677                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8678                         int ticklen;
8679
8680                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8681                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8682                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8683                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8684                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8685                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8686                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8687                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8688                 }
8689
8690                 /* Buffer overflow protection */
8691                 if (pv[0] != NULLCHAR) {
8692                     if (strlen(pv) >= sizeof(tempStats.movelist)
8693                         && appData.debugMode) {
8694                         fprintf(debugFP,
8695                                 "PV is too long; using the first %u bytes.\n",
8696                                 (unsigned) sizeof(tempStats.movelist) - 1);
8697                     }
8698
8699                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8700                 } else {
8701                     sprintf(tempStats.movelist, " no PV\n");
8702                 }
8703
8704                 if (tempStats.seen_stat) {
8705                     tempStats.ok_to_send = 1;
8706                 }
8707
8708                 if (strchr(tempStats.movelist, '(') != NULL) {
8709                     tempStats.line_is_book = 1;
8710                     tempStats.nr_moves = 0;
8711                     tempStats.moves_left = 0;
8712                 } else {
8713                     tempStats.line_is_book = 0;
8714                 }
8715
8716                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8717                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8718
8719                 SendProgramStatsToFrontend( cps, &tempStats );
8720
8721                 /*
8722                     [AS] Protect the thinkOutput buffer from overflow... this
8723                     is only useful if buf1 hasn't overflowed first!
8724                 */
8725                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8726                          plylev,
8727                          (gameMode == TwoMachinesPlay ?
8728                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8729                          ((double) curscore) / 100.0,
8730                          prefixHint ? lastHint : "",
8731                          prefixHint ? " " : "" );
8732
8733                 if( buf1[0] != NULLCHAR ) {
8734                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8735
8736                     if( strlen(pv) > max_len ) {
8737                         if( appData.debugMode) {
8738                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8739                         }
8740                         pv[max_len+1] = '\0';
8741                     }
8742
8743                     strcat( thinkOutput, pv);
8744                 }
8745
8746                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8747                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8748                     DisplayMove(currentMove - 1);
8749                 }
8750                 return;
8751
8752             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8753                 /* crafty (9.25+) says "(only move) <move>"
8754                  * if there is only 1 legal move
8755                  */
8756                 sscanf(p, "(only move) %s", buf1);
8757                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8758                 sprintf(programStats.movelist, "%s (only move)", buf1);
8759                 programStats.depth = 1;
8760                 programStats.nr_moves = 1;
8761                 programStats.moves_left = 1;
8762                 programStats.nodes = 1;
8763                 programStats.time = 1;
8764                 programStats.got_only_move = 1;
8765
8766                 /* Not really, but we also use this member to
8767                    mean "line isn't going to change" (Crafty
8768                    isn't searching, so stats won't change) */
8769                 programStats.line_is_book = 1;
8770
8771                 SendProgramStatsToFrontend( cps, &programStats );
8772
8773                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8774                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8775                     DisplayMove(currentMove - 1);
8776                 }
8777                 return;
8778             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8779                               &time, &nodes, &plylev, &mvleft,
8780                               &mvtot, mvname) >= 5) {
8781                 /* The stat01: line is from Crafty (9.29+) in response
8782                    to the "." command */
8783                 programStats.seen_stat = 1;
8784                 cps->maybeThinking = TRUE;
8785
8786                 if (programStats.got_only_move || !appData.periodicUpdates)
8787                   return;
8788
8789                 programStats.depth = plylev;
8790                 programStats.time = time;
8791                 programStats.nodes = nodes;
8792                 programStats.moves_left = mvleft;
8793                 programStats.nr_moves = mvtot;
8794                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8795                 programStats.ok_to_send = 1;
8796                 programStats.movelist[0] = '\0';
8797
8798                 SendProgramStatsToFrontend( cps, &programStats );
8799
8800                 return;
8801
8802             } else if (strncmp(message,"++",2) == 0) {
8803                 /* Crafty 9.29+ outputs this */
8804                 programStats.got_fail = 2;
8805                 return;
8806
8807             } else if (strncmp(message,"--",2) == 0) {
8808                 /* Crafty 9.29+ outputs this */
8809                 programStats.got_fail = 1;
8810                 return;
8811
8812             } else if (thinkOutput[0] != NULLCHAR &&
8813                        strncmp(message, "    ", 4) == 0) {
8814                 unsigned message_len;
8815
8816                 p = message;
8817                 while (*p && *p == ' ') p++;
8818
8819                 message_len = strlen( p );
8820
8821                 /* [AS] Avoid buffer overflow */
8822                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8823                     strcat(thinkOutput, " ");
8824                     strcat(thinkOutput, p);
8825                 }
8826
8827                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8828                     strcat(programStats.movelist, " ");
8829                     strcat(programStats.movelist, p);
8830                 }
8831
8832                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8833                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8834                     DisplayMove(currentMove - 1);
8835                 }
8836                 return;
8837             }
8838         }
8839         else {
8840             buf1[0] = NULLCHAR;
8841
8842             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8843                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8844             {
8845                 ChessProgramStats cpstats;
8846
8847                 if (plyext != ' ' && plyext != '\t') {
8848                     time *= 100;
8849                 }
8850
8851                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8852                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8853                     curscore = -curscore;
8854                 }
8855
8856                 cpstats.depth = plylev;
8857                 cpstats.nodes = nodes;
8858                 cpstats.time = time;
8859                 cpstats.score = curscore;
8860                 cpstats.got_only_move = 0;
8861                 cpstats.movelist[0] = '\0';
8862
8863                 if (buf1[0] != NULLCHAR) {
8864                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8865                 }
8866
8867                 cpstats.ok_to_send = 0;
8868                 cpstats.line_is_book = 0;
8869                 cpstats.nr_moves = 0;
8870                 cpstats.moves_left = 0;
8871
8872                 SendProgramStatsToFrontend( cps, &cpstats );
8873             }
8874         }
8875     }
8876 }
8877
8878
8879 /* Parse a game score from the character string "game", and
8880    record it as the history of the current game.  The game
8881    score is NOT assumed to start from the standard position.
8882    The display is not updated in any way.
8883    */
8884 void
8885 ParseGameHistory(game)
8886      char *game;
8887 {
8888     ChessMove moveType;
8889     int fromX, fromY, toX, toY, boardIndex;
8890     char promoChar;
8891     char *p, *q;
8892     char buf[MSG_SIZ];
8893
8894     if (appData.debugMode)
8895       fprintf(debugFP, "Parsing game history: %s\n", game);
8896
8897     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8898     gameInfo.site = StrSave(appData.icsHost);
8899     gameInfo.date = PGNDate();
8900     gameInfo.round = StrSave("-");
8901
8902     /* Parse out names of players */
8903     while (*game == ' ') game++;
8904     p = buf;
8905     while (*game != ' ') *p++ = *game++;
8906     *p = NULLCHAR;
8907     gameInfo.white = StrSave(buf);
8908     while (*game == ' ') game++;
8909     p = buf;
8910     while (*game != ' ' && *game != '\n') *p++ = *game++;
8911     *p = NULLCHAR;
8912     gameInfo.black = StrSave(buf);
8913
8914     /* Parse moves */
8915     boardIndex = blackPlaysFirst ? 1 : 0;
8916     yynewstr(game);
8917     for (;;) {
8918         yyboardindex = boardIndex;
8919         moveType = (ChessMove) Myylex();
8920         switch (moveType) {
8921           case IllegalMove:             /* maybe suicide chess, etc. */
8922   if (appData.debugMode) {
8923     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8924     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8925     setbuf(debugFP, NULL);
8926   }
8927           case WhitePromotion:
8928           case BlackPromotion:
8929           case WhiteNonPromotion:
8930           case BlackNonPromotion:
8931           case NormalMove:
8932           case WhiteCapturesEnPassant:
8933           case BlackCapturesEnPassant:
8934           case WhiteKingSideCastle:
8935           case WhiteQueenSideCastle:
8936           case BlackKingSideCastle:
8937           case BlackQueenSideCastle:
8938           case WhiteKingSideCastleWild:
8939           case WhiteQueenSideCastleWild:
8940           case BlackKingSideCastleWild:
8941           case BlackQueenSideCastleWild:
8942           /* PUSH Fabien */
8943           case WhiteHSideCastleFR:
8944           case WhiteASideCastleFR:
8945           case BlackHSideCastleFR:
8946           case BlackASideCastleFR:
8947           /* POP Fabien */
8948             fromX = currentMoveString[0] - AAA;
8949             fromY = currentMoveString[1] - ONE;
8950             toX = currentMoveString[2] - AAA;
8951             toY = currentMoveString[3] - ONE;
8952             promoChar = currentMoveString[4];
8953             break;
8954           case WhiteDrop:
8955           case BlackDrop:
8956             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8957             fromX = moveType == WhiteDrop ?
8958               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8959             (int) CharToPiece(ToLower(currentMoveString[0]));
8960             fromY = DROP_RANK;
8961             toX = currentMoveString[2] - AAA;
8962             toY = currentMoveString[3] - ONE;
8963             promoChar = NULLCHAR;
8964             break;
8965           case AmbiguousMove:
8966             /* bug? */
8967             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8968   if (appData.debugMode) {
8969     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8970     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8971     setbuf(debugFP, NULL);
8972   }
8973             DisplayError(buf, 0);
8974             return;
8975           case ImpossibleMove:
8976             /* bug? */
8977             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8978   if (appData.debugMode) {
8979     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8980     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8981     setbuf(debugFP, NULL);
8982   }
8983             DisplayError(buf, 0);
8984             return;
8985           case EndOfFile:
8986             if (boardIndex < backwardMostMove) {
8987                 /* Oops, gap.  How did that happen? */
8988                 DisplayError(_("Gap in move list"), 0);
8989                 return;
8990             }
8991             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8992             if (boardIndex > forwardMostMove) {
8993                 forwardMostMove = boardIndex;
8994             }
8995             return;
8996           case ElapsedTime:
8997             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8998                 strcat(parseList[boardIndex-1], " ");
8999                 strcat(parseList[boardIndex-1], yy_text);
9000             }
9001             continue;
9002           case Comment:
9003           case PGNTag:
9004           case NAG:
9005           default:
9006             /* ignore */
9007             continue;
9008           case WhiteWins:
9009           case BlackWins:
9010           case GameIsDrawn:
9011           case GameUnfinished:
9012             if (gameMode == IcsExamining) {
9013                 if (boardIndex < backwardMostMove) {
9014                     /* Oops, gap.  How did that happen? */
9015                     return;
9016                 }
9017                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9018                 return;
9019             }
9020             gameInfo.result = moveType;
9021             p = strchr(yy_text, '{');
9022             if (p == NULL) p = strchr(yy_text, '(');
9023             if (p == NULL) {
9024                 p = yy_text;
9025                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9026             } else {
9027                 q = strchr(p, *p == '{' ? '}' : ')');
9028                 if (q != NULL) *q = NULLCHAR;
9029                 p++;
9030             }
9031             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9032             gameInfo.resultDetails = StrSave(p);
9033             continue;
9034         }
9035         if (boardIndex >= forwardMostMove &&
9036             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9037             backwardMostMove = blackPlaysFirst ? 1 : 0;
9038             return;
9039         }
9040         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9041                                  fromY, fromX, toY, toX, promoChar,
9042                                  parseList[boardIndex]);
9043         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9044         /* currentMoveString is set as a side-effect of yylex */
9045         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9046         strcat(moveList[boardIndex], "\n");
9047         boardIndex++;
9048         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9049         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9050           case MT_NONE:
9051           case MT_STALEMATE:
9052           default:
9053             break;
9054           case MT_CHECK:
9055             if(gameInfo.variant != VariantShogi)
9056                 strcat(parseList[boardIndex - 1], "+");
9057             break;
9058           case MT_CHECKMATE:
9059           case MT_STAINMATE:
9060             strcat(parseList[boardIndex - 1], "#");
9061             break;
9062         }
9063     }
9064 }
9065
9066
9067 /* Apply a move to the given board  */
9068 void
9069 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9070      int fromX, fromY, toX, toY;
9071      int promoChar;
9072      Board board;
9073 {
9074   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9075   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9076
9077     /* [HGM] compute & store e.p. status and castling rights for new position */
9078     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9079
9080       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9081       oldEP = (signed char)board[EP_STATUS];
9082       board[EP_STATUS] = EP_NONE;
9083
9084   if (fromY == DROP_RANK) {
9085         /* must be first */
9086         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9087             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9088             return;
9089         }
9090         piece = board[toY][toX] = (ChessSquare) fromX;
9091   } else {
9092       int i;
9093
9094       if( board[toY][toX] != EmptySquare )
9095            board[EP_STATUS] = EP_CAPTURE;
9096
9097       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9098            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9099                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9100       } else
9101       if( board[fromY][fromX] == WhitePawn ) {
9102            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9103                board[EP_STATUS] = EP_PAWN_MOVE;
9104            if( toY-fromY==2) {
9105                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9106                         gameInfo.variant != VariantBerolina || toX < fromX)
9107                       board[EP_STATUS] = toX | berolina;
9108                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9109                         gameInfo.variant != VariantBerolina || toX > fromX)
9110                       board[EP_STATUS] = toX;
9111            }
9112       } else
9113       if( board[fromY][fromX] == BlackPawn ) {
9114            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9115                board[EP_STATUS] = EP_PAWN_MOVE;
9116            if( toY-fromY== -2) {
9117                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9118                         gameInfo.variant != VariantBerolina || toX < fromX)
9119                       board[EP_STATUS] = toX | berolina;
9120                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9121                         gameInfo.variant != VariantBerolina || toX > fromX)
9122                       board[EP_STATUS] = toX;
9123            }
9124        }
9125
9126        for(i=0; i<nrCastlingRights; i++) {
9127            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9128               board[CASTLING][i] == toX   && castlingRank[i] == toY
9129              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9130        }
9131
9132      if (fromX == toX && fromY == toY) return;
9133
9134      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9135      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9136      if(gameInfo.variant == VariantKnightmate)
9137          king += (int) WhiteUnicorn - (int) WhiteKing;
9138
9139     /* Code added by Tord: */
9140     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9141     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9142         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9143       board[fromY][fromX] = EmptySquare;
9144       board[toY][toX] = EmptySquare;
9145       if((toX > fromX) != (piece == WhiteRook)) {
9146         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9147       } else {
9148         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9149       }
9150     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9151                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9152       board[fromY][fromX] = EmptySquare;
9153       board[toY][toX] = EmptySquare;
9154       if((toX > fromX) != (piece == BlackRook)) {
9155         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9156       } else {
9157         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9158       }
9159     /* End of code added by Tord */
9160
9161     } else if (board[fromY][fromX] == king
9162         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9163         && toY == fromY && toX > fromX+1) {
9164         board[fromY][fromX] = EmptySquare;
9165         board[toY][toX] = king;
9166         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9167         board[fromY][BOARD_RGHT-1] = EmptySquare;
9168     } else if (board[fromY][fromX] == king
9169         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9170                && toY == fromY && toX < fromX-1) {
9171         board[fromY][fromX] = EmptySquare;
9172         board[toY][toX] = king;
9173         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9174         board[fromY][BOARD_LEFT] = EmptySquare;
9175     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9176                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9177                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9178                ) {
9179         /* white pawn promotion */
9180         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9181         if(gameInfo.variant==VariantBughouse ||
9182            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9183             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9184         board[fromY][fromX] = EmptySquare;
9185     } else if ((fromY >= BOARD_HEIGHT>>1)
9186                && (toX != fromX)
9187                && gameInfo.variant != VariantXiangqi
9188                && gameInfo.variant != VariantBerolina
9189                && (board[fromY][fromX] == WhitePawn)
9190                && (board[toY][toX] == EmptySquare)) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = WhitePawn;
9193         captured = board[toY - 1][toX];
9194         board[toY - 1][toX] = EmptySquare;
9195     } else if ((fromY == BOARD_HEIGHT-4)
9196                && (toX == fromX)
9197                && gameInfo.variant == VariantBerolina
9198                && (board[fromY][fromX] == WhitePawn)
9199                && (board[toY][toX] == EmptySquare)) {
9200         board[fromY][fromX] = EmptySquare;
9201         board[toY][toX] = WhitePawn;
9202         if(oldEP & EP_BEROLIN_A) {
9203                 captured = board[fromY][fromX-1];
9204                 board[fromY][fromX-1] = EmptySquare;
9205         }else{  captured = board[fromY][fromX+1];
9206                 board[fromY][fromX+1] = EmptySquare;
9207         }
9208     } else if (board[fromY][fromX] == king
9209         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9210                && toY == fromY && toX > fromX+1) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = king;
9213         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9214         board[fromY][BOARD_RGHT-1] = EmptySquare;
9215     } else if (board[fromY][fromX] == king
9216         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9217                && toY == fromY && toX < fromX-1) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = king;
9220         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9221         board[fromY][BOARD_LEFT] = EmptySquare;
9222     } else if (fromY == 7 && fromX == 3
9223                && board[fromY][fromX] == BlackKing
9224                && toY == 7 && toX == 5) {
9225         board[fromY][fromX] = EmptySquare;
9226         board[toY][toX] = BlackKing;
9227         board[fromY][7] = EmptySquare;
9228         board[toY][4] = BlackRook;
9229     } else if (fromY == 7 && fromX == 3
9230                && board[fromY][fromX] == BlackKing
9231                && toY == 7 && toX == 1) {
9232         board[fromY][fromX] = EmptySquare;
9233         board[toY][toX] = BlackKing;
9234         board[fromY][0] = EmptySquare;
9235         board[toY][2] = BlackRook;
9236     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9237                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9238                && toY < promoRank && promoChar
9239                ) {
9240         /* black pawn promotion */
9241         board[toY][toX] = CharToPiece(ToLower(promoChar));
9242         if(gameInfo.variant==VariantBughouse ||
9243            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9244             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9245         board[fromY][fromX] = EmptySquare;
9246     } else if ((fromY < BOARD_HEIGHT>>1)
9247                && (toX != fromX)
9248                && gameInfo.variant != VariantXiangqi
9249                && gameInfo.variant != VariantBerolina
9250                && (board[fromY][fromX] == BlackPawn)
9251                && (board[toY][toX] == EmptySquare)) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = BlackPawn;
9254         captured = board[toY + 1][toX];
9255         board[toY + 1][toX] = EmptySquare;
9256     } else if ((fromY == 3)
9257                && (toX == fromX)
9258                && gameInfo.variant == VariantBerolina
9259                && (board[fromY][fromX] == BlackPawn)
9260                && (board[toY][toX] == EmptySquare)) {
9261         board[fromY][fromX] = EmptySquare;
9262         board[toY][toX] = BlackPawn;
9263         if(oldEP & EP_BEROLIN_A) {
9264                 captured = board[fromY][fromX-1];
9265                 board[fromY][fromX-1] = EmptySquare;
9266         }else{  captured = board[fromY][fromX+1];
9267                 board[fromY][fromX+1] = EmptySquare;
9268         }
9269     } else {
9270         board[toY][toX] = board[fromY][fromX];
9271         board[fromY][fromX] = EmptySquare;
9272     }
9273   }
9274
9275     if (gameInfo.holdingsWidth != 0) {
9276
9277       /* !!A lot more code needs to be written to support holdings  */
9278       /* [HGM] OK, so I have written it. Holdings are stored in the */
9279       /* penultimate board files, so they are automaticlly stored   */
9280       /* in the game history.                                       */
9281       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9282                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9283         /* Delete from holdings, by decreasing count */
9284         /* and erasing image if necessary            */
9285         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9286         if(p < (int) BlackPawn) { /* white drop */
9287              p -= (int)WhitePawn;
9288                  p = PieceToNumber((ChessSquare)p);
9289              if(p >= gameInfo.holdingsSize) p = 0;
9290              if(--board[p][BOARD_WIDTH-2] <= 0)
9291                   board[p][BOARD_WIDTH-1] = EmptySquare;
9292              if((int)board[p][BOARD_WIDTH-2] < 0)
9293                         board[p][BOARD_WIDTH-2] = 0;
9294         } else {                  /* black drop */
9295              p -= (int)BlackPawn;
9296                  p = PieceToNumber((ChessSquare)p);
9297              if(p >= gameInfo.holdingsSize) p = 0;
9298              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9299                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9300              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9301                         board[BOARD_HEIGHT-1-p][1] = 0;
9302         }
9303       }
9304       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9305           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9306         /* [HGM] holdings: Add to holdings, if holdings exist */
9307         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9308                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9309                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9310         }
9311         p = (int) captured;
9312         if (p >= (int) BlackPawn) {
9313           p -= (int)BlackPawn;
9314           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9315                   /* in Shogi restore piece to its original  first */
9316                   captured = (ChessSquare) (DEMOTED captured);
9317                   p = DEMOTED p;
9318           }
9319           p = PieceToNumber((ChessSquare)p);
9320           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9321           board[p][BOARD_WIDTH-2]++;
9322           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9323         } else {
9324           p -= (int)WhitePawn;
9325           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9326                   captured = (ChessSquare) (DEMOTED captured);
9327                   p = DEMOTED p;
9328           }
9329           p = PieceToNumber((ChessSquare)p);
9330           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9331           board[BOARD_HEIGHT-1-p][1]++;
9332           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9333         }
9334       }
9335     } else if (gameInfo.variant == VariantAtomic) {
9336       if (captured != EmptySquare) {
9337         int y, x;
9338         for (y = toY-1; y <= toY+1; y++) {
9339           for (x = toX-1; x <= toX+1; x++) {
9340             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9341                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9342               board[y][x] = EmptySquare;
9343             }
9344           }
9345         }
9346         board[toY][toX] = EmptySquare;
9347       }
9348     }
9349     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9350         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9351     } else
9352     if(promoChar == '+') {
9353         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9354         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9355     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9356         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9357     }
9358     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9359                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9360         // [HGM] superchess: take promotion piece out of holdings
9361         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9362         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9363             if(!--board[k][BOARD_WIDTH-2])
9364                 board[k][BOARD_WIDTH-1] = EmptySquare;
9365         } else {
9366             if(!--board[BOARD_HEIGHT-1-k][1])
9367                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9368         }
9369     }
9370
9371 }
9372
9373 /* Updates forwardMostMove */
9374 void
9375 MakeMove(fromX, fromY, toX, toY, promoChar)
9376      int fromX, fromY, toX, toY;
9377      int promoChar;
9378 {
9379 //    forwardMostMove++; // [HGM] bare: moved downstream
9380
9381     (void) CoordsToAlgebraic(boards[forwardMostMove],
9382                              PosFlags(forwardMostMove),
9383                              fromY, fromX, toY, toX, promoChar,
9384                              parseList[forwardMostMove]);
9385
9386     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9387         int timeLeft; static int lastLoadFlag=0; int king, piece;
9388         piece = boards[forwardMostMove][fromY][fromX];
9389         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9390         if(gameInfo.variant == VariantKnightmate)
9391             king += (int) WhiteUnicorn - (int) WhiteKing;
9392         if(forwardMostMove == 0) {
9393             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9394                 fprintf(serverMoves, "%s;", UserName());
9395             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9396                 fprintf(serverMoves, "%s;", second.tidy);
9397             fprintf(serverMoves, "%s;", first.tidy);
9398             if(gameMode == MachinePlaysWhite)
9399                 fprintf(serverMoves, "%s;", UserName());
9400             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9401                 fprintf(serverMoves, "%s;", second.tidy);
9402         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9403         lastLoadFlag = loadFlag;
9404         // print base move
9405         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9406         // print castling suffix
9407         if( toY == fromY && piece == king ) {
9408             if(toX-fromX > 1)
9409                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9410             if(fromX-toX >1)
9411                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9412         }
9413         // e.p. suffix
9414         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9415              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9416              boards[forwardMostMove][toY][toX] == EmptySquare
9417              && fromX != toX && fromY != toY)
9418                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9419         // promotion suffix
9420         if(promoChar != NULLCHAR)
9421                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9422         if(!loadFlag) {
9423                 char buf[MOVE_LEN*2], *p; int len;
9424             fprintf(serverMoves, "/%d/%d",
9425                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9426             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9427             else                      timeLeft = blackTimeRemaining/1000;
9428             fprintf(serverMoves, "/%d", timeLeft);
9429                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9430                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9431                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9432             fprintf(serverMoves, "/%s", buf);
9433         }
9434         fflush(serverMoves);
9435     }
9436
9437     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9438         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9439       return;
9440     }
9441     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9442     if (commentList[forwardMostMove+1] != NULL) {
9443         free(commentList[forwardMostMove+1]);
9444         commentList[forwardMostMove+1] = NULL;
9445     }
9446     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9447     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9448     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9449     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9450     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9451     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9452     adjustedClock = FALSE;
9453     gameInfo.result = GameUnfinished;
9454     if (gameInfo.resultDetails != NULL) {
9455         free(gameInfo.resultDetails);
9456         gameInfo.resultDetails = NULL;
9457     }
9458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9459                               moveList[forwardMostMove - 1]);
9460     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9461       case MT_NONE:
9462       case MT_STALEMATE:
9463       default:
9464         break;
9465       case MT_CHECK:
9466         if(gameInfo.variant != VariantShogi)
9467             strcat(parseList[forwardMostMove - 1], "+");
9468         break;
9469       case MT_CHECKMATE:
9470       case MT_STAINMATE:
9471         strcat(parseList[forwardMostMove - 1], "#");
9472         break;
9473     }
9474     if (appData.debugMode) {
9475         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9476     }
9477
9478 }
9479
9480 /* Updates currentMove if not pausing */
9481 void
9482 ShowMove(fromX, fromY, toX, toY)
9483 {
9484     int instant = (gameMode == PlayFromGameFile) ?
9485         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9486     if(appData.noGUI) return;
9487     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9488         if (!instant) {
9489             if (forwardMostMove == currentMove + 1) {
9490                 AnimateMove(boards[forwardMostMove - 1],
9491                             fromX, fromY, toX, toY);
9492             }
9493             if (appData.highlightLastMove) {
9494                 SetHighlights(fromX, fromY, toX, toY);
9495             }
9496         }
9497         currentMove = forwardMostMove;
9498     }
9499
9500     if (instant) return;
9501
9502     DisplayMove(currentMove - 1);
9503     DrawPosition(FALSE, boards[currentMove]);
9504     DisplayBothClocks();
9505     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9506 }
9507
9508 void SendEgtPath(ChessProgramState *cps)
9509 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9510         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9511
9512         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9513
9514         while(*p) {
9515             char c, *q = name+1, *r, *s;
9516
9517             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9518             while(*p && *p != ',') *q++ = *p++;
9519             *q++ = ':'; *q = 0;
9520             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9521                 strcmp(name, ",nalimov:") == 0 ) {
9522                 // take nalimov path from the menu-changeable option first, if it is defined
9523               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9524                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9525             } else
9526             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9527                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9528                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9529                 s = r = StrStr(s, ":") + 1; // beginning of path info
9530                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9531                 c = *r; *r = 0;             // temporarily null-terminate path info
9532                     *--q = 0;               // strip of trailig ':' from name
9533                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9534                 *r = c;
9535                 SendToProgram(buf,cps);     // send egtbpath command for this format
9536             }
9537             if(*p == ',') p++; // read away comma to position for next format name
9538         }
9539 }
9540
9541 void
9542 InitChessProgram(cps, setup)
9543      ChessProgramState *cps;
9544      int setup; /* [HGM] needed to setup FRC opening position */
9545 {
9546     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9547     if (appData.noChessProgram) return;
9548     hintRequested = FALSE;
9549     bookRequested = FALSE;
9550
9551     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9552     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9553     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9554     if(cps->memSize) { /* [HGM] memory */
9555       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9556         SendToProgram(buf, cps);
9557     }
9558     SendEgtPath(cps); /* [HGM] EGT */
9559     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9560       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9561         SendToProgram(buf, cps);
9562     }
9563
9564     SendToProgram(cps->initString, cps);
9565     if (gameInfo.variant != VariantNormal &&
9566         gameInfo.variant != VariantLoadable
9567         /* [HGM] also send variant if board size non-standard */
9568         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9569                                             ) {
9570       char *v = VariantName(gameInfo.variant);
9571       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9572         /* [HGM] in protocol 1 we have to assume all variants valid */
9573         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9574         DisplayFatalError(buf, 0, 1);
9575         return;
9576       }
9577
9578       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9579       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantXiangqi )
9581            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9582       if( gameInfo.variant == VariantShogi )
9583            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9584       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9586       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9587           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9588            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9589       if( gameInfo.variant == VariantCourier )
9590            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9591       if( gameInfo.variant == VariantSuper )
9592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9593       if( gameInfo.variant == VariantGreat )
9594            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9595       if( gameInfo.variant == VariantSChess )
9596            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9597       if( gameInfo.variant == VariantGrand )
9598            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9599
9600       if(overruled) {
9601         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9602                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9603            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9604            if(StrStr(cps->variants, b) == NULL) {
9605                // specific sized variant not known, check if general sizing allowed
9606                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9607                    if(StrStr(cps->variants, "boardsize") == NULL) {
9608                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9609                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9610                        DisplayFatalError(buf, 0, 1);
9611                        return;
9612                    }
9613                    /* [HGM] here we really should compare with the maximum supported board size */
9614                }
9615            }
9616       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9617       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9618       SendToProgram(buf, cps);
9619     }
9620     currentlyInitializedVariant = gameInfo.variant;
9621
9622     /* [HGM] send opening position in FRC to first engine */
9623     if(setup) {
9624           SendToProgram("force\n", cps);
9625           SendBoard(cps, 0);
9626           /* engine is now in force mode! Set flag to wake it up after first move. */
9627           setboardSpoiledMachineBlack = 1;
9628     }
9629
9630     if (cps->sendICS) {
9631       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9632       SendToProgram(buf, cps);
9633     }
9634     cps->maybeThinking = FALSE;
9635     cps->offeredDraw = 0;
9636     if (!appData.icsActive) {
9637         SendTimeControl(cps, movesPerSession, timeControl,
9638                         timeIncrement, appData.searchDepth,
9639                         searchTime);
9640     }
9641     if (appData.showThinking
9642         // [HGM] thinking: four options require thinking output to be sent
9643         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9644                                 ) {
9645         SendToProgram("post\n", cps);
9646     }
9647     SendToProgram("hard\n", cps);
9648     if (!appData.ponderNextMove) {
9649         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9650            it without being sure what state we are in first.  "hard"
9651            is not a toggle, so that one is OK.
9652          */
9653         SendToProgram("easy\n", cps);
9654     }
9655     if (cps->usePing) {
9656       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9657       SendToProgram(buf, cps);
9658     }
9659     cps->initDone = TRUE;
9660     ClearEngineOutputPane(cps == &second);
9661 }
9662
9663
9664 void
9665 StartChessProgram(cps)
9666      ChessProgramState *cps;
9667 {
9668     char buf[MSG_SIZ];
9669     int err;
9670
9671     if (appData.noChessProgram) return;
9672     cps->initDone = FALSE;
9673
9674     if (strcmp(cps->host, "localhost") == 0) {
9675         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9676     } else if (*appData.remoteShell == NULLCHAR) {
9677         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9678     } else {
9679         if (*appData.remoteUser == NULLCHAR) {
9680           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9681                     cps->program);
9682         } else {
9683           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9684                     cps->host, appData.remoteUser, cps->program);
9685         }
9686         err = StartChildProcess(buf, "", &cps->pr);
9687     }
9688
9689     if (err != 0) {
9690       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9691         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9692         if(cps != &first) return;
9693         appData.noChessProgram = TRUE;
9694         ThawUI();
9695         SetNCPMode();
9696 //      DisplayFatalError(buf, err, 1);
9697 //      cps->pr = NoProc;
9698 //      cps->isr = NULL;
9699         return;
9700     }
9701
9702     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9703     if (cps->protocolVersion > 1) {
9704       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9705       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9706       cps->comboCnt = 0;  //                and values of combo boxes
9707       SendToProgram(buf, cps);
9708     } else {
9709       SendToProgram("xboard\n", cps);
9710     }
9711 }
9712
9713 void
9714 TwoMachinesEventIfReady P((void))
9715 {
9716   static int curMess = 0;
9717   if (first.lastPing != first.lastPong) {
9718     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9719     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9720     return;
9721   }
9722   if (second.lastPing != second.lastPong) {
9723     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9724     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9725     return;
9726   }
9727   DisplayMessage("", ""); curMess = 0;
9728   ThawUI();
9729   TwoMachinesEvent();
9730 }
9731
9732 char *
9733 MakeName(char *template)
9734 {
9735     time_t clock;
9736     struct tm *tm;
9737     static char buf[MSG_SIZ];
9738     char *p = buf;
9739     int i;
9740
9741     clock = time((time_t *)NULL);
9742     tm = localtime(&clock);
9743
9744     while(*p++ = *template++) if(p[-1] == '%') {
9745         switch(*template++) {
9746           case 0:   *p = 0; return buf;
9747           case 'Y': i = tm->tm_year+1900; break;
9748           case 'y': i = tm->tm_year-100; break;
9749           case 'M': i = tm->tm_mon+1; break;
9750           case 'd': i = tm->tm_mday; break;
9751           case 'h': i = tm->tm_hour; break;
9752           case 'm': i = tm->tm_min; break;
9753           case 's': i = tm->tm_sec; break;
9754           default:  i = 0;
9755         }
9756         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9757     }
9758     return buf;
9759 }
9760
9761 int
9762 CountPlayers(char *p)
9763 {
9764     int n = 0;
9765     while(p = strchr(p, '\n')) p++, n++; // count participants
9766     return n;
9767 }
9768
9769 FILE *
9770 WriteTourneyFile(char *results, FILE *f)
9771 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9772     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9773     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9774         // create a file with tournament description
9775         fprintf(f, "-participants {%s}\n", appData.participants);
9776         fprintf(f, "-seedBase %d\n", appData.seedBase);
9777         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9778         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9779         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9780         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9781         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9782         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9783         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9784         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9785         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9786         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9787         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9788         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9789         if(searchTime > 0)
9790                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9791         else {
9792                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9793                 fprintf(f, "-tc %s\n", appData.timeControl);
9794                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9795         }
9796         fprintf(f, "-results \"%s\"\n", results);
9797     }
9798     return f;
9799 }
9800
9801 #define MAXENGINES 1000
9802 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9803
9804 void Substitute(char *participants, int expunge)
9805 {
9806     int i, changed, changes=0, nPlayers=0;
9807     char *p, *q, *r, buf[MSG_SIZ];
9808     if(participants == NULL) return;
9809     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9810     r = p = participants; q = appData.participants;
9811     while(*p && *p == *q) {
9812         if(*p == '\n') r = p+1, nPlayers++;
9813         p++; q++;
9814     }
9815     if(*p) { // difference
9816         while(*p && *p++ != '\n');
9817         while(*q && *q++ != '\n');
9818       changed = nPlayers;
9819         changes = 1 + (strcmp(p, q) != 0);
9820     }
9821     if(changes == 1) { // a single engine mnemonic was changed
9822         q = r; while(*q) nPlayers += (*q++ == '\n');
9823         p = buf; while(*r && (*p = *r++) != '\n') p++;
9824         *p = NULLCHAR;
9825         NamesToList(firstChessProgramNames, command, mnemonic);
9826         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9827         if(mnemonic[i]) { // The substitute is valid
9828             FILE *f;
9829             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9830                 flock(fileno(f), LOCK_EX);
9831                 ParseArgsFromFile(f);
9832                 fseek(f, 0, SEEK_SET);
9833                 FREE(appData.participants); appData.participants = participants;
9834                 if(expunge) { // erase results of replaced engine
9835                     int len = strlen(appData.results), w, b, dummy;
9836                     for(i=0; i<len; i++) {
9837                         Pairing(i, nPlayers, &w, &b, &dummy);
9838                         if((w == changed || b == changed) && appData.results[i] == '*') {
9839                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9840                             fclose(f);
9841                             return;
9842                         }
9843                     }
9844                     for(i=0; i<len; i++) {
9845                         Pairing(i, nPlayers, &w, &b, &dummy);
9846                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9847                     }
9848                 }
9849                 WriteTourneyFile(appData.results, f);
9850                 fclose(f); // release lock
9851                 return;
9852             }
9853         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9854     }
9855     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9856     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9857     free(participants);
9858     return;
9859 }
9860
9861 int
9862 CreateTourney(char *name)
9863 {
9864         FILE *f;
9865         if(matchMode && strcmp(name, appData.tourneyFile)) {
9866              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9867         }
9868         if(name[0] == NULLCHAR) {
9869             if(appData.participants[0])
9870                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9871             return 0;
9872         }
9873         f = fopen(name, "r");
9874         if(f) { // file exists
9875             ASSIGN(appData.tourneyFile, name);
9876             ParseArgsFromFile(f); // parse it
9877         } else {
9878             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9879             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9880                 DisplayError(_("Not enough participants"), 0);
9881                 return 0;
9882             }
9883             ASSIGN(appData.tourneyFile, name);
9884             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9885             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9886         }
9887         fclose(f);
9888         appData.noChessProgram = FALSE;
9889         appData.clockMode = TRUE;
9890         SetGNUMode();
9891         return 1;
9892 }
9893
9894 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9895 {
9896     char buf[MSG_SIZ], *p, *q;
9897     int i=1;
9898     while(*names) {
9899         p = names; q = buf;
9900         while(*p && *p != '\n') *q++ = *p++;
9901         *q = 0;
9902         if(engineList[i]) free(engineList[i]);
9903         engineList[i] = strdup(buf);
9904         if(*p == '\n') p++;
9905         TidyProgramName(engineList[i], "localhost", buf);
9906         if(engineMnemonic[i]) free(engineMnemonic[i]);
9907         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9908             strcat(buf, " (");
9909             sscanf(q + 8, "%s", buf + strlen(buf));
9910             strcat(buf, ")");
9911         }
9912         engineMnemonic[i] = strdup(buf);
9913         names = p; i++;
9914       if(i > MAXENGINES - 2) break;
9915     }
9916     engineList[i] = engineMnemonic[i] = NULL;
9917 }
9918
9919 // following implemented as macro to avoid type limitations
9920 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9921
9922 void SwapEngines(int n)
9923 {   // swap settings for first engine and other engine (so far only some selected options)
9924     int h;
9925     char *p;
9926     if(n == 0) return;
9927     SWAP(directory, p)
9928     SWAP(chessProgram, p)
9929     SWAP(isUCI, h)
9930     SWAP(hasOwnBookUCI, h)
9931     SWAP(protocolVersion, h)
9932     SWAP(reuse, h)
9933     SWAP(scoreIsAbsolute, h)
9934     SWAP(timeOdds, h)
9935     SWAP(logo, p)
9936     SWAP(pgnName, p)
9937     SWAP(pvSAN, h)
9938     SWAP(engOptions, p)
9939 }
9940
9941 void
9942 SetPlayer(int player)
9943 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9944     int i;
9945     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9946     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9947     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9948     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9949     if(mnemonic[i]) {
9950         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9951         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9952         appData.firstHasOwnBookUCI = !appData.defNoBook;
9953         ParseArgsFromString(buf);
9954     }
9955     free(engineName);
9956 }
9957
9958 int
9959 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9960 {   // determine players from game number
9961     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9962
9963     if(appData.tourneyType == 0) {
9964         roundsPerCycle = (nPlayers - 1) | 1;
9965         pairingsPerRound = nPlayers / 2;
9966     } else if(appData.tourneyType > 0) {
9967         roundsPerCycle = nPlayers - appData.tourneyType;
9968         pairingsPerRound = appData.tourneyType;
9969     }
9970     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9971     gamesPerCycle = gamesPerRound * roundsPerCycle;
9972     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9973     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9974     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9975     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9976     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9977     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9978
9979     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9980     if(appData.roundSync) *syncInterval = gamesPerRound;
9981
9982     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9983
9984     if(appData.tourneyType == 0) {
9985         if(curPairing == (nPlayers-1)/2 ) {
9986             *whitePlayer = curRound;
9987             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9988         } else {
9989             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9990             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9991             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9992             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9993         }
9994     } else if(appData.tourneyType > 0) {
9995         *whitePlayer = curPairing;
9996         *blackPlayer = curRound + appData.tourneyType;
9997     }
9998
9999     // take care of white/black alternation per round. 
10000     // For cycles and games this is already taken care of by default, derived from matchGame!
10001     return curRound & 1;
10002 }
10003
10004 int
10005 NextTourneyGame(int nr, int *swapColors)
10006 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10007     char *p, *q;
10008     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10009     FILE *tf;
10010     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10011     tf = fopen(appData.tourneyFile, "r");
10012     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10013     ParseArgsFromFile(tf); fclose(tf);
10014     InitTimeControls(); // TC might be altered from tourney file
10015
10016     nPlayers = CountPlayers(appData.participants); // count participants
10017     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10018     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10019
10020     if(syncInterval) {
10021         p = q = appData.results;
10022         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10023         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10024             DisplayMessage(_("Waiting for other game(s)"),"");
10025             waitingForGame = TRUE;
10026             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10027             return 0;
10028         }
10029         waitingForGame = FALSE;
10030     }
10031
10032     if(appData.tourneyType < 0) {
10033         if(nr>=0 && !pairingReceived) {
10034             char buf[1<<16];
10035             if(pairing.pr == NoProc) {
10036                 if(!appData.pairingEngine[0]) {
10037                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10038                     return 0;
10039                 }
10040                 StartChessProgram(&pairing); // starts the pairing engine
10041             }
10042             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10043             SendToProgram(buf, &pairing);
10044             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10045             SendToProgram(buf, &pairing);
10046             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10047         }
10048         pairingReceived = 0;                              // ... so we continue here 
10049         *swapColors = 0;
10050         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10051         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10052         matchGame = 1; roundNr = nr / syncInterval + 1;
10053     }
10054
10055     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10056
10057     // redefine engines, engine dir, etc.
10058     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10059     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10060     SwapEngines(1);
10061     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10062     SwapEngines(1);         // and make that valid for second engine by swapping
10063     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10064     InitEngine(&second, 1);
10065     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10066     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10067     return 1;
10068 }
10069
10070 void
10071 NextMatchGame()
10072 {   // performs game initialization that does not invoke engines, and then tries to start the game
10073     int res, firstWhite, swapColors = 0;
10074     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10075     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10076     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10077     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10078     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10079     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10080     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10081     Reset(FALSE, first.pr != NoProc);
10082     res = LoadGameOrPosition(matchGame); // setup game
10083     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10084     if(!res) return; // abort when bad game/pos file
10085     TwoMachinesEvent();
10086 }
10087
10088 void UserAdjudicationEvent( int result )
10089 {
10090     ChessMove gameResult = GameIsDrawn;
10091
10092     if( result > 0 ) {
10093         gameResult = WhiteWins;
10094     }
10095     else if( result < 0 ) {
10096         gameResult = BlackWins;
10097     }
10098
10099     if( gameMode == TwoMachinesPlay ) {
10100         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10101     }
10102 }
10103
10104
10105 // [HGM] save: calculate checksum of game to make games easily identifiable
10106 int StringCheckSum(char *s)
10107 {
10108         int i = 0;
10109         if(s==NULL) return 0;
10110         while(*s) i = i*259 + *s++;
10111         return i;
10112 }
10113
10114 int GameCheckSum()
10115 {
10116         int i, sum=0;
10117         for(i=backwardMostMove; i<forwardMostMove; i++) {
10118                 sum += pvInfoList[i].depth;
10119                 sum += StringCheckSum(parseList[i]);
10120                 sum += StringCheckSum(commentList[i]);
10121                 sum *= 261;
10122         }
10123         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10124         return sum + StringCheckSum(commentList[i]);
10125 } // end of save patch
10126
10127 void
10128 GameEnds(result, resultDetails, whosays)
10129      ChessMove result;
10130      char *resultDetails;
10131      int whosays;
10132 {
10133     GameMode nextGameMode;
10134     int isIcsGame;
10135     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10136
10137     if(endingGame) return; /* [HGM] crash: forbid recursion */
10138     endingGame = 1;
10139     if(twoBoards) { // [HGM] dual: switch back to one board
10140         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10141         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10142     }
10143     if (appData.debugMode) {
10144       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10145               result, resultDetails ? resultDetails : "(null)", whosays);
10146     }
10147
10148     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10149
10150     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10151         /* If we are playing on ICS, the server decides when the
10152            game is over, but the engine can offer to draw, claim
10153            a draw, or resign.
10154          */
10155 #if ZIPPY
10156         if (appData.zippyPlay && first.initDone) {
10157             if (result == GameIsDrawn) {
10158                 /* In case draw still needs to be claimed */
10159                 SendToICS(ics_prefix);
10160                 SendToICS("draw\n");
10161             } else if (StrCaseStr(resultDetails, "resign")) {
10162                 SendToICS(ics_prefix);
10163                 SendToICS("resign\n");
10164             }
10165         }
10166 #endif
10167         endingGame = 0; /* [HGM] crash */
10168         return;
10169     }
10170
10171     /* If we're loading the game from a file, stop */
10172     if (whosays == GE_FILE) {
10173       (void) StopLoadGameTimer();
10174       gameFileFP = NULL;
10175     }
10176
10177     /* Cancel draw offers */
10178     first.offeredDraw = second.offeredDraw = 0;
10179
10180     /* If this is an ICS game, only ICS can really say it's done;
10181        if not, anyone can. */
10182     isIcsGame = (gameMode == IcsPlayingWhite ||
10183                  gameMode == IcsPlayingBlack ||
10184                  gameMode == IcsObserving    ||
10185                  gameMode == IcsExamining);
10186
10187     if (!isIcsGame || whosays == GE_ICS) {
10188         /* OK -- not an ICS game, or ICS said it was done */
10189         StopClocks();
10190         if (!isIcsGame && !appData.noChessProgram)
10191           SetUserThinkingEnables();
10192
10193         /* [HGM] if a machine claims the game end we verify this claim */
10194         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10195             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10196                 char claimer;
10197                 ChessMove trueResult = (ChessMove) -1;
10198
10199                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10200                                             first.twoMachinesColor[0] :
10201                                             second.twoMachinesColor[0] ;
10202
10203                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10206                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10207                 } else
10208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10209                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10210                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10211                 } else
10212                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10213                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10214                 }
10215
10216                 // now verify win claims, but not in drop games, as we don't understand those yet
10217                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10218                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10219                     (result == WhiteWins && claimer == 'w' ||
10220                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10221                       if (appData.debugMode) {
10222                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10223                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10224                       }
10225                       if(result != trueResult) {
10226                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10227                               result = claimer == 'w' ? BlackWins : WhiteWins;
10228                               resultDetails = buf;
10229                       }
10230                 } else
10231                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10232                     && (forwardMostMove <= backwardMostMove ||
10233                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10234                         (claimer=='b')==(forwardMostMove&1))
10235                                                                                   ) {
10236                       /* [HGM] verify: draws that were not flagged are false claims */
10237                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10238                       result = claimer == 'w' ? BlackWins : WhiteWins;
10239                       resultDetails = buf;
10240                 }
10241                 /* (Claiming a loss is accepted no questions asked!) */
10242             }
10243             /* [HGM] bare: don't allow bare King to win */
10244             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10245                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10246                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10247                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10248                && result != GameIsDrawn)
10249             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10250                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10251                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10252                         if(p >= 0 && p <= (int)WhiteKing) k++;
10253                 }
10254                 if (appData.debugMode) {
10255                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10256                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10257                 }
10258                 if(k <= 1) {
10259                         result = GameIsDrawn;
10260                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10261                         resultDetails = buf;
10262                 }
10263             }
10264         }
10265
10266
10267         if(serverMoves != NULL && !loadFlag) { char c = '=';
10268             if(result==WhiteWins) c = '+';
10269             if(result==BlackWins) c = '-';
10270             if(resultDetails != NULL)
10271                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10272         }
10273         if (resultDetails != NULL) {
10274             gameInfo.result = result;
10275             gameInfo.resultDetails = StrSave(resultDetails);
10276
10277             /* display last move only if game was not loaded from file */
10278             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10279                 DisplayMove(currentMove - 1);
10280
10281             if (forwardMostMove != 0) {
10282                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10283                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10284                                                                 ) {
10285                     if (*appData.saveGameFile != NULLCHAR) {
10286                         SaveGameToFile(appData.saveGameFile, TRUE);
10287                     } else if (appData.autoSaveGames) {
10288                         AutoSaveGame();
10289                     }
10290                     if (*appData.savePositionFile != NULLCHAR) {
10291                         SavePositionToFile(appData.savePositionFile);
10292                     }
10293                 }
10294             }
10295
10296             /* Tell program how game ended in case it is learning */
10297             /* [HGM] Moved this to after saving the PGN, just in case */
10298             /* engine died and we got here through time loss. In that */
10299             /* case we will get a fatal error writing the pipe, which */
10300             /* would otherwise lose us the PGN.                       */
10301             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10302             /* output during GameEnds should never be fatal anymore   */
10303             if (gameMode == MachinePlaysWhite ||
10304                 gameMode == MachinePlaysBlack ||
10305                 gameMode == TwoMachinesPlay ||
10306                 gameMode == IcsPlayingWhite ||
10307                 gameMode == IcsPlayingBlack ||
10308                 gameMode == BeginningOfGame) {
10309                 char buf[MSG_SIZ];
10310                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10311                         resultDetails);
10312                 if (first.pr != NoProc) {
10313                     SendToProgram(buf, &first);
10314                 }
10315                 if (second.pr != NoProc &&
10316                     gameMode == TwoMachinesPlay) {
10317                     SendToProgram(buf, &second);
10318                 }
10319             }
10320         }
10321
10322         if (appData.icsActive) {
10323             if (appData.quietPlay &&
10324                 (gameMode == IcsPlayingWhite ||
10325                  gameMode == IcsPlayingBlack)) {
10326                 SendToICS(ics_prefix);
10327                 SendToICS("set shout 1\n");
10328             }
10329             nextGameMode = IcsIdle;
10330             ics_user_moved = FALSE;
10331             /* clean up premove.  It's ugly when the game has ended and the
10332              * premove highlights are still on the board.
10333              */
10334             if (gotPremove) {
10335               gotPremove = FALSE;
10336               ClearPremoveHighlights();
10337               DrawPosition(FALSE, boards[currentMove]);
10338             }
10339             if (whosays == GE_ICS) {
10340                 switch (result) {
10341                 case WhiteWins:
10342                     if (gameMode == IcsPlayingWhite)
10343                         PlayIcsWinSound();
10344                     else if(gameMode == IcsPlayingBlack)
10345                         PlayIcsLossSound();
10346                     break;
10347                 case BlackWins:
10348                     if (gameMode == IcsPlayingBlack)
10349                         PlayIcsWinSound();
10350                     else if(gameMode == IcsPlayingWhite)
10351                         PlayIcsLossSound();
10352                     break;
10353                 case GameIsDrawn:
10354                     PlayIcsDrawSound();
10355                     break;
10356                 default:
10357                     PlayIcsUnfinishedSound();
10358                 }
10359             }
10360         } else if (gameMode == EditGame ||
10361                    gameMode == PlayFromGameFile ||
10362                    gameMode == AnalyzeMode ||
10363                    gameMode == AnalyzeFile) {
10364             nextGameMode = gameMode;
10365         } else {
10366             nextGameMode = EndOfGame;
10367         }
10368         pausing = FALSE;
10369         ModeHighlight();
10370     } else {
10371         nextGameMode = gameMode;
10372     }
10373
10374     if (appData.noChessProgram) {
10375         gameMode = nextGameMode;
10376         ModeHighlight();
10377         endingGame = 0; /* [HGM] crash */
10378         return;
10379     }
10380
10381     if (first.reuse) {
10382         /* Put first chess program into idle state */
10383         if (first.pr != NoProc &&
10384             (gameMode == MachinePlaysWhite ||
10385              gameMode == MachinePlaysBlack ||
10386              gameMode == TwoMachinesPlay ||
10387              gameMode == IcsPlayingWhite ||
10388              gameMode == IcsPlayingBlack ||
10389              gameMode == BeginningOfGame)) {
10390             SendToProgram("force\n", &first);
10391             if (first.usePing) {
10392               char buf[MSG_SIZ];
10393               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10394               SendToProgram(buf, &first);
10395             }
10396         }
10397     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10398         /* Kill off first chess program */
10399         if (first.isr != NULL)
10400           RemoveInputSource(first.isr);
10401         first.isr = NULL;
10402
10403         if (first.pr != NoProc) {
10404             ExitAnalyzeMode();
10405             DoSleep( appData.delayBeforeQuit );
10406             SendToProgram("quit\n", &first);
10407             DoSleep( appData.delayAfterQuit );
10408             DestroyChildProcess(first.pr, first.useSigterm);
10409         }
10410         first.pr = NoProc;
10411     }
10412     if (second.reuse) {
10413         /* Put second chess program into idle state */
10414         if (second.pr != NoProc &&
10415             gameMode == TwoMachinesPlay) {
10416             SendToProgram("force\n", &second);
10417             if (second.usePing) {
10418               char buf[MSG_SIZ];
10419               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10420               SendToProgram(buf, &second);
10421             }
10422         }
10423     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10424         /* Kill off second chess program */
10425         if (second.isr != NULL)
10426           RemoveInputSource(second.isr);
10427         second.isr = NULL;
10428
10429         if (second.pr != NoProc) {
10430             DoSleep( appData.delayBeforeQuit );
10431             SendToProgram("quit\n", &second);
10432             DoSleep( appData.delayAfterQuit );
10433             DestroyChildProcess(second.pr, second.useSigterm);
10434         }
10435         second.pr = NoProc;
10436     }
10437
10438     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10439         char resChar = '=';
10440         switch (result) {
10441         case WhiteWins:
10442           resChar = '+';
10443           if (first.twoMachinesColor[0] == 'w') {
10444             first.matchWins++;
10445           } else {
10446             second.matchWins++;
10447           }
10448           break;
10449         case BlackWins:
10450           resChar = '-';
10451           if (first.twoMachinesColor[0] == 'b') {
10452             first.matchWins++;
10453           } else {
10454             second.matchWins++;
10455           }
10456           break;
10457         case GameUnfinished:
10458           resChar = ' ';
10459         default:
10460           break;
10461         }
10462
10463         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10464         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10465             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10466             ReserveGame(nextGame, resChar); // sets nextGame
10467             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10468             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10469         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10470
10471         if (nextGame <= appData.matchGames && !abortMatch) {
10472             gameMode = nextGameMode;
10473             matchGame = nextGame; // this will be overruled in tourney mode!
10474             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10475             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10476             endingGame = 0; /* [HGM] crash */
10477             return;
10478         } else {
10479             gameMode = nextGameMode;
10480             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10481                      first.tidy, second.tidy,
10482                      first.matchWins, second.matchWins,
10483                      appData.matchGames - (first.matchWins + second.matchWins));
10484             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10485             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10486             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10487             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10488                 first.twoMachinesColor = "black\n";
10489                 second.twoMachinesColor = "white\n";
10490             } else {
10491                 first.twoMachinesColor = "white\n";
10492                 second.twoMachinesColor = "black\n";
10493             }
10494         }
10495     }
10496     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10497         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10498       ExitAnalyzeMode();
10499     gameMode = nextGameMode;
10500     ModeHighlight();
10501     endingGame = 0;  /* [HGM] crash */
10502     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10503         if(matchMode == TRUE) { // match through command line: exit with or without popup
10504             if(ranking) {
10505                 ToNrEvent(forwardMostMove);
10506                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10507                 else ExitEvent(0);
10508             } else DisplayFatalError(buf, 0, 0);
10509         } else { // match through menu; just stop, with or without popup
10510             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10511             ModeHighlight();
10512             if(ranking){
10513                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10514             } else DisplayNote(buf);
10515       }
10516       if(ranking) free(ranking);
10517     }
10518 }
10519
10520 /* Assumes program was just initialized (initString sent).
10521    Leaves program in force mode. */
10522 void
10523 FeedMovesToProgram(cps, upto)
10524      ChessProgramState *cps;
10525      int upto;
10526 {
10527     int i;
10528
10529     if (appData.debugMode)
10530       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10531               startedFromSetupPosition ? "position and " : "",
10532               backwardMostMove, upto, cps->which);
10533     if(currentlyInitializedVariant != gameInfo.variant) {
10534       char buf[MSG_SIZ];
10535         // [HGM] variantswitch: make engine aware of new variant
10536         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10537                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10538         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10539         SendToProgram(buf, cps);
10540         currentlyInitializedVariant = gameInfo.variant;
10541     }
10542     SendToProgram("force\n", cps);
10543     if (startedFromSetupPosition) {
10544         SendBoard(cps, backwardMostMove);
10545     if (appData.debugMode) {
10546         fprintf(debugFP, "feedMoves\n");
10547     }
10548     }
10549     for (i = backwardMostMove; i < upto; i++) {
10550         SendMoveToProgram(i, cps);
10551     }
10552 }
10553
10554
10555 int
10556 ResurrectChessProgram()
10557 {
10558      /* The chess program may have exited.
10559         If so, restart it and feed it all the moves made so far. */
10560     static int doInit = 0;
10561
10562     if (appData.noChessProgram) return 1;
10563
10564     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10565         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10566         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10567         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10568     } else {
10569         if (first.pr != NoProc) return 1;
10570         StartChessProgram(&first);
10571     }
10572     InitChessProgram(&first, FALSE);
10573     FeedMovesToProgram(&first, currentMove);
10574
10575     if (!first.sendTime) {
10576         /* can't tell gnuchess what its clock should read,
10577            so we bow to its notion. */
10578         ResetClocks();
10579         timeRemaining[0][currentMove] = whiteTimeRemaining;
10580         timeRemaining[1][currentMove] = blackTimeRemaining;
10581     }
10582
10583     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10584                 appData.icsEngineAnalyze) && first.analysisSupport) {
10585       SendToProgram("analyze\n", &first);
10586       first.analyzing = TRUE;
10587     }
10588     return 1;
10589 }
10590
10591 /*
10592  * Button procedures
10593  */
10594 void
10595 Reset(redraw, init)
10596      int redraw, init;
10597 {
10598     int i;
10599
10600     if (appData.debugMode) {
10601         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10602                 redraw, init, gameMode);
10603     }
10604     CleanupTail(); // [HGM] vari: delete any stored variations
10605     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10606     pausing = pauseExamInvalid = FALSE;
10607     startedFromSetupPosition = blackPlaysFirst = FALSE;
10608     firstMove = TRUE;
10609     whiteFlag = blackFlag = FALSE;
10610     userOfferedDraw = FALSE;
10611     hintRequested = bookRequested = FALSE;
10612     first.maybeThinking = FALSE;
10613     second.maybeThinking = FALSE;
10614     first.bookSuspend = FALSE; // [HGM] book
10615     second.bookSuspend = FALSE;
10616     thinkOutput[0] = NULLCHAR;
10617     lastHint[0] = NULLCHAR;
10618     ClearGameInfo(&gameInfo);
10619     gameInfo.variant = StringToVariant(appData.variant);
10620     ics_user_moved = ics_clock_paused = FALSE;
10621     ics_getting_history = H_FALSE;
10622     ics_gamenum = -1;
10623     white_holding[0] = black_holding[0] = NULLCHAR;
10624     ClearProgramStats();
10625     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10626
10627     ResetFrontEnd();
10628     ClearHighlights();
10629     flipView = appData.flipView;
10630     ClearPremoveHighlights();
10631     gotPremove = FALSE;
10632     alarmSounded = FALSE;
10633
10634     GameEnds(EndOfFile, NULL, GE_PLAYER);
10635     if(appData.serverMovesName != NULL) {
10636         /* [HGM] prepare to make moves file for broadcasting */
10637         clock_t t = clock();
10638         if(serverMoves != NULL) fclose(serverMoves);
10639         serverMoves = fopen(appData.serverMovesName, "r");
10640         if(serverMoves != NULL) {
10641             fclose(serverMoves);
10642             /* delay 15 sec before overwriting, so all clients can see end */
10643             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10644         }
10645         serverMoves = fopen(appData.serverMovesName, "w");
10646     }
10647
10648     ExitAnalyzeMode();
10649     gameMode = BeginningOfGame;
10650     ModeHighlight();
10651     if(appData.icsActive) gameInfo.variant = VariantNormal;
10652     currentMove = forwardMostMove = backwardMostMove = 0;
10653     InitPosition(redraw);
10654     for (i = 0; i < MAX_MOVES; i++) {
10655         if (commentList[i] != NULL) {
10656             free(commentList[i]);
10657             commentList[i] = NULL;
10658         }
10659     }
10660     ResetClocks();
10661     timeRemaining[0][0] = whiteTimeRemaining;
10662     timeRemaining[1][0] = blackTimeRemaining;
10663
10664     if (first.pr == NoProc) {
10665         StartChessProgram(&first);
10666     }
10667     if (init) {
10668             InitChessProgram(&first, startedFromSetupPosition);
10669     }
10670     DisplayTitle("");
10671     DisplayMessage("", "");
10672     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10673     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10674 }
10675
10676 void
10677 AutoPlayGameLoop()
10678 {
10679     for (;;) {
10680         if (!AutoPlayOneMove())
10681           return;
10682         if (matchMode || appData.timeDelay == 0)
10683           continue;
10684         if (appData.timeDelay < 0)
10685           return;
10686         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10687         break;
10688     }
10689 }
10690
10691
10692 int
10693 AutoPlayOneMove()
10694 {
10695     int fromX, fromY, toX, toY;
10696
10697     if (appData.debugMode) {
10698       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10699     }
10700
10701     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10702       return FALSE;
10703
10704     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10705       pvInfoList[currentMove].depth = programStats.depth;
10706       pvInfoList[currentMove].score = programStats.score;
10707       pvInfoList[currentMove].time  = 0;
10708       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10709     }
10710
10711     if (currentMove >= forwardMostMove) {
10712       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10713 //      gameMode = EndOfGame;
10714 //      ModeHighlight();
10715
10716       /* [AS] Clear current move marker at the end of a game */
10717       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10718
10719       return FALSE;
10720     }
10721
10722     toX = moveList[currentMove][2] - AAA;
10723     toY = moveList[currentMove][3] - ONE;
10724
10725     if (moveList[currentMove][1] == '@') {
10726         if (appData.highlightLastMove) {
10727             SetHighlights(-1, -1, toX, toY);
10728         }
10729     } else {
10730         fromX = moveList[currentMove][0] - AAA;
10731         fromY = moveList[currentMove][1] - ONE;
10732
10733         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10734
10735         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10736
10737         if (appData.highlightLastMove) {
10738             SetHighlights(fromX, fromY, toX, toY);
10739         }
10740     }
10741     DisplayMove(currentMove);
10742     SendMoveToProgram(currentMove++, &first);
10743     DisplayBothClocks();
10744     DrawPosition(FALSE, boards[currentMove]);
10745     // [HGM] PV info: always display, routine tests if empty
10746     DisplayComment(currentMove - 1, commentList[currentMove]);
10747     return TRUE;
10748 }
10749
10750
10751 int
10752 LoadGameOneMove(readAhead)
10753      ChessMove readAhead;
10754 {
10755     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10756     char promoChar = NULLCHAR;
10757     ChessMove moveType;
10758     char move[MSG_SIZ];
10759     char *p, *q;
10760
10761     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10762         gameMode != AnalyzeMode && gameMode != Training) {
10763         gameFileFP = NULL;
10764         return FALSE;
10765     }
10766
10767     yyboardindex = forwardMostMove;
10768     if (readAhead != EndOfFile) {
10769       moveType = readAhead;
10770     } else {
10771       if (gameFileFP == NULL)
10772           return FALSE;
10773       moveType = (ChessMove) Myylex();
10774     }
10775
10776     done = FALSE;
10777     switch (moveType) {
10778       case Comment:
10779         if (appData.debugMode)
10780           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10781         p = yy_text;
10782
10783         /* append the comment but don't display it */
10784         AppendComment(currentMove, p, FALSE);
10785         return TRUE;
10786
10787       case WhiteCapturesEnPassant:
10788       case BlackCapturesEnPassant:
10789       case WhitePromotion:
10790       case BlackPromotion:
10791       case WhiteNonPromotion:
10792       case BlackNonPromotion:
10793       case NormalMove:
10794       case WhiteKingSideCastle:
10795       case WhiteQueenSideCastle:
10796       case BlackKingSideCastle:
10797       case BlackQueenSideCastle:
10798       case WhiteKingSideCastleWild:
10799       case WhiteQueenSideCastleWild:
10800       case BlackKingSideCastleWild:
10801       case BlackQueenSideCastleWild:
10802       /* PUSH Fabien */
10803       case WhiteHSideCastleFR:
10804       case WhiteASideCastleFR:
10805       case BlackHSideCastleFR:
10806       case BlackASideCastleFR:
10807       /* POP Fabien */
10808         if (appData.debugMode)
10809           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10810         fromX = currentMoveString[0] - AAA;
10811         fromY = currentMoveString[1] - ONE;
10812         toX = currentMoveString[2] - AAA;
10813         toY = currentMoveString[3] - ONE;
10814         promoChar = currentMoveString[4];
10815         break;
10816
10817       case WhiteDrop:
10818       case BlackDrop:
10819         if (appData.debugMode)
10820           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10821         fromX = moveType == WhiteDrop ?
10822           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10823         (int) CharToPiece(ToLower(currentMoveString[0]));
10824         fromY = DROP_RANK;
10825         toX = currentMoveString[2] - AAA;
10826         toY = currentMoveString[3] - ONE;
10827         break;
10828
10829       case WhiteWins:
10830       case BlackWins:
10831       case GameIsDrawn:
10832       case GameUnfinished:
10833         if (appData.debugMode)
10834           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10835         p = strchr(yy_text, '{');
10836         if (p == NULL) p = strchr(yy_text, '(');
10837         if (p == NULL) {
10838             p = yy_text;
10839             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10840         } else {
10841             q = strchr(p, *p == '{' ? '}' : ')');
10842             if (q != NULL) *q = NULLCHAR;
10843             p++;
10844         }
10845         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10846         GameEnds(moveType, p, GE_FILE);
10847         done = TRUE;
10848         if (cmailMsgLoaded) {
10849             ClearHighlights();
10850             flipView = WhiteOnMove(currentMove);
10851             if (moveType == GameUnfinished) flipView = !flipView;
10852             if (appData.debugMode)
10853               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10854         }
10855         break;
10856
10857       case EndOfFile:
10858         if (appData.debugMode)
10859           fprintf(debugFP, "Parser hit end of file\n");
10860         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10861           case MT_NONE:
10862           case MT_CHECK:
10863             break;
10864           case MT_CHECKMATE:
10865           case MT_STAINMATE:
10866             if (WhiteOnMove(currentMove)) {
10867                 GameEnds(BlackWins, "Black mates", GE_FILE);
10868             } else {
10869                 GameEnds(WhiteWins, "White mates", GE_FILE);
10870             }
10871             break;
10872           case MT_STALEMATE:
10873             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10874             break;
10875         }
10876         done = TRUE;
10877         break;
10878
10879       case MoveNumberOne:
10880         if (lastLoadGameStart == GNUChessGame) {
10881             /* GNUChessGames have numbers, but they aren't move numbers */
10882             if (appData.debugMode)
10883               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10884                       yy_text, (int) moveType);
10885             return LoadGameOneMove(EndOfFile); /* tail recursion */
10886         }
10887         /* else fall thru */
10888
10889       case XBoardGame:
10890       case GNUChessGame:
10891       case PGNTag:
10892         /* Reached start of next game in file */
10893         if (appData.debugMode)
10894           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10895         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10896           case MT_NONE:
10897           case MT_CHECK:
10898             break;
10899           case MT_CHECKMATE:
10900           case MT_STAINMATE:
10901             if (WhiteOnMove(currentMove)) {
10902                 GameEnds(BlackWins, "Black mates", GE_FILE);
10903             } else {
10904                 GameEnds(WhiteWins, "White mates", GE_FILE);
10905             }
10906             break;
10907           case MT_STALEMATE:
10908             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10909             break;
10910         }
10911         done = TRUE;
10912         break;
10913
10914       case PositionDiagram:     /* should not happen; ignore */
10915       case ElapsedTime:         /* ignore */
10916       case NAG:                 /* ignore */
10917         if (appData.debugMode)
10918           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10919                   yy_text, (int) moveType);
10920         return LoadGameOneMove(EndOfFile); /* tail recursion */
10921
10922       case IllegalMove:
10923         if (appData.testLegality) {
10924             if (appData.debugMode)
10925               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10926             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10927                     (forwardMostMove / 2) + 1,
10928                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10929             DisplayError(move, 0);
10930             done = TRUE;
10931         } else {
10932             if (appData.debugMode)
10933               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10934                       yy_text, currentMoveString);
10935             fromX = currentMoveString[0] - AAA;
10936             fromY = currentMoveString[1] - ONE;
10937             toX = currentMoveString[2] - AAA;
10938             toY = currentMoveString[3] - ONE;
10939             promoChar = currentMoveString[4];
10940         }
10941         break;
10942
10943       case AmbiguousMove:
10944         if (appData.debugMode)
10945           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10946         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10947                 (forwardMostMove / 2) + 1,
10948                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10949         DisplayError(move, 0);
10950         done = TRUE;
10951         break;
10952
10953       default:
10954       case ImpossibleMove:
10955         if (appData.debugMode)
10956           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10957         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10958                 (forwardMostMove / 2) + 1,
10959                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10960         DisplayError(move, 0);
10961         done = TRUE;
10962         break;
10963     }
10964
10965     if (done) {
10966         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10967             DrawPosition(FALSE, boards[currentMove]);
10968             DisplayBothClocks();
10969             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10970               DisplayComment(currentMove - 1, commentList[currentMove]);
10971         }
10972         (void) StopLoadGameTimer();
10973         gameFileFP = NULL;
10974         cmailOldMove = forwardMostMove;
10975         return FALSE;
10976     } else {
10977         /* currentMoveString is set as a side-effect of yylex */
10978
10979         thinkOutput[0] = NULLCHAR;
10980         MakeMove(fromX, fromY, toX, toY, promoChar);
10981         currentMove = forwardMostMove;
10982         return TRUE;
10983     }
10984 }
10985
10986 /* Load the nth game from the given file */
10987 int
10988 LoadGameFromFile(filename, n, title, useList)
10989      char *filename;
10990      int n;
10991      char *title;
10992      /*Boolean*/ int useList;
10993 {
10994     FILE *f;
10995     char buf[MSG_SIZ];
10996
10997     if (strcmp(filename, "-") == 0) {
10998         f = stdin;
10999         title = "stdin";
11000     } else {
11001         f = fopen(filename, "rb");
11002         if (f == NULL) {
11003           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11004             DisplayError(buf, errno);
11005             return FALSE;
11006         }
11007     }
11008     if (fseek(f, 0, 0) == -1) {
11009         /* f is not seekable; probably a pipe */
11010         useList = FALSE;
11011     }
11012     if (useList && n == 0) {
11013         int error = GameListBuild(f);
11014         if (error) {
11015             DisplayError(_("Cannot build game list"), error);
11016         } else if (!ListEmpty(&gameList) &&
11017                    ((ListGame *) gameList.tailPred)->number > 1) {
11018             GameListPopUp(f, title);
11019             return TRUE;
11020         }
11021         GameListDestroy();
11022         n = 1;
11023     }
11024     if (n == 0) n = 1;
11025     return LoadGame(f, n, title, FALSE);
11026 }
11027
11028
11029 void
11030 MakeRegisteredMove()
11031 {
11032     int fromX, fromY, toX, toY;
11033     char promoChar;
11034     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11035         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11036           case CMAIL_MOVE:
11037           case CMAIL_DRAW:
11038             if (appData.debugMode)
11039               fprintf(debugFP, "Restoring %s for game %d\n",
11040                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11041
11042             thinkOutput[0] = NULLCHAR;
11043             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11044             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11045             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11046             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11047             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11048             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11049             MakeMove(fromX, fromY, toX, toY, promoChar);
11050             ShowMove(fromX, fromY, toX, toY);
11051
11052             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11053               case MT_NONE:
11054               case MT_CHECK:
11055                 break;
11056
11057               case MT_CHECKMATE:
11058               case MT_STAINMATE:
11059                 if (WhiteOnMove(currentMove)) {
11060                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11061                 } else {
11062                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11063                 }
11064                 break;
11065
11066               case MT_STALEMATE:
11067                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11068                 break;
11069             }
11070
11071             break;
11072
11073           case CMAIL_RESIGN:
11074             if (WhiteOnMove(currentMove)) {
11075                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11076             } else {
11077                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11078             }
11079             break;
11080
11081           case CMAIL_ACCEPT:
11082             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11083             break;
11084
11085           default:
11086             break;
11087         }
11088     }
11089
11090     return;
11091 }
11092
11093 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11094 int
11095 CmailLoadGame(f, gameNumber, title, useList)
11096      FILE *f;
11097      int gameNumber;
11098      char *title;
11099      int useList;
11100 {
11101     int retVal;
11102
11103     if (gameNumber > nCmailGames) {
11104         DisplayError(_("No more games in this message"), 0);
11105         return FALSE;
11106     }
11107     if (f == lastLoadGameFP) {
11108         int offset = gameNumber - lastLoadGameNumber;
11109         if (offset == 0) {
11110             cmailMsg[0] = NULLCHAR;
11111             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11112                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11113                 nCmailMovesRegistered--;
11114             }
11115             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11116             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11117                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11118             }
11119         } else {
11120             if (! RegisterMove()) return FALSE;
11121         }
11122     }
11123
11124     retVal = LoadGame(f, gameNumber, title, useList);
11125
11126     /* Make move registered during previous look at this game, if any */
11127     MakeRegisteredMove();
11128
11129     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11130         commentList[currentMove]
11131           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11132         DisplayComment(currentMove - 1, commentList[currentMove]);
11133     }
11134
11135     return retVal;
11136 }
11137
11138 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11139 int
11140 ReloadGame(offset)
11141      int offset;
11142 {
11143     int gameNumber = lastLoadGameNumber + offset;
11144     if (lastLoadGameFP == NULL) {
11145         DisplayError(_("No game has been loaded yet"), 0);
11146         return FALSE;
11147     }
11148     if (gameNumber <= 0) {
11149         DisplayError(_("Can't back up any further"), 0);
11150         return FALSE;
11151     }
11152     if (cmailMsgLoaded) {
11153         return CmailLoadGame(lastLoadGameFP, gameNumber,
11154                              lastLoadGameTitle, lastLoadGameUseList);
11155     } else {
11156         return LoadGame(lastLoadGameFP, gameNumber,
11157                         lastLoadGameTitle, lastLoadGameUseList);
11158     }
11159 }
11160
11161 int keys[EmptySquare+1];
11162
11163 int
11164 PositionMatches(Board b1, Board b2)
11165 {
11166     int r, f, sum=0;
11167     switch(appData.searchMode) {
11168         case 1: return CompareWithRights(b1, b2);
11169         case 2:
11170             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11171                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11172             }
11173             return TRUE;
11174         case 3:
11175             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11176               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11177                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11178             }
11179             return sum==0;
11180         case 4:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11183             }
11184             return sum==0;
11185     }
11186     return TRUE;
11187 }
11188
11189 #define Q_PROMO  4
11190 #define Q_EP     3
11191 #define Q_BCASTL 2
11192 #define Q_WCASTL 1
11193
11194 int pieceList[256], quickBoard[256];
11195 ChessSquare pieceType[256] = { EmptySquare };
11196 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11197 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11198 int soughtTotal, turn;
11199 Boolean epOK, flipSearch;
11200
11201 typedef struct {
11202     unsigned char piece, to;
11203 } Move;
11204
11205 #define DSIZE (250000)
11206
11207 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11208 Move *moveDatabase = initialSpace;
11209 unsigned int movePtr, dataSize = DSIZE;
11210
11211 int MakePieceList(Board board, int *counts)
11212 {
11213     int r, f, n=Q_PROMO, total=0;
11214     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11215     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11216         int sq = f + (r<<4);
11217         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11218             quickBoard[sq] = ++n;
11219             pieceList[n] = sq;
11220             pieceType[n] = board[r][f];
11221             counts[board[r][f]]++;
11222             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11223             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11224             total++;
11225         }
11226     }
11227     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11228     return total;
11229 }
11230
11231 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11232 {
11233     int sq = fromX + (fromY<<4);
11234     int piece = quickBoard[sq];
11235     quickBoard[sq] = 0;
11236     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11237     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11238         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11239         moveDatabase[movePtr++].piece = Q_WCASTL;
11240         quickBoard[sq] = piece;
11241         piece = quickBoard[from]; quickBoard[from] = 0;
11242         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11243     } else
11244     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11245         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11246         moveDatabase[movePtr++].piece = Q_BCASTL;
11247         quickBoard[sq] = piece;
11248         piece = quickBoard[from]; quickBoard[from] = 0;
11249         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11250     } else
11251     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11252         quickBoard[(fromY<<4)+toX] = 0;
11253         moveDatabase[movePtr].piece = Q_EP;
11254         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11255         moveDatabase[movePtr].to = sq;
11256     } else
11257     if(promoPiece != pieceType[piece]) {
11258         moveDatabase[movePtr++].piece = Q_PROMO;
11259         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11260     }
11261     moveDatabase[movePtr].piece = piece;
11262     quickBoard[sq] = piece;
11263     movePtr++;
11264 }
11265
11266 int PackGame(Board board)
11267 {
11268     Move *newSpace = NULL;
11269     moveDatabase[movePtr].piece = 0; // terminate previous game
11270     if(movePtr > dataSize) {
11271         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11272         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11273         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11274         if(newSpace) {
11275             int i;
11276             Move *p = moveDatabase, *q = newSpace;
11277             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11278             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11279             moveDatabase = newSpace;
11280         } else { // calloc failed, we must be out of memory. Too bad...
11281             dataSize = 0; // prevent calloc events for all subsequent games
11282             return 0;     // and signal this one isn't cached
11283         }
11284     }
11285     movePtr++;
11286     MakePieceList(board, counts);
11287     return movePtr;
11288 }
11289
11290 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11291 {   // compare according to search mode
11292     int r, f;
11293     switch(appData.searchMode)
11294     {
11295       case 1: // exact position match
11296         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11297         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11298             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11299         }
11300         break;
11301       case 2: // can have extra material on empty squares
11302         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11303             if(board[r][f] == EmptySquare) continue;
11304             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11305         }
11306         break;
11307       case 3: // material with exact Pawn structure
11308         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11309             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11310             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11311         } // fall through to material comparison
11312       case 4: // exact material
11313         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11314         break;
11315       case 6: // material range with given imbalance
11316         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11317         // fall through to range comparison
11318       case 5: // material range
11319         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11320     }
11321     return TRUE;
11322 }
11323
11324 int QuickScan(Board board, Move *move)
11325 {   // reconstruct game,and compare all positions in it
11326     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11327     do {
11328         int piece = move->piece;
11329         int to = move->to, from = pieceList[piece];
11330         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11331           if(!piece) return -1;
11332           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11333             piece = (++move)->piece;
11334             from = pieceList[piece];
11335             counts[pieceType[piece]]--;
11336             pieceType[piece] = (ChessSquare) move->to;
11337             counts[move->to]++;
11338           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11339             counts[pieceType[quickBoard[to]]]--;
11340             quickBoard[to] = 0; total--;
11341             move++;
11342             continue;
11343           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11344             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11345             from  = pieceList[piece]; // so this must be King
11346             quickBoard[from] = 0;
11347             quickBoard[to] = piece;
11348             pieceList[piece] = to;
11349             move++;
11350             continue;
11351           }
11352         }
11353         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11354         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11355         quickBoard[from] = 0;
11356         quickBoard[to] = piece;
11357         pieceList[piece] = to;
11358         cnt++; turn ^= 3;
11359         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11360            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11361            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11362                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11363           ) {
11364             static int lastCounts[EmptySquare+1];
11365             int i;
11366             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11367             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11368         } else stretch = 0;
11369         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11370         move++;
11371     } while(1);
11372 }
11373
11374 void InitSearch()
11375 {
11376     int r, f;
11377     flipSearch = FALSE;
11378     CopyBoard(soughtBoard, boards[currentMove]);
11379     soughtTotal = MakePieceList(soughtBoard, maxSought);
11380     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11381     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11382     CopyBoard(reverseBoard, boards[currentMove]);
11383     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11384         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11385         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11386         reverseBoard[r][f] = piece;
11387     }
11388     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11389     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11390     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11391                  || (boards[currentMove][CASTLING][2] == NoRights || 
11392                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11393                  && (boards[currentMove][CASTLING][5] == NoRights || 
11394                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11395       ) {
11396         flipSearch = TRUE;
11397         CopyBoard(flipBoard, soughtBoard);
11398         CopyBoard(rotateBoard, reverseBoard);
11399         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11400             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11401             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11402         }
11403     }
11404     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11405     if(appData.searchMode >= 5) {
11406         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11407         MakePieceList(soughtBoard, minSought);
11408         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11409     }
11410     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11411         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11412 }
11413
11414 GameInfo dummyInfo;
11415
11416 int GameContainsPosition(FILE *f, ListGame *lg)
11417 {
11418     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11419     int fromX, fromY, toX, toY;
11420     char promoChar;
11421     static int initDone=FALSE;
11422
11423     // weed out games based on numerical tag comparison
11424     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11425     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11426     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11427     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11428     if(!initDone) {
11429         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11430         initDone = TRUE;
11431     }
11432     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11433     else CopyBoard(boards[scratch], initialPosition); // default start position
11434     if(lg->moves) {
11435         turn = btm + 1;
11436         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11437         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11438     }
11439     if(btm) plyNr++;
11440     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11441     fseek(f, lg->offset, 0);
11442     yynewfile(f);
11443     while(1) {
11444         yyboardindex = scratch;
11445         quickFlag = plyNr+1;
11446         next = Myylex();
11447         quickFlag = 0;
11448         switch(next) {
11449             case PGNTag:
11450                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11451             default:
11452                 continue;
11453
11454             case XBoardGame:
11455             case GNUChessGame:
11456                 if(plyNr) return -1; // after we have seen moves, this is for new game
11457               continue;
11458
11459             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11460             case ImpossibleMove:
11461             case WhiteWins: // game ends here with these four
11462             case BlackWins:
11463             case GameIsDrawn:
11464             case GameUnfinished:
11465                 return -1;
11466
11467             case IllegalMove:
11468                 if(appData.testLegality) return -1;
11469             case WhiteCapturesEnPassant:
11470             case BlackCapturesEnPassant:
11471             case WhitePromotion:
11472             case BlackPromotion:
11473             case WhiteNonPromotion:
11474             case BlackNonPromotion:
11475             case NormalMove:
11476             case WhiteKingSideCastle:
11477             case WhiteQueenSideCastle:
11478             case BlackKingSideCastle:
11479             case BlackQueenSideCastle:
11480             case WhiteKingSideCastleWild:
11481             case WhiteQueenSideCastleWild:
11482             case BlackKingSideCastleWild:
11483             case BlackQueenSideCastleWild:
11484             case WhiteHSideCastleFR:
11485             case WhiteASideCastleFR:
11486             case BlackHSideCastleFR:
11487             case BlackASideCastleFR:
11488                 fromX = currentMoveString[0] - AAA;
11489                 fromY = currentMoveString[1] - ONE;
11490                 toX = currentMoveString[2] - AAA;
11491                 toY = currentMoveString[3] - ONE;
11492                 promoChar = currentMoveString[4];
11493                 break;
11494             case WhiteDrop:
11495             case BlackDrop:
11496                 fromX = next == WhiteDrop ?
11497                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11498                   (int) CharToPiece(ToLower(currentMoveString[0]));
11499                 fromY = DROP_RANK;
11500                 toX = currentMoveString[2] - AAA;
11501                 toY = currentMoveString[3] - ONE;
11502                 promoChar = 0;
11503                 break;
11504         }
11505         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11506         plyNr++;
11507         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11508         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11509         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11510         if(appData.findMirror) {
11511             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11512             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11513         }
11514     }
11515 }
11516
11517 /* Load the nth game from open file f */
11518 int
11519 LoadGame(f, gameNumber, title, useList)
11520      FILE *f;
11521      int gameNumber;
11522      char *title;
11523      int useList;
11524 {
11525     ChessMove cm;
11526     char buf[MSG_SIZ];
11527     int gn = gameNumber;
11528     ListGame *lg = NULL;
11529     int numPGNTags = 0;
11530     int err, pos = -1;
11531     GameMode oldGameMode;
11532     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11533
11534     if (appData.debugMode)
11535         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11536
11537     if (gameMode == Training )
11538         SetTrainingModeOff();
11539
11540     oldGameMode = gameMode;
11541     if (gameMode != BeginningOfGame) {
11542       Reset(FALSE, TRUE);
11543     }
11544
11545     gameFileFP = f;
11546     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11547         fclose(lastLoadGameFP);
11548     }
11549
11550     if (useList) {
11551         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11552
11553         if (lg) {
11554             fseek(f, lg->offset, 0);
11555             GameListHighlight(gameNumber);
11556             pos = lg->position;
11557             gn = 1;
11558         }
11559         else {
11560             DisplayError(_("Game number out of range"), 0);
11561             return FALSE;
11562         }
11563     } else {
11564         GameListDestroy();
11565         if (fseek(f, 0, 0) == -1) {
11566             if (f == lastLoadGameFP ?
11567                 gameNumber == lastLoadGameNumber + 1 :
11568                 gameNumber == 1) {
11569                 gn = 1;
11570             } else {
11571                 DisplayError(_("Can't seek on game file"), 0);
11572                 return FALSE;
11573             }
11574         }
11575     }
11576     lastLoadGameFP = f;
11577     lastLoadGameNumber = gameNumber;
11578     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11579     lastLoadGameUseList = useList;
11580
11581     yynewfile(f);
11582
11583     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11584       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11585                 lg->gameInfo.black);
11586             DisplayTitle(buf);
11587     } else if (*title != NULLCHAR) {
11588         if (gameNumber > 1) {
11589           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11590             DisplayTitle(buf);
11591         } else {
11592             DisplayTitle(title);
11593         }
11594     }
11595
11596     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11597         gameMode = PlayFromGameFile;
11598         ModeHighlight();
11599     }
11600
11601     currentMove = forwardMostMove = backwardMostMove = 0;
11602     CopyBoard(boards[0], initialPosition);
11603     StopClocks();
11604
11605     /*
11606      * Skip the first gn-1 games in the file.
11607      * Also skip over anything that precedes an identifiable
11608      * start of game marker, to avoid being confused by
11609      * garbage at the start of the file.  Currently
11610      * recognized start of game markers are the move number "1",
11611      * the pattern "gnuchess .* game", the pattern
11612      * "^[#;%] [^ ]* game file", and a PGN tag block.
11613      * A game that starts with one of the latter two patterns
11614      * will also have a move number 1, possibly
11615      * following a position diagram.
11616      * 5-4-02: Let's try being more lenient and allowing a game to
11617      * start with an unnumbered move.  Does that break anything?
11618      */
11619     cm = lastLoadGameStart = EndOfFile;
11620     while (gn > 0) {
11621         yyboardindex = forwardMostMove;
11622         cm = (ChessMove) Myylex();
11623         switch (cm) {
11624           case EndOfFile:
11625             if (cmailMsgLoaded) {
11626                 nCmailGames = CMAIL_MAX_GAMES - gn;
11627             } else {
11628                 Reset(TRUE, TRUE);
11629                 DisplayError(_("Game not found in file"), 0);
11630             }
11631             return FALSE;
11632
11633           case GNUChessGame:
11634           case XBoardGame:
11635             gn--;
11636             lastLoadGameStart = cm;
11637             break;
11638
11639           case MoveNumberOne:
11640             switch (lastLoadGameStart) {
11641               case GNUChessGame:
11642               case XBoardGame:
11643               case PGNTag:
11644                 break;
11645               case MoveNumberOne:
11646               case EndOfFile:
11647                 gn--;           /* count this game */
11648                 lastLoadGameStart = cm;
11649                 break;
11650               default:
11651                 /* impossible */
11652                 break;
11653             }
11654             break;
11655
11656           case PGNTag:
11657             switch (lastLoadGameStart) {
11658               case GNUChessGame:
11659               case PGNTag:
11660               case MoveNumberOne:
11661               case EndOfFile:
11662                 gn--;           /* count this game */
11663                 lastLoadGameStart = cm;
11664                 break;
11665               case XBoardGame:
11666                 lastLoadGameStart = cm; /* game counted already */
11667                 break;
11668               default:
11669                 /* impossible */
11670                 break;
11671             }
11672             if (gn > 0) {
11673                 do {
11674                     yyboardindex = forwardMostMove;
11675                     cm = (ChessMove) Myylex();
11676                 } while (cm == PGNTag || cm == Comment);
11677             }
11678             break;
11679
11680           case WhiteWins:
11681           case BlackWins:
11682           case GameIsDrawn:
11683             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11684                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11685                     != CMAIL_OLD_RESULT) {
11686                     nCmailResults ++ ;
11687                     cmailResult[  CMAIL_MAX_GAMES
11688                                 - gn - 1] = CMAIL_OLD_RESULT;
11689                 }
11690             }
11691             break;
11692
11693           case NormalMove:
11694             /* Only a NormalMove can be at the start of a game
11695              * without a position diagram. */
11696             if (lastLoadGameStart == EndOfFile ) {
11697               gn--;
11698               lastLoadGameStart = MoveNumberOne;
11699             }
11700             break;
11701
11702           default:
11703             break;
11704         }
11705     }
11706
11707     if (appData.debugMode)
11708       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11709
11710     if (cm == XBoardGame) {
11711         /* Skip any header junk before position diagram and/or move 1 */
11712         for (;;) {
11713             yyboardindex = forwardMostMove;
11714             cm = (ChessMove) Myylex();
11715
11716             if (cm == EndOfFile ||
11717                 cm == GNUChessGame || cm == XBoardGame) {
11718                 /* Empty game; pretend end-of-file and handle later */
11719                 cm = EndOfFile;
11720                 break;
11721             }
11722
11723             if (cm == MoveNumberOne || cm == PositionDiagram ||
11724                 cm == PGNTag || cm == Comment)
11725               break;
11726         }
11727     } else if (cm == GNUChessGame) {
11728         if (gameInfo.event != NULL) {
11729             free(gameInfo.event);
11730         }
11731         gameInfo.event = StrSave(yy_text);
11732     }
11733
11734     startedFromSetupPosition = FALSE;
11735     while (cm == PGNTag) {
11736         if (appData.debugMode)
11737           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11738         err = ParsePGNTag(yy_text, &gameInfo);
11739         if (!err) numPGNTags++;
11740
11741         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11742         if(gameInfo.variant != oldVariant) {
11743             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11744             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11745             InitPosition(TRUE);
11746             oldVariant = gameInfo.variant;
11747             if (appData.debugMode)
11748               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11749         }
11750
11751
11752         if (gameInfo.fen != NULL) {
11753           Board initial_position;
11754           startedFromSetupPosition = TRUE;
11755           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11756             Reset(TRUE, TRUE);
11757             DisplayError(_("Bad FEN position in file"), 0);
11758             return FALSE;
11759           }
11760           CopyBoard(boards[0], initial_position);
11761           if (blackPlaysFirst) {
11762             currentMove = forwardMostMove = backwardMostMove = 1;
11763             CopyBoard(boards[1], initial_position);
11764             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11765             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11766             timeRemaining[0][1] = whiteTimeRemaining;
11767             timeRemaining[1][1] = blackTimeRemaining;
11768             if (commentList[0] != NULL) {
11769               commentList[1] = commentList[0];
11770               commentList[0] = NULL;
11771             }
11772           } else {
11773             currentMove = forwardMostMove = backwardMostMove = 0;
11774           }
11775           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11776           {   int i;
11777               initialRulePlies = FENrulePlies;
11778               for( i=0; i< nrCastlingRights; i++ )
11779                   initialRights[i] = initial_position[CASTLING][i];
11780           }
11781           yyboardindex = forwardMostMove;
11782           free(gameInfo.fen);
11783           gameInfo.fen = NULL;
11784         }
11785
11786         yyboardindex = forwardMostMove;
11787         cm = (ChessMove) Myylex();
11788
11789         /* Handle comments interspersed among the tags */
11790         while (cm == Comment) {
11791             char *p;
11792             if (appData.debugMode)
11793               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11794             p = yy_text;
11795             AppendComment(currentMove, p, FALSE);
11796             yyboardindex = forwardMostMove;
11797             cm = (ChessMove) Myylex();
11798         }
11799     }
11800
11801     /* don't rely on existence of Event tag since if game was
11802      * pasted from clipboard the Event tag may not exist
11803      */
11804     if (numPGNTags > 0){
11805         char *tags;
11806         if (gameInfo.variant == VariantNormal) {
11807           VariantClass v = StringToVariant(gameInfo.event);
11808           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11809           if(v < VariantShogi) gameInfo.variant = v;
11810         }
11811         if (!matchMode) {
11812           if( appData.autoDisplayTags ) {
11813             tags = PGNTags(&gameInfo);
11814             TagsPopUp(tags, CmailMsg());
11815             free(tags);
11816           }
11817         }
11818     } else {
11819         /* Make something up, but don't display it now */
11820         SetGameInfo();
11821         TagsPopDown();
11822     }
11823
11824     if (cm == PositionDiagram) {
11825         int i, j;
11826         char *p;
11827         Board initial_position;
11828
11829         if (appData.debugMode)
11830           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11831
11832         if (!startedFromSetupPosition) {
11833             p = yy_text;
11834             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11835               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11836                 switch (*p) {
11837                   case '{':
11838                   case '[':
11839                   case '-':
11840                   case ' ':
11841                   case '\t':
11842                   case '\n':
11843                   case '\r':
11844                     break;
11845                   default:
11846                     initial_position[i][j++] = CharToPiece(*p);
11847                     break;
11848                 }
11849             while (*p == ' ' || *p == '\t' ||
11850                    *p == '\n' || *p == '\r') p++;
11851
11852             if (strncmp(p, "black", strlen("black"))==0)
11853               blackPlaysFirst = TRUE;
11854             else
11855               blackPlaysFirst = FALSE;
11856             startedFromSetupPosition = TRUE;
11857
11858             CopyBoard(boards[0], initial_position);
11859             if (blackPlaysFirst) {
11860                 currentMove = forwardMostMove = backwardMostMove = 1;
11861                 CopyBoard(boards[1], initial_position);
11862                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11863                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11864                 timeRemaining[0][1] = whiteTimeRemaining;
11865                 timeRemaining[1][1] = blackTimeRemaining;
11866                 if (commentList[0] != NULL) {
11867                     commentList[1] = commentList[0];
11868                     commentList[0] = NULL;
11869                 }
11870             } else {
11871                 currentMove = forwardMostMove = backwardMostMove = 0;
11872             }
11873         }
11874         yyboardindex = forwardMostMove;
11875         cm = (ChessMove) Myylex();
11876     }
11877
11878     if (first.pr == NoProc) {
11879         StartChessProgram(&first);
11880     }
11881     InitChessProgram(&first, FALSE);
11882     SendToProgram("force\n", &first);
11883     if (startedFromSetupPosition) {
11884         SendBoard(&first, forwardMostMove);
11885     if (appData.debugMode) {
11886         fprintf(debugFP, "Load Game\n");
11887     }
11888         DisplayBothClocks();
11889     }
11890
11891     /* [HGM] server: flag to write setup moves in broadcast file as one */
11892     loadFlag = appData.suppressLoadMoves;
11893
11894     while (cm == Comment) {
11895         char *p;
11896         if (appData.debugMode)
11897           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11898         p = yy_text;
11899         AppendComment(currentMove, p, FALSE);
11900         yyboardindex = forwardMostMove;
11901         cm = (ChessMove) Myylex();
11902     }
11903
11904     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11905         cm == WhiteWins || cm == BlackWins ||
11906         cm == GameIsDrawn || cm == GameUnfinished) {
11907         DisplayMessage("", _("No moves in game"));
11908         if (cmailMsgLoaded) {
11909             if (appData.debugMode)
11910               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11911             ClearHighlights();
11912             flipView = FALSE;
11913         }
11914         DrawPosition(FALSE, boards[currentMove]);
11915         DisplayBothClocks();
11916         gameMode = EditGame;
11917         ModeHighlight();
11918         gameFileFP = NULL;
11919         cmailOldMove = 0;
11920         return TRUE;
11921     }
11922
11923     // [HGM] PV info: routine tests if comment empty
11924     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11925         DisplayComment(currentMove - 1, commentList[currentMove]);
11926     }
11927     if (!matchMode && appData.timeDelay != 0)
11928       DrawPosition(FALSE, boards[currentMove]);
11929
11930     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11931       programStats.ok_to_send = 1;
11932     }
11933
11934     /* if the first token after the PGN tags is a move
11935      * and not move number 1, retrieve it from the parser
11936      */
11937     if (cm != MoveNumberOne)
11938         LoadGameOneMove(cm);
11939
11940     /* load the remaining moves from the file */
11941     while (LoadGameOneMove(EndOfFile)) {
11942       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11943       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11944     }
11945
11946     /* rewind to the start of the game */
11947     currentMove = backwardMostMove;
11948
11949     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11950
11951     if (oldGameMode == AnalyzeFile ||
11952         oldGameMode == AnalyzeMode) {
11953       AnalyzeFileEvent();
11954     }
11955
11956     if (!matchMode && pos >= 0) {
11957         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11958     } else
11959     if (matchMode || appData.timeDelay == 0) {
11960       ToEndEvent();
11961     } else if (appData.timeDelay > 0) {
11962       AutoPlayGameLoop();
11963     }
11964
11965     if (appData.debugMode)
11966         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11967
11968     loadFlag = 0; /* [HGM] true game starts */
11969     return TRUE;
11970 }
11971
11972 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11973 int
11974 ReloadPosition(offset)
11975      int offset;
11976 {
11977     int positionNumber = lastLoadPositionNumber + offset;
11978     if (lastLoadPositionFP == NULL) {
11979         DisplayError(_("No position has been loaded yet"), 0);
11980         return FALSE;
11981     }
11982     if (positionNumber <= 0) {
11983         DisplayError(_("Can't back up any further"), 0);
11984         return FALSE;
11985     }
11986     return LoadPosition(lastLoadPositionFP, positionNumber,
11987                         lastLoadPositionTitle);
11988 }
11989
11990 /* Load the nth position from the given file */
11991 int
11992 LoadPositionFromFile(filename, n, title)
11993      char *filename;
11994      int n;
11995      char *title;
11996 {
11997     FILE *f;
11998     char buf[MSG_SIZ];
11999
12000     if (strcmp(filename, "-") == 0) {
12001         return LoadPosition(stdin, n, "stdin");
12002     } else {
12003         f = fopen(filename, "rb");
12004         if (f == NULL) {
12005             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12006             DisplayError(buf, errno);
12007             return FALSE;
12008         } else {
12009             return LoadPosition(f, n, title);
12010         }
12011     }
12012 }
12013
12014 /* Load the nth position from the given open file, and close it */
12015 int
12016 LoadPosition(f, positionNumber, title)
12017      FILE *f;
12018      int positionNumber;
12019      char *title;
12020 {
12021     char *p, line[MSG_SIZ];
12022     Board initial_position;
12023     int i, j, fenMode, pn;
12024
12025     if (gameMode == Training )
12026         SetTrainingModeOff();
12027
12028     if (gameMode != BeginningOfGame) {
12029         Reset(FALSE, TRUE);
12030     }
12031     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12032         fclose(lastLoadPositionFP);
12033     }
12034     if (positionNumber == 0) positionNumber = 1;
12035     lastLoadPositionFP = f;
12036     lastLoadPositionNumber = positionNumber;
12037     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12038     if (first.pr == NoProc && !appData.noChessProgram) {
12039       StartChessProgram(&first);
12040       InitChessProgram(&first, FALSE);
12041     }
12042     pn = positionNumber;
12043     if (positionNumber < 0) {
12044         /* Negative position number means to seek to that byte offset */
12045         if (fseek(f, -positionNumber, 0) == -1) {
12046             DisplayError(_("Can't seek on position file"), 0);
12047             return FALSE;
12048         };
12049         pn = 1;
12050     } else {
12051         if (fseek(f, 0, 0) == -1) {
12052             if (f == lastLoadPositionFP ?
12053                 positionNumber == lastLoadPositionNumber + 1 :
12054                 positionNumber == 1) {
12055                 pn = 1;
12056             } else {
12057                 DisplayError(_("Can't seek on position file"), 0);
12058                 return FALSE;
12059             }
12060         }
12061     }
12062     /* See if this file is FEN or old-style xboard */
12063     if (fgets(line, MSG_SIZ, f) == NULL) {
12064         DisplayError(_("Position not found in file"), 0);
12065         return FALSE;
12066     }
12067     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12068     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12069
12070     if (pn >= 2) {
12071         if (fenMode || line[0] == '#') pn--;
12072         while (pn > 0) {
12073             /* skip positions before number pn */
12074             if (fgets(line, MSG_SIZ, f) == NULL) {
12075                 Reset(TRUE, TRUE);
12076                 DisplayError(_("Position not found in file"), 0);
12077                 return FALSE;
12078             }
12079             if (fenMode || line[0] == '#') pn--;
12080         }
12081     }
12082
12083     if (fenMode) {
12084         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12085             DisplayError(_("Bad FEN position in file"), 0);
12086             return FALSE;
12087         }
12088     } else {
12089         (void) fgets(line, MSG_SIZ, f);
12090         (void) fgets(line, MSG_SIZ, f);
12091
12092         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12093             (void) fgets(line, MSG_SIZ, f);
12094             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12095                 if (*p == ' ')
12096                   continue;
12097                 initial_position[i][j++] = CharToPiece(*p);
12098             }
12099         }
12100
12101         blackPlaysFirst = FALSE;
12102         if (!feof(f)) {
12103             (void) fgets(line, MSG_SIZ, f);
12104             if (strncmp(line, "black", strlen("black"))==0)
12105               blackPlaysFirst = TRUE;
12106         }
12107     }
12108     startedFromSetupPosition = TRUE;
12109
12110     CopyBoard(boards[0], initial_position);
12111     if (blackPlaysFirst) {
12112         currentMove = forwardMostMove = backwardMostMove = 1;
12113         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12114         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12115         CopyBoard(boards[1], initial_position);
12116         DisplayMessage("", _("Black to play"));
12117     } else {
12118         currentMove = forwardMostMove = backwardMostMove = 0;
12119         DisplayMessage("", _("White to play"));
12120     }
12121     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12122     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12123         SendToProgram("force\n", &first);
12124         SendBoard(&first, forwardMostMove);
12125     }
12126     if (appData.debugMode) {
12127 int i, j;
12128   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12129   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12130         fprintf(debugFP, "Load Position\n");
12131     }
12132
12133     if (positionNumber > 1) {
12134       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12135         DisplayTitle(line);
12136     } else {
12137         DisplayTitle(title);
12138     }
12139     gameMode = EditGame;
12140     ModeHighlight();
12141     ResetClocks();
12142     timeRemaining[0][1] = whiteTimeRemaining;
12143     timeRemaining[1][1] = blackTimeRemaining;
12144     DrawPosition(FALSE, boards[currentMove]);
12145
12146     return TRUE;
12147 }
12148
12149
12150 void
12151 CopyPlayerNameIntoFileName(dest, src)
12152      char **dest, *src;
12153 {
12154     while (*src != NULLCHAR && *src != ',') {
12155         if (*src == ' ') {
12156             *(*dest)++ = '_';
12157             src++;
12158         } else {
12159             *(*dest)++ = *src++;
12160         }
12161     }
12162 }
12163
12164 char *DefaultFileName(ext)
12165      char *ext;
12166 {
12167     static char def[MSG_SIZ];
12168     char *p;
12169
12170     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12171         p = def;
12172         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12173         *p++ = '-';
12174         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12175         *p++ = '.';
12176         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12177     } else {
12178         def[0] = NULLCHAR;
12179     }
12180     return def;
12181 }
12182
12183 /* Save the current game to the given file */
12184 int
12185 SaveGameToFile(filename, append)
12186      char *filename;
12187      int append;
12188 {
12189     FILE *f;
12190     char buf[MSG_SIZ];
12191     int result, i, t,tot=0;
12192
12193     if (strcmp(filename, "-") == 0) {
12194         return SaveGame(stdout, 0, NULL);
12195     } else {
12196         for(i=0; i<10; i++) { // upto 10 tries
12197              f = fopen(filename, append ? "a" : "w");
12198              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12199              if(f || errno != 13) break;
12200              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12201              tot += t;
12202         }
12203         if (f == NULL) {
12204             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12205             DisplayError(buf, errno);
12206             return FALSE;
12207         } else {
12208             safeStrCpy(buf, lastMsg, MSG_SIZ);
12209             DisplayMessage(_("Waiting for access to save file"), "");
12210             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12211             DisplayMessage(_("Saving game"), "");
12212             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12213             result = SaveGame(f, 0, NULL);
12214             DisplayMessage(buf, "");
12215             return result;
12216         }
12217     }
12218 }
12219
12220 char *
12221 SavePart(str)
12222      char *str;
12223 {
12224     static char buf[MSG_SIZ];
12225     char *p;
12226
12227     p = strchr(str, ' ');
12228     if (p == NULL) return str;
12229     strncpy(buf, str, p - str);
12230     buf[p - str] = NULLCHAR;
12231     return buf;
12232 }
12233
12234 #define PGN_MAX_LINE 75
12235
12236 #define PGN_SIDE_WHITE  0
12237 #define PGN_SIDE_BLACK  1
12238
12239 /* [AS] */
12240 static int FindFirstMoveOutOfBook( int side )
12241 {
12242     int result = -1;
12243
12244     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12245         int index = backwardMostMove;
12246         int has_book_hit = 0;
12247
12248         if( (index % 2) != side ) {
12249             index++;
12250         }
12251
12252         while( index < forwardMostMove ) {
12253             /* Check to see if engine is in book */
12254             int depth = pvInfoList[index].depth;
12255             int score = pvInfoList[index].score;
12256             int in_book = 0;
12257
12258             if( depth <= 2 ) {
12259                 in_book = 1;
12260             }
12261             else if( score == 0 && depth == 63 ) {
12262                 in_book = 1; /* Zappa */
12263             }
12264             else if( score == 2 && depth == 99 ) {
12265                 in_book = 1; /* Abrok */
12266             }
12267
12268             has_book_hit += in_book;
12269
12270             if( ! in_book ) {
12271                 result = index;
12272
12273                 break;
12274             }
12275
12276             index += 2;
12277         }
12278     }
12279
12280     return result;
12281 }
12282
12283 /* [AS] */
12284 void GetOutOfBookInfo( char * buf )
12285 {
12286     int oob[2];
12287     int i;
12288     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12289
12290     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12291     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12292
12293     *buf = '\0';
12294
12295     if( oob[0] >= 0 || oob[1] >= 0 ) {
12296         for( i=0; i<2; i++ ) {
12297             int idx = oob[i];
12298
12299             if( idx >= 0 ) {
12300                 if( i > 0 && oob[0] >= 0 ) {
12301                     strcat( buf, "   " );
12302                 }
12303
12304                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12305                 sprintf( buf+strlen(buf), "%s%.2f",
12306                     pvInfoList[idx].score >= 0 ? "+" : "",
12307                     pvInfoList[idx].score / 100.0 );
12308             }
12309         }
12310     }
12311 }
12312
12313 /* Save game in PGN style and close the file */
12314 int
12315 SaveGamePGN(f)
12316      FILE *f;
12317 {
12318     int i, offset, linelen, newblock;
12319     time_t tm;
12320 //    char *movetext;
12321     char numtext[32];
12322     int movelen, numlen, blank;
12323     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12324
12325     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12326
12327     tm = time((time_t *) NULL);
12328
12329     PrintPGNTags(f, &gameInfo);
12330
12331     if (backwardMostMove > 0 || startedFromSetupPosition) {
12332         char *fen = PositionToFEN(backwardMostMove, NULL);
12333         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12334         fprintf(f, "\n{--------------\n");
12335         PrintPosition(f, backwardMostMove);
12336         fprintf(f, "--------------}\n");
12337         free(fen);
12338     }
12339     else {
12340         /* [AS] Out of book annotation */
12341         if( appData.saveOutOfBookInfo ) {
12342             char buf[64];
12343
12344             GetOutOfBookInfo( buf );
12345
12346             if( buf[0] != '\0' ) {
12347                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12348             }
12349         }
12350
12351         fprintf(f, "\n");
12352     }
12353
12354     i = backwardMostMove;
12355     linelen = 0;
12356     newblock = TRUE;
12357
12358     while (i < forwardMostMove) {
12359         /* Print comments preceding this move */
12360         if (commentList[i] != NULL) {
12361             if (linelen > 0) fprintf(f, "\n");
12362             fprintf(f, "%s", commentList[i]);
12363             linelen = 0;
12364             newblock = TRUE;
12365         }
12366
12367         /* Format move number */
12368         if ((i % 2) == 0)
12369           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12370         else
12371           if (newblock)
12372             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12373           else
12374             numtext[0] = NULLCHAR;
12375
12376         numlen = strlen(numtext);
12377         newblock = FALSE;
12378
12379         /* Print move number */
12380         blank = linelen > 0 && numlen > 0;
12381         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12382             fprintf(f, "\n");
12383             linelen = 0;
12384             blank = 0;
12385         }
12386         if (blank) {
12387             fprintf(f, " ");
12388             linelen++;
12389         }
12390         fprintf(f, "%s", numtext);
12391         linelen += numlen;
12392
12393         /* Get move */
12394         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12395         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12396
12397         /* Print move */
12398         blank = linelen > 0 && movelen > 0;
12399         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12400             fprintf(f, "\n");
12401             linelen = 0;
12402             blank = 0;
12403         }
12404         if (blank) {
12405             fprintf(f, " ");
12406             linelen++;
12407         }
12408         fprintf(f, "%s", move_buffer);
12409         linelen += movelen;
12410
12411         /* [AS] Add PV info if present */
12412         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12413             /* [HGM] add time */
12414             char buf[MSG_SIZ]; int seconds;
12415
12416             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12417
12418             if( seconds <= 0)
12419               buf[0] = 0;
12420             else
12421               if( seconds < 30 )
12422                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12423               else
12424                 {
12425                   seconds = (seconds + 4)/10; // round to full seconds
12426                   if( seconds < 60 )
12427                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12428                   else
12429                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12430                 }
12431
12432             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12433                       pvInfoList[i].score >= 0 ? "+" : "",
12434                       pvInfoList[i].score / 100.0,
12435                       pvInfoList[i].depth,
12436                       buf );
12437
12438             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12439
12440             /* Print score/depth */
12441             blank = linelen > 0 && movelen > 0;
12442             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12443                 fprintf(f, "\n");
12444                 linelen = 0;
12445                 blank = 0;
12446             }
12447             if (blank) {
12448                 fprintf(f, " ");
12449                 linelen++;
12450             }
12451             fprintf(f, "%s", move_buffer);
12452             linelen += movelen;
12453         }
12454
12455         i++;
12456     }
12457
12458     /* Start a new line */
12459     if (linelen > 0) fprintf(f, "\n");
12460
12461     /* Print comments after last move */
12462     if (commentList[i] != NULL) {
12463         fprintf(f, "%s\n", commentList[i]);
12464     }
12465
12466     /* Print result */
12467     if (gameInfo.resultDetails != NULL &&
12468         gameInfo.resultDetails[0] != NULLCHAR) {
12469         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12470                 PGNResult(gameInfo.result));
12471     } else {
12472         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12473     }
12474
12475     fclose(f);
12476     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12477     return TRUE;
12478 }
12479
12480 /* Save game in old style and close the file */
12481 int
12482 SaveGameOldStyle(f)
12483      FILE *f;
12484 {
12485     int i, offset;
12486     time_t tm;
12487
12488     tm = time((time_t *) NULL);
12489
12490     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12491     PrintOpponents(f);
12492
12493     if (backwardMostMove > 0 || startedFromSetupPosition) {
12494         fprintf(f, "\n[--------------\n");
12495         PrintPosition(f, backwardMostMove);
12496         fprintf(f, "--------------]\n");
12497     } else {
12498         fprintf(f, "\n");
12499     }
12500
12501     i = backwardMostMove;
12502     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12503
12504     while (i < forwardMostMove) {
12505         if (commentList[i] != NULL) {
12506             fprintf(f, "[%s]\n", commentList[i]);
12507         }
12508
12509         if ((i % 2) == 1) {
12510             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12511             i++;
12512         } else {
12513             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12514             i++;
12515             if (commentList[i] != NULL) {
12516                 fprintf(f, "\n");
12517                 continue;
12518             }
12519             if (i >= forwardMostMove) {
12520                 fprintf(f, "\n");
12521                 break;
12522             }
12523             fprintf(f, "%s\n", parseList[i]);
12524             i++;
12525         }
12526     }
12527
12528     if (commentList[i] != NULL) {
12529         fprintf(f, "[%s]\n", commentList[i]);
12530     }
12531
12532     /* This isn't really the old style, but it's close enough */
12533     if (gameInfo.resultDetails != NULL &&
12534         gameInfo.resultDetails[0] != NULLCHAR) {
12535         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12536                 gameInfo.resultDetails);
12537     } else {
12538         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12539     }
12540
12541     fclose(f);
12542     return TRUE;
12543 }
12544
12545 /* Save the current game to open file f and close the file */
12546 int
12547 SaveGame(f, dummy, dummy2)
12548      FILE *f;
12549      int dummy;
12550      char *dummy2;
12551 {
12552     if (gameMode == EditPosition) EditPositionDone(TRUE);
12553     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12554     if (appData.oldSaveStyle)
12555       return SaveGameOldStyle(f);
12556     else
12557       return SaveGamePGN(f);
12558 }
12559
12560 /* Save the current position to the given file */
12561 int
12562 SavePositionToFile(filename)
12563      char *filename;
12564 {
12565     FILE *f;
12566     char buf[MSG_SIZ];
12567
12568     if (strcmp(filename, "-") == 0) {
12569         return SavePosition(stdout, 0, NULL);
12570     } else {
12571         f = fopen(filename, "a");
12572         if (f == NULL) {
12573             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12574             DisplayError(buf, errno);
12575             return FALSE;
12576         } else {
12577             safeStrCpy(buf, lastMsg, MSG_SIZ);
12578             DisplayMessage(_("Waiting for access to save file"), "");
12579             flock(fileno(f), LOCK_EX); // [HGM] lock
12580             DisplayMessage(_("Saving position"), "");
12581             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12582             SavePosition(f, 0, NULL);
12583             DisplayMessage(buf, "");
12584             return TRUE;
12585         }
12586     }
12587 }
12588
12589 /* Save the current position to the given open file and close the file */
12590 int
12591 SavePosition(f, dummy, dummy2)
12592      FILE *f;
12593      int dummy;
12594      char *dummy2;
12595 {
12596     time_t tm;
12597     char *fen;
12598
12599     if (gameMode == EditPosition) EditPositionDone(TRUE);
12600     if (appData.oldSaveStyle) {
12601         tm = time((time_t *) NULL);
12602
12603         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12604         PrintOpponents(f);
12605         fprintf(f, "[--------------\n");
12606         PrintPosition(f, currentMove);
12607         fprintf(f, "--------------]\n");
12608     } else {
12609         fen = PositionToFEN(currentMove, NULL);
12610         fprintf(f, "%s\n", fen);
12611         free(fen);
12612     }
12613     fclose(f);
12614     return TRUE;
12615 }
12616
12617 void
12618 ReloadCmailMsgEvent(unregister)
12619      int unregister;
12620 {
12621 #if !WIN32
12622     static char *inFilename = NULL;
12623     static char *outFilename;
12624     int i;
12625     struct stat inbuf, outbuf;
12626     int status;
12627
12628     /* Any registered moves are unregistered if unregister is set, */
12629     /* i.e. invoked by the signal handler */
12630     if (unregister) {
12631         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12632             cmailMoveRegistered[i] = FALSE;
12633             if (cmailCommentList[i] != NULL) {
12634                 free(cmailCommentList[i]);
12635                 cmailCommentList[i] = NULL;
12636             }
12637         }
12638         nCmailMovesRegistered = 0;
12639     }
12640
12641     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12642         cmailResult[i] = CMAIL_NOT_RESULT;
12643     }
12644     nCmailResults = 0;
12645
12646     if (inFilename == NULL) {
12647         /* Because the filenames are static they only get malloced once  */
12648         /* and they never get freed                                      */
12649         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12650         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12651
12652         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12653         sprintf(outFilename, "%s.out", appData.cmailGameName);
12654     }
12655
12656     status = stat(outFilename, &outbuf);
12657     if (status < 0) {
12658         cmailMailedMove = FALSE;
12659     } else {
12660         status = stat(inFilename, &inbuf);
12661         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12662     }
12663
12664     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12665        counts the games, notes how each one terminated, etc.
12666
12667        It would be nice to remove this kludge and instead gather all
12668        the information while building the game list.  (And to keep it
12669        in the game list nodes instead of having a bunch of fixed-size
12670        parallel arrays.)  Note this will require getting each game's
12671        termination from the PGN tags, as the game list builder does
12672        not process the game moves.  --mann
12673        */
12674     cmailMsgLoaded = TRUE;
12675     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12676
12677     /* Load first game in the file or popup game menu */
12678     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12679
12680 #endif /* !WIN32 */
12681     return;
12682 }
12683
12684 int
12685 RegisterMove()
12686 {
12687     FILE *f;
12688     char string[MSG_SIZ];
12689
12690     if (   cmailMailedMove
12691         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12692         return TRUE;            /* Allow free viewing  */
12693     }
12694
12695     /* Unregister move to ensure that we don't leave RegisterMove        */
12696     /* with the move registered when the conditions for registering no   */
12697     /* longer hold                                                       */
12698     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12699         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12700         nCmailMovesRegistered --;
12701
12702         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12703           {
12704               free(cmailCommentList[lastLoadGameNumber - 1]);
12705               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12706           }
12707     }
12708
12709     if (cmailOldMove == -1) {
12710         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12711         return FALSE;
12712     }
12713
12714     if (currentMove > cmailOldMove + 1) {
12715         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12716         return FALSE;
12717     }
12718
12719     if (currentMove < cmailOldMove) {
12720         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12721         return FALSE;
12722     }
12723
12724     if (forwardMostMove > currentMove) {
12725         /* Silently truncate extra moves */
12726         TruncateGame();
12727     }
12728
12729     if (   (currentMove == cmailOldMove + 1)
12730         || (   (currentMove == cmailOldMove)
12731             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12732                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12733         if (gameInfo.result != GameUnfinished) {
12734             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12735         }
12736
12737         if (commentList[currentMove] != NULL) {
12738             cmailCommentList[lastLoadGameNumber - 1]
12739               = StrSave(commentList[currentMove]);
12740         }
12741         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12742
12743         if (appData.debugMode)
12744           fprintf(debugFP, "Saving %s for game %d\n",
12745                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12746
12747         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12748
12749         f = fopen(string, "w");
12750         if (appData.oldSaveStyle) {
12751             SaveGameOldStyle(f); /* also closes the file */
12752
12753             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12754             f = fopen(string, "w");
12755             SavePosition(f, 0, NULL); /* also closes the file */
12756         } else {
12757             fprintf(f, "{--------------\n");
12758             PrintPosition(f, currentMove);
12759             fprintf(f, "--------------}\n\n");
12760
12761             SaveGame(f, 0, NULL); /* also closes the file*/
12762         }
12763
12764         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12765         nCmailMovesRegistered ++;
12766     } else if (nCmailGames == 1) {
12767         DisplayError(_("You have not made a move yet"), 0);
12768         return FALSE;
12769     }
12770
12771     return TRUE;
12772 }
12773
12774 void
12775 MailMoveEvent()
12776 {
12777 #if !WIN32
12778     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12779     FILE *commandOutput;
12780     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12781     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12782     int nBuffers;
12783     int i;
12784     int archived;
12785     char *arcDir;
12786
12787     if (! cmailMsgLoaded) {
12788         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12789         return;
12790     }
12791
12792     if (nCmailGames == nCmailResults) {
12793         DisplayError(_("No unfinished games"), 0);
12794         return;
12795     }
12796
12797 #if CMAIL_PROHIBIT_REMAIL
12798     if (cmailMailedMove) {
12799       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);
12800         DisplayError(msg, 0);
12801         return;
12802     }
12803 #endif
12804
12805     if (! (cmailMailedMove || RegisterMove())) return;
12806
12807     if (   cmailMailedMove
12808         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12809       snprintf(string, MSG_SIZ, partCommandString,
12810                appData.debugMode ? " -v" : "", appData.cmailGameName);
12811         commandOutput = popen(string, "r");
12812
12813         if (commandOutput == NULL) {
12814             DisplayError(_("Failed to invoke cmail"), 0);
12815         } else {
12816             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12817                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12818             }
12819             if (nBuffers > 1) {
12820                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12821                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12822                 nBytes = MSG_SIZ - 1;
12823             } else {
12824                 (void) memcpy(msg, buffer, nBytes);
12825             }
12826             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12827
12828             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12829                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12830
12831                 archived = TRUE;
12832                 for (i = 0; i < nCmailGames; i ++) {
12833                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12834                         archived = FALSE;
12835                     }
12836                 }
12837                 if (   archived
12838                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12839                         != NULL)) {
12840                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12841                            arcDir,
12842                            appData.cmailGameName,
12843                            gameInfo.date);
12844                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12845                     cmailMsgLoaded = FALSE;
12846                 }
12847             }
12848
12849             DisplayInformation(msg);
12850             pclose(commandOutput);
12851         }
12852     } else {
12853         if ((*cmailMsg) != '\0') {
12854             DisplayInformation(cmailMsg);
12855         }
12856     }
12857
12858     return;
12859 #endif /* !WIN32 */
12860 }
12861
12862 char *
12863 CmailMsg()
12864 {
12865 #if WIN32
12866     return NULL;
12867 #else
12868     int  prependComma = 0;
12869     char number[5];
12870     char string[MSG_SIZ];       /* Space for game-list */
12871     int  i;
12872
12873     if (!cmailMsgLoaded) return "";
12874
12875     if (cmailMailedMove) {
12876       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12877     } else {
12878         /* Create a list of games left */
12879       snprintf(string, MSG_SIZ, "[");
12880         for (i = 0; i < nCmailGames; i ++) {
12881             if (! (   cmailMoveRegistered[i]
12882                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12883                 if (prependComma) {
12884                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12885                 } else {
12886                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12887                     prependComma = 1;
12888                 }
12889
12890                 strcat(string, number);
12891             }
12892         }
12893         strcat(string, "]");
12894
12895         if (nCmailMovesRegistered + nCmailResults == 0) {
12896             switch (nCmailGames) {
12897               case 1:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12899                 break;
12900
12901               case 2:
12902                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12903                 break;
12904
12905               default:
12906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12907                          nCmailGames);
12908                 break;
12909             }
12910         } else {
12911             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12912               case 1:
12913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12914                          string);
12915                 break;
12916
12917               case 0:
12918                 if (nCmailResults == nCmailGames) {
12919                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12920                 } else {
12921                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12922                 }
12923                 break;
12924
12925               default:
12926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12927                          string);
12928             }
12929         }
12930     }
12931     return cmailMsg;
12932 #endif /* WIN32 */
12933 }
12934
12935 void
12936 ResetGameEvent()
12937 {
12938     if (gameMode == Training)
12939       SetTrainingModeOff();
12940
12941     Reset(TRUE, TRUE);
12942     cmailMsgLoaded = FALSE;
12943     if (appData.icsActive) {
12944       SendToICS(ics_prefix);
12945       SendToICS("refresh\n");
12946     }
12947 }
12948
12949 void
12950 ExitEvent(status)
12951      int status;
12952 {
12953     exiting++;
12954     if (exiting > 2) {
12955       /* Give up on clean exit */
12956       exit(status);
12957     }
12958     if (exiting > 1) {
12959       /* Keep trying for clean exit */
12960       return;
12961     }
12962
12963     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12964
12965     if (telnetISR != NULL) {
12966       RemoveInputSource(telnetISR);
12967     }
12968     if (icsPR != NoProc) {
12969       DestroyChildProcess(icsPR, TRUE);
12970     }
12971
12972     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12973     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12974
12975     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12976     /* make sure this other one finishes before killing it!                  */
12977     if(endingGame) { int count = 0;
12978         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12979         while(endingGame && count++ < 10) DoSleep(1);
12980         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12981     }
12982
12983     /* Kill off chess programs */
12984     if (first.pr != NoProc) {
12985         ExitAnalyzeMode();
12986
12987         DoSleep( appData.delayBeforeQuit );
12988         SendToProgram("quit\n", &first);
12989         DoSleep( appData.delayAfterQuit );
12990         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12991     }
12992     if (second.pr != NoProc) {
12993         DoSleep( appData.delayBeforeQuit );
12994         SendToProgram("quit\n", &second);
12995         DoSleep( appData.delayAfterQuit );
12996         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12997     }
12998     if (first.isr != NULL) {
12999         RemoveInputSource(first.isr);
13000     }
13001     if (second.isr != NULL) {
13002         RemoveInputSource(second.isr);
13003     }
13004
13005     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13006     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13007
13008     ShutDownFrontEnd();
13009     exit(status);
13010 }
13011
13012 void
13013 PauseEvent()
13014 {
13015     if (appData.debugMode)
13016         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13017     if (pausing) {
13018         pausing = FALSE;
13019         ModeHighlight();
13020         if (gameMode == MachinePlaysWhite ||
13021             gameMode == MachinePlaysBlack) {
13022             StartClocks();
13023         } else {
13024             DisplayBothClocks();
13025         }
13026         if (gameMode == PlayFromGameFile) {
13027             if (appData.timeDelay >= 0)
13028                 AutoPlayGameLoop();
13029         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13030             Reset(FALSE, TRUE);
13031             SendToICS(ics_prefix);
13032             SendToICS("refresh\n");
13033         } else if (currentMove < forwardMostMove) {
13034             ForwardInner(forwardMostMove);
13035         }
13036         pauseExamInvalid = FALSE;
13037     } else {
13038         switch (gameMode) {
13039           default:
13040             return;
13041           case IcsExamining:
13042             pauseExamForwardMostMove = forwardMostMove;
13043             pauseExamInvalid = FALSE;
13044             /* fall through */
13045           case IcsObserving:
13046           case IcsPlayingWhite:
13047           case IcsPlayingBlack:
13048             pausing = TRUE;
13049             ModeHighlight();
13050             return;
13051           case PlayFromGameFile:
13052             (void) StopLoadGameTimer();
13053             pausing = TRUE;
13054             ModeHighlight();
13055             break;
13056           case BeginningOfGame:
13057             if (appData.icsActive) return;
13058             /* else fall through */
13059           case MachinePlaysWhite:
13060           case MachinePlaysBlack:
13061           case TwoMachinesPlay:
13062             if (forwardMostMove == 0)
13063               return;           /* don't pause if no one has moved */
13064             if ((gameMode == MachinePlaysWhite &&
13065                  !WhiteOnMove(forwardMostMove)) ||
13066                 (gameMode == MachinePlaysBlack &&
13067                  WhiteOnMove(forwardMostMove))) {
13068                 StopClocks();
13069             }
13070             pausing = TRUE;
13071             ModeHighlight();
13072             break;
13073         }
13074     }
13075 }
13076
13077 void
13078 EditCommentEvent()
13079 {
13080     char title[MSG_SIZ];
13081
13082     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13083       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13084     } else {
13085       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13086                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13087                parseList[currentMove - 1]);
13088     }
13089
13090     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13091 }
13092
13093
13094 void
13095 EditTagsEvent()
13096 {
13097     char *tags = PGNTags(&gameInfo);
13098     bookUp = FALSE;
13099     EditTagsPopUp(tags, NULL);
13100     free(tags);
13101 }
13102
13103 void
13104 AnalyzeModeEvent()
13105 {
13106     if (appData.noChessProgram || gameMode == AnalyzeMode)
13107       return;
13108
13109     if (gameMode != AnalyzeFile) {
13110         if (!appData.icsEngineAnalyze) {
13111                EditGameEvent();
13112                if (gameMode != EditGame) return;
13113         }
13114         ResurrectChessProgram();
13115         SendToProgram("analyze\n", &first);
13116         first.analyzing = TRUE;
13117         /*first.maybeThinking = TRUE;*/
13118         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13119         EngineOutputPopUp();
13120     }
13121     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13122     pausing = FALSE;
13123     ModeHighlight();
13124     SetGameInfo();
13125
13126     StartAnalysisClock();
13127     GetTimeMark(&lastNodeCountTime);
13128     lastNodeCount = 0;
13129 }
13130
13131 void
13132 AnalyzeFileEvent()
13133 {
13134     if (appData.noChessProgram || gameMode == AnalyzeFile)
13135       return;
13136
13137     if (gameMode != AnalyzeMode) {
13138         EditGameEvent();
13139         if (gameMode != EditGame) return;
13140         ResurrectChessProgram();
13141         SendToProgram("analyze\n", &first);
13142         first.analyzing = TRUE;
13143         /*first.maybeThinking = TRUE;*/
13144         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13145         EngineOutputPopUp();
13146     }
13147     gameMode = AnalyzeFile;
13148     pausing = FALSE;
13149     ModeHighlight();
13150     SetGameInfo();
13151
13152     StartAnalysisClock();
13153     GetTimeMark(&lastNodeCountTime);
13154     lastNodeCount = 0;
13155     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13156 }
13157
13158 void
13159 MachineWhiteEvent()
13160 {
13161     char buf[MSG_SIZ];
13162     char *bookHit = NULL;
13163
13164     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13165       return;
13166
13167
13168     if (gameMode == PlayFromGameFile ||
13169         gameMode == TwoMachinesPlay  ||
13170         gameMode == Training         ||
13171         gameMode == AnalyzeMode      ||
13172         gameMode == EndOfGame)
13173         EditGameEvent();
13174
13175     if (gameMode == EditPosition)
13176         EditPositionDone(TRUE);
13177
13178     if (!WhiteOnMove(currentMove)) {
13179         DisplayError(_("It is not White's turn"), 0);
13180         return;
13181     }
13182
13183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13184       ExitAnalyzeMode();
13185
13186     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13187         gameMode == AnalyzeFile)
13188         TruncateGame();
13189
13190     ResurrectChessProgram();    /* in case it isn't running */
13191     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13192         gameMode = MachinePlaysWhite;
13193         ResetClocks();
13194     } else
13195     gameMode = MachinePlaysWhite;
13196     pausing = FALSE;
13197     ModeHighlight();
13198     SetGameInfo();
13199     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13200     DisplayTitle(buf);
13201     if (first.sendName) {
13202       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13203       SendToProgram(buf, &first);
13204     }
13205     if (first.sendTime) {
13206       if (first.useColors) {
13207         SendToProgram("black\n", &first); /*gnu kludge*/
13208       }
13209       SendTimeRemaining(&first, TRUE);
13210     }
13211     if (first.useColors) {
13212       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13213     }
13214     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13215     SetMachineThinkingEnables();
13216     first.maybeThinking = TRUE;
13217     StartClocks();
13218     firstMove = FALSE;
13219
13220     if (appData.autoFlipView && !flipView) {
13221       flipView = !flipView;
13222       DrawPosition(FALSE, NULL);
13223       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13224     }
13225
13226     if(bookHit) { // [HGM] book: simulate book reply
13227         static char bookMove[MSG_SIZ]; // a bit generous?
13228
13229         programStats.nodes = programStats.depth = programStats.time =
13230         programStats.score = programStats.got_only_move = 0;
13231         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13232
13233         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13234         strcat(bookMove, bookHit);
13235         HandleMachineMove(bookMove, &first);
13236     }
13237 }
13238
13239 void
13240 MachineBlackEvent()
13241 {
13242   char buf[MSG_SIZ];
13243   char *bookHit = NULL;
13244
13245     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13246         return;
13247
13248
13249     if (gameMode == PlayFromGameFile ||
13250         gameMode == TwoMachinesPlay  ||
13251         gameMode == Training         ||
13252         gameMode == AnalyzeMode      ||
13253         gameMode == EndOfGame)
13254         EditGameEvent();
13255
13256     if (gameMode == EditPosition)
13257         EditPositionDone(TRUE);
13258
13259     if (WhiteOnMove(currentMove)) {
13260         DisplayError(_("It is not Black's turn"), 0);
13261         return;
13262     }
13263
13264     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13265       ExitAnalyzeMode();
13266
13267     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13268         gameMode == AnalyzeFile)
13269         TruncateGame();
13270
13271     ResurrectChessProgram();    /* in case it isn't running */
13272     gameMode = MachinePlaysBlack;
13273     pausing = FALSE;
13274     ModeHighlight();
13275     SetGameInfo();
13276     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13277     DisplayTitle(buf);
13278     if (first.sendName) {
13279       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13280       SendToProgram(buf, &first);
13281     }
13282     if (first.sendTime) {
13283       if (first.useColors) {
13284         SendToProgram("white\n", &first); /*gnu kludge*/
13285       }
13286       SendTimeRemaining(&first, FALSE);
13287     }
13288     if (first.useColors) {
13289       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13290     }
13291     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13292     SetMachineThinkingEnables();
13293     first.maybeThinking = TRUE;
13294     StartClocks();
13295
13296     if (appData.autoFlipView && flipView) {
13297       flipView = !flipView;
13298       DrawPosition(FALSE, NULL);
13299       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13300     }
13301     if(bookHit) { // [HGM] book: simulate book reply
13302         static char bookMove[MSG_SIZ]; // a bit generous?
13303
13304         programStats.nodes = programStats.depth = programStats.time =
13305         programStats.score = programStats.got_only_move = 0;
13306         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13307
13308         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13309         strcat(bookMove, bookHit);
13310         HandleMachineMove(bookMove, &first);
13311     }
13312 }
13313
13314
13315 void
13316 DisplayTwoMachinesTitle()
13317 {
13318     char buf[MSG_SIZ];
13319     if (appData.matchGames > 0) {
13320         if(appData.tourneyFile[0]) {
13321           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13322                    gameInfo.white, gameInfo.black,
13323                    nextGame+1, appData.matchGames+1,
13324                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13325         } else 
13326         if (first.twoMachinesColor[0] == 'w') {
13327           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13328                    gameInfo.white, gameInfo.black,
13329                    first.matchWins, second.matchWins,
13330                    matchGame - 1 - (first.matchWins + second.matchWins));
13331         } else {
13332           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13333                    gameInfo.white, gameInfo.black,
13334                    second.matchWins, first.matchWins,
13335                    matchGame - 1 - (first.matchWins + second.matchWins));
13336         }
13337     } else {
13338       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13339     }
13340     DisplayTitle(buf);
13341 }
13342
13343 void
13344 SettingsMenuIfReady()
13345 {
13346   if (second.lastPing != second.lastPong) {
13347     DisplayMessage("", _("Waiting for second chess program"));
13348     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13349     return;
13350   }
13351   ThawUI();
13352   DisplayMessage("", "");
13353   SettingsPopUp(&second);
13354 }
13355
13356 int
13357 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13358 {
13359     char buf[MSG_SIZ];
13360     if (cps->pr == NoProc) {
13361         StartChessProgram(cps);
13362         if (cps->protocolVersion == 1) {
13363           retry();
13364         } else {
13365           /* kludge: allow timeout for initial "feature" command */
13366           FreezeUI();
13367           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13368           DisplayMessage("", buf);
13369           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13370         }
13371         return 1;
13372     }
13373     return 0;
13374 }
13375
13376 void
13377 TwoMachinesEvent P((void))
13378 {
13379     int i;
13380     char buf[MSG_SIZ];
13381     ChessProgramState *onmove;
13382     char *bookHit = NULL;
13383     static int stalling = 0;
13384     TimeMark now;
13385     long wait;
13386
13387     if (appData.noChessProgram) return;
13388
13389     switch (gameMode) {
13390       case TwoMachinesPlay:
13391         return;
13392       case MachinePlaysWhite:
13393       case MachinePlaysBlack:
13394         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13395             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13396             return;
13397         }
13398         /* fall through */
13399       case BeginningOfGame:
13400       case PlayFromGameFile:
13401       case EndOfGame:
13402         EditGameEvent();
13403         if (gameMode != EditGame) return;
13404         break;
13405       case EditPosition:
13406         EditPositionDone(TRUE);
13407         break;
13408       case AnalyzeMode:
13409       case AnalyzeFile:
13410         ExitAnalyzeMode();
13411         break;
13412       case EditGame:
13413       default:
13414         break;
13415     }
13416
13417 //    forwardMostMove = currentMove;
13418     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13419
13420     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13421
13422     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13423     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13424       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13425       return;
13426     }
13427     if(!stalling) {
13428       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13429       SendToProgram("force\n", &second);
13430       stalling = 1;
13431       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13432       return;
13433     }
13434     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13435     if(appData.matchPause>10000 || appData.matchPause<10)
13436                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13437     wait = SubtractTimeMarks(&now, &pauseStart);
13438     if(wait < appData.matchPause) {
13439         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13440         return;
13441     }
13442     stalling = 0;
13443     DisplayMessage("", "");
13444     if (startedFromSetupPosition) {
13445         SendBoard(&second, backwardMostMove);
13446     if (appData.debugMode) {
13447         fprintf(debugFP, "Two Machines\n");
13448     }
13449     }
13450     for (i = backwardMostMove; i < forwardMostMove; i++) {
13451         SendMoveToProgram(i, &second);
13452     }
13453
13454     gameMode = TwoMachinesPlay;
13455     pausing = FALSE;
13456     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13457     SetGameInfo();
13458     DisplayTwoMachinesTitle();
13459     firstMove = TRUE;
13460     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13461         onmove = &first;
13462     } else {
13463         onmove = &second;
13464     }
13465     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13466     SendToProgram(first.computerString, &first);
13467     if (first.sendName) {
13468       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13469       SendToProgram(buf, &first);
13470     }
13471     SendToProgram(second.computerString, &second);
13472     if (second.sendName) {
13473       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13474       SendToProgram(buf, &second);
13475     }
13476
13477     ResetClocks();
13478     if (!first.sendTime || !second.sendTime) {
13479         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13480         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13481     }
13482     if (onmove->sendTime) {
13483       if (onmove->useColors) {
13484         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13485       }
13486       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13487     }
13488     if (onmove->useColors) {
13489       SendToProgram(onmove->twoMachinesColor, onmove);
13490     }
13491     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13492 //    SendToProgram("go\n", onmove);
13493     onmove->maybeThinking = TRUE;
13494     SetMachineThinkingEnables();
13495
13496     StartClocks();
13497
13498     if(bookHit) { // [HGM] book: simulate book reply
13499         static char bookMove[MSG_SIZ]; // a bit generous?
13500
13501         programStats.nodes = programStats.depth = programStats.time =
13502         programStats.score = programStats.got_only_move = 0;
13503         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13504
13505         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13506         strcat(bookMove, bookHit);
13507         savedMessage = bookMove; // args for deferred call
13508         savedState = onmove;
13509         ScheduleDelayedEvent(DeferredBookMove, 1);
13510     }
13511 }
13512
13513 void
13514 TrainingEvent()
13515 {
13516     if (gameMode == Training) {
13517       SetTrainingModeOff();
13518       gameMode = PlayFromGameFile;
13519       DisplayMessage("", _("Training mode off"));
13520     } else {
13521       gameMode = Training;
13522       animateTraining = appData.animate;
13523
13524       /* make sure we are not already at the end of the game */
13525       if (currentMove < forwardMostMove) {
13526         SetTrainingModeOn();
13527         DisplayMessage("", _("Training mode on"));
13528       } else {
13529         gameMode = PlayFromGameFile;
13530         DisplayError(_("Already at end of game"), 0);
13531       }
13532     }
13533     ModeHighlight();
13534 }
13535
13536 void
13537 IcsClientEvent()
13538 {
13539     if (!appData.icsActive) return;
13540     switch (gameMode) {
13541       case IcsPlayingWhite:
13542       case IcsPlayingBlack:
13543       case IcsObserving:
13544       case IcsIdle:
13545       case BeginningOfGame:
13546       case IcsExamining:
13547         return;
13548
13549       case EditGame:
13550         break;
13551
13552       case EditPosition:
13553         EditPositionDone(TRUE);
13554         break;
13555
13556       case AnalyzeMode:
13557       case AnalyzeFile:
13558         ExitAnalyzeMode();
13559         break;
13560
13561       default:
13562         EditGameEvent();
13563         break;
13564     }
13565
13566     gameMode = IcsIdle;
13567     ModeHighlight();
13568     return;
13569 }
13570
13571
13572 void
13573 EditGameEvent()
13574 {
13575     int i;
13576
13577     switch (gameMode) {
13578       case Training:
13579         SetTrainingModeOff();
13580         break;
13581       case MachinePlaysWhite:
13582       case MachinePlaysBlack:
13583       case BeginningOfGame:
13584         SendToProgram("force\n", &first);
13585         SetUserThinkingEnables();
13586         break;
13587       case PlayFromGameFile:
13588         (void) StopLoadGameTimer();
13589         if (gameFileFP != NULL) {
13590             gameFileFP = NULL;
13591         }
13592         break;
13593       case EditPosition:
13594         EditPositionDone(TRUE);
13595         break;
13596       case AnalyzeMode:
13597       case AnalyzeFile:
13598         ExitAnalyzeMode();
13599         SendToProgram("force\n", &first);
13600         break;
13601       case TwoMachinesPlay:
13602         GameEnds(EndOfFile, NULL, GE_PLAYER);
13603         ResurrectChessProgram();
13604         SetUserThinkingEnables();
13605         break;
13606       case EndOfGame:
13607         ResurrectChessProgram();
13608         break;
13609       case IcsPlayingBlack:
13610       case IcsPlayingWhite:
13611         DisplayError(_("Warning: You are still playing a game"), 0);
13612         break;
13613       case IcsObserving:
13614         DisplayError(_("Warning: You are still observing a game"), 0);
13615         break;
13616       case IcsExamining:
13617         DisplayError(_("Warning: You are still examining a game"), 0);
13618         break;
13619       case IcsIdle:
13620         break;
13621       case EditGame:
13622       default:
13623         return;
13624     }
13625
13626     pausing = FALSE;
13627     StopClocks();
13628     first.offeredDraw = second.offeredDraw = 0;
13629
13630     if (gameMode == PlayFromGameFile) {
13631         whiteTimeRemaining = timeRemaining[0][currentMove];
13632         blackTimeRemaining = timeRemaining[1][currentMove];
13633         DisplayTitle("");
13634     }
13635
13636     if (gameMode == MachinePlaysWhite ||
13637         gameMode == MachinePlaysBlack ||
13638         gameMode == TwoMachinesPlay ||
13639         gameMode == EndOfGame) {
13640         i = forwardMostMove;
13641         while (i > currentMove) {
13642             SendToProgram("undo\n", &first);
13643             i--;
13644         }
13645         if(!adjustedClock) {
13646         whiteTimeRemaining = timeRemaining[0][currentMove];
13647         blackTimeRemaining = timeRemaining[1][currentMove];
13648         DisplayBothClocks();
13649         }
13650         if (whiteFlag || blackFlag) {
13651             whiteFlag = blackFlag = 0;
13652         }
13653         DisplayTitle("");
13654     }
13655
13656     gameMode = EditGame;
13657     ModeHighlight();
13658     SetGameInfo();
13659 }
13660
13661
13662 void
13663 EditPositionEvent()
13664 {
13665     if (gameMode == EditPosition) {
13666         EditGameEvent();
13667         return;
13668     }
13669
13670     EditGameEvent();
13671     if (gameMode != EditGame) return;
13672
13673     gameMode = EditPosition;
13674     ModeHighlight();
13675     SetGameInfo();
13676     if (currentMove > 0)
13677       CopyBoard(boards[0], boards[currentMove]);
13678
13679     blackPlaysFirst = !WhiteOnMove(currentMove);
13680     ResetClocks();
13681     currentMove = forwardMostMove = backwardMostMove = 0;
13682     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13683     DisplayMove(-1);
13684 }
13685
13686 void
13687 ExitAnalyzeMode()
13688 {
13689     /* [DM] icsEngineAnalyze - possible call from other functions */
13690     if (appData.icsEngineAnalyze) {
13691         appData.icsEngineAnalyze = FALSE;
13692
13693         DisplayMessage("",_("Close ICS engine analyze..."));
13694     }
13695     if (first.analysisSupport && first.analyzing) {
13696       SendToProgram("exit\n", &first);
13697       first.analyzing = FALSE;
13698     }
13699     thinkOutput[0] = NULLCHAR;
13700 }
13701
13702 void
13703 EditPositionDone(Boolean fakeRights)
13704 {
13705     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13706
13707     startedFromSetupPosition = TRUE;
13708     InitChessProgram(&first, FALSE);
13709     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13710       boards[0][EP_STATUS] = EP_NONE;
13711       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13712     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13713         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13714         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13715       } else boards[0][CASTLING][2] = NoRights;
13716     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13717         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13718         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13719       } else boards[0][CASTLING][5] = NoRights;
13720     }
13721     SendToProgram("force\n", &first);
13722     if (blackPlaysFirst) {
13723         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13724         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13725         currentMove = forwardMostMove = backwardMostMove = 1;
13726         CopyBoard(boards[1], boards[0]);
13727     } else {
13728         currentMove = forwardMostMove = backwardMostMove = 0;
13729     }
13730     SendBoard(&first, forwardMostMove);
13731     if (appData.debugMode) {
13732         fprintf(debugFP, "EditPosDone\n");
13733     }
13734     DisplayTitle("");
13735     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13736     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13737     gameMode = EditGame;
13738     ModeHighlight();
13739     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13740     ClearHighlights(); /* [AS] */
13741 }
13742
13743 /* Pause for `ms' milliseconds */
13744 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13745 void
13746 TimeDelay(ms)
13747      long ms;
13748 {
13749     TimeMark m1, m2;
13750
13751     GetTimeMark(&m1);
13752     do {
13753         GetTimeMark(&m2);
13754     } while (SubtractTimeMarks(&m2, &m1) < ms);
13755 }
13756
13757 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13758 void
13759 SendMultiLineToICS(buf)
13760      char *buf;
13761 {
13762     char temp[MSG_SIZ+1], *p;
13763     int len;
13764
13765     len = strlen(buf);
13766     if (len > MSG_SIZ)
13767       len = MSG_SIZ;
13768
13769     strncpy(temp, buf, len);
13770     temp[len] = 0;
13771
13772     p = temp;
13773     while (*p) {
13774         if (*p == '\n' || *p == '\r')
13775           *p = ' ';
13776         ++p;
13777     }
13778
13779     strcat(temp, "\n");
13780     SendToICS(temp);
13781     SendToPlayer(temp, strlen(temp));
13782 }
13783
13784 void
13785 SetWhiteToPlayEvent()
13786 {
13787     if (gameMode == EditPosition) {
13788         blackPlaysFirst = FALSE;
13789         DisplayBothClocks();    /* works because currentMove is 0 */
13790     } else if (gameMode == IcsExamining) {
13791         SendToICS(ics_prefix);
13792         SendToICS("tomove white\n");
13793     }
13794 }
13795
13796 void
13797 SetBlackToPlayEvent()
13798 {
13799     if (gameMode == EditPosition) {
13800         blackPlaysFirst = TRUE;
13801         currentMove = 1;        /* kludge */
13802         DisplayBothClocks();
13803         currentMove = 0;
13804     } else if (gameMode == IcsExamining) {
13805         SendToICS(ics_prefix);
13806         SendToICS("tomove black\n");
13807     }
13808 }
13809
13810 void
13811 EditPositionMenuEvent(selection, x, y)
13812      ChessSquare selection;
13813      int x, y;
13814 {
13815     char buf[MSG_SIZ];
13816     ChessSquare piece = boards[0][y][x];
13817
13818     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13819
13820     switch (selection) {
13821       case ClearBoard:
13822         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13823             SendToICS(ics_prefix);
13824             SendToICS("bsetup clear\n");
13825         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13826             SendToICS(ics_prefix);
13827             SendToICS("clearboard\n");
13828         } else {
13829             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13830                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13831                 for (y = 0; y < BOARD_HEIGHT; y++) {
13832                     if (gameMode == IcsExamining) {
13833                         if (boards[currentMove][y][x] != EmptySquare) {
13834                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13835                                     AAA + x, ONE + y);
13836                             SendToICS(buf);
13837                         }
13838                     } else {
13839                         boards[0][y][x] = p;
13840                     }
13841                 }
13842             }
13843         }
13844         if (gameMode == EditPosition) {
13845             DrawPosition(FALSE, boards[0]);
13846         }
13847         break;
13848
13849       case WhitePlay:
13850         SetWhiteToPlayEvent();
13851         break;
13852
13853       case BlackPlay:
13854         SetBlackToPlayEvent();
13855         break;
13856
13857       case EmptySquare:
13858         if (gameMode == IcsExamining) {
13859             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13860             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13861             SendToICS(buf);
13862         } else {
13863             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13864                 if(x == BOARD_LEFT-2) {
13865                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13866                     boards[0][y][1] = 0;
13867                 } else
13868                 if(x == BOARD_RGHT+1) {
13869                     if(y >= gameInfo.holdingsSize) break;
13870                     boards[0][y][BOARD_WIDTH-2] = 0;
13871                 } else break;
13872             }
13873             boards[0][y][x] = EmptySquare;
13874             DrawPosition(FALSE, boards[0]);
13875         }
13876         break;
13877
13878       case PromotePiece:
13879         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13880            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13881             selection = (ChessSquare) (PROMOTED piece);
13882         } else if(piece == EmptySquare) selection = WhiteSilver;
13883         else selection = (ChessSquare)((int)piece - 1);
13884         goto defaultlabel;
13885
13886       case DemotePiece:
13887         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13888            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13889             selection = (ChessSquare) (DEMOTED piece);
13890         } else if(piece == EmptySquare) selection = BlackSilver;
13891         else selection = (ChessSquare)((int)piece + 1);
13892         goto defaultlabel;
13893
13894       case WhiteQueen:
13895       case BlackQueen:
13896         if(gameInfo.variant == VariantShatranj ||
13897            gameInfo.variant == VariantXiangqi  ||
13898            gameInfo.variant == VariantCourier  ||
13899            gameInfo.variant == VariantMakruk     )
13900             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13901         goto defaultlabel;
13902
13903       case WhiteKing:
13904       case BlackKing:
13905         if(gameInfo.variant == VariantXiangqi)
13906             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13907         if(gameInfo.variant == VariantKnightmate)
13908             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13909       default:
13910         defaultlabel:
13911         if (gameMode == IcsExamining) {
13912             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13913             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13914                      PieceToChar(selection), AAA + x, ONE + y);
13915             SendToICS(buf);
13916         } else {
13917             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13918                 int n;
13919                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13920                     n = PieceToNumber(selection - BlackPawn);
13921                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13922                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13923                     boards[0][BOARD_HEIGHT-1-n][1]++;
13924                 } else
13925                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13926                     n = PieceToNumber(selection);
13927                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13928                     boards[0][n][BOARD_WIDTH-1] = selection;
13929                     boards[0][n][BOARD_WIDTH-2]++;
13930                 }
13931             } else
13932             boards[0][y][x] = selection;
13933             DrawPosition(TRUE, boards[0]);
13934         }
13935         break;
13936     }
13937 }
13938
13939
13940 void
13941 DropMenuEvent(selection, x, y)
13942      ChessSquare selection;
13943      int x, y;
13944 {
13945     ChessMove moveType;
13946
13947     switch (gameMode) {
13948       case IcsPlayingWhite:
13949       case MachinePlaysBlack:
13950         if (!WhiteOnMove(currentMove)) {
13951             DisplayMoveError(_("It is Black's turn"));
13952             return;
13953         }
13954         moveType = WhiteDrop;
13955         break;
13956       case IcsPlayingBlack:
13957       case MachinePlaysWhite:
13958         if (WhiteOnMove(currentMove)) {
13959             DisplayMoveError(_("It is White's turn"));
13960             return;
13961         }
13962         moveType = BlackDrop;
13963         break;
13964       case EditGame:
13965         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13966         break;
13967       default:
13968         return;
13969     }
13970
13971     if (moveType == BlackDrop && selection < BlackPawn) {
13972       selection = (ChessSquare) ((int) selection
13973                                  + (int) BlackPawn - (int) WhitePawn);
13974     }
13975     if (boards[currentMove][y][x] != EmptySquare) {
13976         DisplayMoveError(_("That square is occupied"));
13977         return;
13978     }
13979
13980     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13981 }
13982
13983 void
13984 AcceptEvent()
13985 {
13986     /* Accept a pending offer of any kind from opponent */
13987
13988     if (appData.icsActive) {
13989         SendToICS(ics_prefix);
13990         SendToICS("accept\n");
13991     } else if (cmailMsgLoaded) {
13992         if (currentMove == cmailOldMove &&
13993             commentList[cmailOldMove] != NULL &&
13994             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13995                    "Black offers a draw" : "White offers a draw")) {
13996             TruncateGame();
13997             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13998             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13999         } else {
14000             DisplayError(_("There is no pending offer on this move"), 0);
14001             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14002         }
14003     } else {
14004         /* Not used for offers from chess program */
14005     }
14006 }
14007
14008 void
14009 DeclineEvent()
14010 {
14011     /* Decline a pending offer of any kind from opponent */
14012
14013     if (appData.icsActive) {
14014         SendToICS(ics_prefix);
14015         SendToICS("decline\n");
14016     } else if (cmailMsgLoaded) {
14017         if (currentMove == cmailOldMove &&
14018             commentList[cmailOldMove] != NULL &&
14019             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14020                    "Black offers a draw" : "White offers a draw")) {
14021 #ifdef NOTDEF
14022             AppendComment(cmailOldMove, "Draw declined", TRUE);
14023             DisplayComment(cmailOldMove - 1, "Draw declined");
14024 #endif /*NOTDEF*/
14025         } else {
14026             DisplayError(_("There is no pending offer on this move"), 0);
14027         }
14028     } else {
14029         /* Not used for offers from chess program */
14030     }
14031 }
14032
14033 void
14034 RematchEvent()
14035 {
14036     /* Issue ICS rematch command */
14037     if (appData.icsActive) {
14038         SendToICS(ics_prefix);
14039         SendToICS("rematch\n");
14040     }
14041 }
14042
14043 void
14044 CallFlagEvent()
14045 {
14046     /* Call your opponent's flag (claim a win on time) */
14047     if (appData.icsActive) {
14048         SendToICS(ics_prefix);
14049         SendToICS("flag\n");
14050     } else {
14051         switch (gameMode) {
14052           default:
14053             return;
14054           case MachinePlaysWhite:
14055             if (whiteFlag) {
14056                 if (blackFlag)
14057                   GameEnds(GameIsDrawn, "Both players ran out of time",
14058                            GE_PLAYER);
14059                 else
14060                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14061             } else {
14062                 DisplayError(_("Your opponent is not out of time"), 0);
14063             }
14064             break;
14065           case MachinePlaysBlack:
14066             if (blackFlag) {
14067                 if (whiteFlag)
14068                   GameEnds(GameIsDrawn, "Both players ran out of time",
14069                            GE_PLAYER);
14070                 else
14071                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14072             } else {
14073                 DisplayError(_("Your opponent is not out of time"), 0);
14074             }
14075             break;
14076         }
14077     }
14078 }
14079
14080 void
14081 ClockClick(int which)
14082 {       // [HGM] code moved to back-end from winboard.c
14083         if(which) { // black clock
14084           if (gameMode == EditPosition || gameMode == IcsExamining) {
14085             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14086             SetBlackToPlayEvent();
14087           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14088           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14089           } else if (shiftKey) {
14090             AdjustClock(which, -1);
14091           } else if (gameMode == IcsPlayingWhite ||
14092                      gameMode == MachinePlaysBlack) {
14093             CallFlagEvent();
14094           }
14095         } else { // white clock
14096           if (gameMode == EditPosition || gameMode == IcsExamining) {
14097             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14098             SetWhiteToPlayEvent();
14099           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14100           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14101           } else if (shiftKey) {
14102             AdjustClock(which, -1);
14103           } else if (gameMode == IcsPlayingBlack ||
14104                    gameMode == MachinePlaysWhite) {
14105             CallFlagEvent();
14106           }
14107         }
14108 }
14109
14110 void
14111 DrawEvent()
14112 {
14113     /* Offer draw or accept pending draw offer from opponent */
14114
14115     if (appData.icsActive) {
14116         /* Note: tournament rules require draw offers to be
14117            made after you make your move but before you punch
14118            your clock.  Currently ICS doesn't let you do that;
14119            instead, you immediately punch your clock after making
14120            a move, but you can offer a draw at any time. */
14121
14122         SendToICS(ics_prefix);
14123         SendToICS("draw\n");
14124         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14125     } else if (cmailMsgLoaded) {
14126         if (currentMove == cmailOldMove &&
14127             commentList[cmailOldMove] != NULL &&
14128             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14129                    "Black offers a draw" : "White offers a draw")) {
14130             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14132         } else if (currentMove == cmailOldMove + 1) {
14133             char *offer = WhiteOnMove(cmailOldMove) ?
14134               "White offers a draw" : "Black offers a draw";
14135             AppendComment(currentMove, offer, TRUE);
14136             DisplayComment(currentMove - 1, offer);
14137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14138         } else {
14139             DisplayError(_("You must make your move before offering a draw"), 0);
14140             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14141         }
14142     } else if (first.offeredDraw) {
14143         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14144     } else {
14145         if (first.sendDrawOffers) {
14146             SendToProgram("draw\n", &first);
14147             userOfferedDraw = TRUE;
14148         }
14149     }
14150 }
14151
14152 void
14153 AdjournEvent()
14154 {
14155     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14156
14157     if (appData.icsActive) {
14158         SendToICS(ics_prefix);
14159         SendToICS("adjourn\n");
14160     } else {
14161         /* Currently GNU Chess doesn't offer or accept Adjourns */
14162     }
14163 }
14164
14165
14166 void
14167 AbortEvent()
14168 {
14169     /* Offer Abort or accept pending Abort offer from opponent */
14170
14171     if (appData.icsActive) {
14172         SendToICS(ics_prefix);
14173         SendToICS("abort\n");
14174     } else {
14175         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14176     }
14177 }
14178
14179 void
14180 ResignEvent()
14181 {
14182     /* Resign.  You can do this even if it's not your turn. */
14183
14184     if (appData.icsActive) {
14185         SendToICS(ics_prefix);
14186         SendToICS("resign\n");
14187     } else {
14188         switch (gameMode) {
14189           case MachinePlaysWhite:
14190             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14191             break;
14192           case MachinePlaysBlack:
14193             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14194             break;
14195           case EditGame:
14196             if (cmailMsgLoaded) {
14197                 TruncateGame();
14198                 if (WhiteOnMove(cmailOldMove)) {
14199                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14200                 } else {
14201                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14202                 }
14203                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14204             }
14205             break;
14206           default:
14207             break;
14208         }
14209     }
14210 }
14211
14212
14213 void
14214 StopObservingEvent()
14215 {
14216     /* Stop observing current games */
14217     SendToICS(ics_prefix);
14218     SendToICS("unobserve\n");
14219 }
14220
14221 void
14222 StopExaminingEvent()
14223 {
14224     /* Stop observing current game */
14225     SendToICS(ics_prefix);
14226     SendToICS("unexamine\n");
14227 }
14228
14229 void
14230 ForwardInner(target)
14231      int target;
14232 {
14233     int limit;
14234
14235     if (appData.debugMode)
14236         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14237                 target, currentMove, forwardMostMove);
14238
14239     if (gameMode == EditPosition)
14240       return;
14241
14242     MarkTargetSquares(1);
14243
14244     if (gameMode == PlayFromGameFile && !pausing)
14245       PauseEvent();
14246
14247     if (gameMode == IcsExamining && pausing)
14248       limit = pauseExamForwardMostMove;
14249     else
14250       limit = forwardMostMove;
14251
14252     if (target > limit) target = limit;
14253
14254     if (target > 0 && moveList[target - 1][0]) {
14255         int fromX, fromY, toX, toY;
14256         toX = moveList[target - 1][2] - AAA;
14257         toY = moveList[target - 1][3] - ONE;
14258         if (moveList[target - 1][1] == '@') {
14259             if (appData.highlightLastMove) {
14260                 SetHighlights(-1, -1, toX, toY);
14261             }
14262         } else {
14263             fromX = moveList[target - 1][0] - AAA;
14264             fromY = moveList[target - 1][1] - ONE;
14265             if (target == currentMove + 1) {
14266                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14267             }
14268             if (appData.highlightLastMove) {
14269                 SetHighlights(fromX, fromY, toX, toY);
14270             }
14271         }
14272     }
14273     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14274         gameMode == Training || gameMode == PlayFromGameFile ||
14275         gameMode == AnalyzeFile) {
14276         while (currentMove < target) {
14277             SendMoveToProgram(currentMove++, &first);
14278         }
14279     } else {
14280         currentMove = target;
14281     }
14282
14283     if (gameMode == EditGame || gameMode == EndOfGame) {
14284         whiteTimeRemaining = timeRemaining[0][currentMove];
14285         blackTimeRemaining = timeRemaining[1][currentMove];
14286     }
14287     DisplayBothClocks();
14288     DisplayMove(currentMove - 1);
14289     DrawPosition(FALSE, boards[currentMove]);
14290     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14291     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14292         DisplayComment(currentMove - 1, commentList[currentMove]);
14293     }
14294 }
14295
14296
14297 void
14298 ForwardEvent()
14299 {
14300     if (gameMode == IcsExamining && !pausing) {
14301         SendToICS(ics_prefix);
14302         SendToICS("forward\n");
14303     } else {
14304         ForwardInner(currentMove + 1);
14305     }
14306 }
14307
14308 void
14309 ToEndEvent()
14310 {
14311     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14312         /* to optimze, we temporarily turn off analysis mode while we feed
14313          * the remaining moves to the engine. Otherwise we get analysis output
14314          * after each move.
14315          */
14316         if (first.analysisSupport) {
14317           SendToProgram("exit\nforce\n", &first);
14318           first.analyzing = FALSE;
14319         }
14320     }
14321
14322     if (gameMode == IcsExamining && !pausing) {
14323         SendToICS(ics_prefix);
14324         SendToICS("forward 999999\n");
14325     } else {
14326         ForwardInner(forwardMostMove);
14327     }
14328
14329     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14330         /* we have fed all the moves, so reactivate analysis mode */
14331         SendToProgram("analyze\n", &first);
14332         first.analyzing = TRUE;
14333         /*first.maybeThinking = TRUE;*/
14334         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14335     }
14336 }
14337
14338 void
14339 BackwardInner(target)
14340      int target;
14341 {
14342     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14343
14344     if (appData.debugMode)
14345         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14346                 target, currentMove, forwardMostMove);
14347
14348     if (gameMode == EditPosition) return;
14349     MarkTargetSquares(1);
14350     if (currentMove <= backwardMostMove) {
14351         ClearHighlights();
14352         DrawPosition(full_redraw, boards[currentMove]);
14353         return;
14354     }
14355     if (gameMode == PlayFromGameFile && !pausing)
14356       PauseEvent();
14357
14358     if (moveList[target][0]) {
14359         int fromX, fromY, toX, toY;
14360         toX = moveList[target][2] - AAA;
14361         toY = moveList[target][3] - ONE;
14362         if (moveList[target][1] == '@') {
14363             if (appData.highlightLastMove) {
14364                 SetHighlights(-1, -1, toX, toY);
14365             }
14366         } else {
14367             fromX = moveList[target][0] - AAA;
14368             fromY = moveList[target][1] - ONE;
14369             if (target == currentMove - 1) {
14370                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14371             }
14372             if (appData.highlightLastMove) {
14373                 SetHighlights(fromX, fromY, toX, toY);
14374             }
14375         }
14376     }
14377     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14378         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14379         while (currentMove > target) {
14380             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14381                 // null move cannot be undone. Reload program with move history before it.
14382                 int i;
14383                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14384                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14385                 }
14386                 SendBoard(&first, i); 
14387                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14388                 break;
14389             }
14390             SendToProgram("undo\n", &first);
14391             currentMove--;
14392         }
14393     } else {
14394         currentMove = target;
14395     }
14396
14397     if (gameMode == EditGame || gameMode == EndOfGame) {
14398         whiteTimeRemaining = timeRemaining[0][currentMove];
14399         blackTimeRemaining = timeRemaining[1][currentMove];
14400     }
14401     DisplayBothClocks();
14402     DisplayMove(currentMove - 1);
14403     DrawPosition(full_redraw, boards[currentMove]);
14404     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14405     // [HGM] PV info: routine tests if comment empty
14406     DisplayComment(currentMove - 1, commentList[currentMove]);
14407 }
14408
14409 void
14410 BackwardEvent()
14411 {
14412     if (gameMode == IcsExamining && !pausing) {
14413         SendToICS(ics_prefix);
14414         SendToICS("backward\n");
14415     } else {
14416         BackwardInner(currentMove - 1);
14417     }
14418 }
14419
14420 void
14421 ToStartEvent()
14422 {
14423     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14424         /* to optimize, we temporarily turn off analysis mode while we undo
14425          * all the moves. Otherwise we get analysis output after each undo.
14426          */
14427         if (first.analysisSupport) {
14428           SendToProgram("exit\nforce\n", &first);
14429           first.analyzing = FALSE;
14430         }
14431     }
14432
14433     if (gameMode == IcsExamining && !pausing) {
14434         SendToICS(ics_prefix);
14435         SendToICS("backward 999999\n");
14436     } else {
14437         BackwardInner(backwardMostMove);
14438     }
14439
14440     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14441         /* we have fed all the moves, so reactivate analysis mode */
14442         SendToProgram("analyze\n", &first);
14443         first.analyzing = TRUE;
14444         /*first.maybeThinking = TRUE;*/
14445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14446     }
14447 }
14448
14449 void
14450 ToNrEvent(int to)
14451 {
14452   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14453   if (to >= forwardMostMove) to = forwardMostMove;
14454   if (to <= backwardMostMove) to = backwardMostMove;
14455   if (to < currentMove) {
14456     BackwardInner(to);
14457   } else {
14458     ForwardInner(to);
14459   }
14460 }
14461
14462 void
14463 RevertEvent(Boolean annotate)
14464 {
14465     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14466         return;
14467     }
14468     if (gameMode != IcsExamining) {
14469         DisplayError(_("You are not examining a game"), 0);
14470         return;
14471     }
14472     if (pausing) {
14473         DisplayError(_("You can't revert while pausing"), 0);
14474         return;
14475     }
14476     SendToICS(ics_prefix);
14477     SendToICS("revert\n");
14478 }
14479
14480 void
14481 RetractMoveEvent()
14482 {
14483     switch (gameMode) {
14484       case MachinePlaysWhite:
14485       case MachinePlaysBlack:
14486         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14487             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14488             return;
14489         }
14490         if (forwardMostMove < 2) return;
14491         currentMove = forwardMostMove = forwardMostMove - 2;
14492         whiteTimeRemaining = timeRemaining[0][currentMove];
14493         blackTimeRemaining = timeRemaining[1][currentMove];
14494         DisplayBothClocks();
14495         DisplayMove(currentMove - 1);
14496         ClearHighlights();/*!! could figure this out*/
14497         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14498         SendToProgram("remove\n", &first);
14499         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14500         break;
14501
14502       case BeginningOfGame:
14503       default:
14504         break;
14505
14506       case IcsPlayingWhite:
14507       case IcsPlayingBlack:
14508         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14509             SendToICS(ics_prefix);
14510             SendToICS("takeback 2\n");
14511         } else {
14512             SendToICS(ics_prefix);
14513             SendToICS("takeback 1\n");
14514         }
14515         break;
14516     }
14517 }
14518
14519 void
14520 MoveNowEvent()
14521 {
14522     ChessProgramState *cps;
14523
14524     switch (gameMode) {
14525       case MachinePlaysWhite:
14526         if (!WhiteOnMove(forwardMostMove)) {
14527             DisplayError(_("It is your turn"), 0);
14528             return;
14529         }
14530         cps = &first;
14531         break;
14532       case MachinePlaysBlack:
14533         if (WhiteOnMove(forwardMostMove)) {
14534             DisplayError(_("It is your turn"), 0);
14535             return;
14536         }
14537         cps = &first;
14538         break;
14539       case TwoMachinesPlay:
14540         if (WhiteOnMove(forwardMostMove) ==
14541             (first.twoMachinesColor[0] == 'w')) {
14542             cps = &first;
14543         } else {
14544             cps = &second;
14545         }
14546         break;
14547       case BeginningOfGame:
14548       default:
14549         return;
14550     }
14551     SendToProgram("?\n", cps);
14552 }
14553
14554 void
14555 TruncateGameEvent()
14556 {
14557     EditGameEvent();
14558     if (gameMode != EditGame) return;
14559     TruncateGame();
14560 }
14561
14562 void
14563 TruncateGame()
14564 {
14565     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14566     if (forwardMostMove > currentMove) {
14567         if (gameInfo.resultDetails != NULL) {
14568             free(gameInfo.resultDetails);
14569             gameInfo.resultDetails = NULL;
14570             gameInfo.result = GameUnfinished;
14571         }
14572         forwardMostMove = currentMove;
14573         HistorySet(parseList, backwardMostMove, forwardMostMove,
14574                    currentMove-1);
14575     }
14576 }
14577
14578 void
14579 HintEvent()
14580 {
14581     if (appData.noChessProgram) return;
14582     switch (gameMode) {
14583       case MachinePlaysWhite:
14584         if (WhiteOnMove(forwardMostMove)) {
14585             DisplayError(_("Wait until your turn"), 0);
14586             return;
14587         }
14588         break;
14589       case BeginningOfGame:
14590       case MachinePlaysBlack:
14591         if (!WhiteOnMove(forwardMostMove)) {
14592             DisplayError(_("Wait until your turn"), 0);
14593             return;
14594         }
14595         break;
14596       default:
14597         DisplayError(_("No hint available"), 0);
14598         return;
14599     }
14600     SendToProgram("hint\n", &first);
14601     hintRequested = TRUE;
14602 }
14603
14604 void
14605 BookEvent()
14606 {
14607     if (appData.noChessProgram) return;
14608     switch (gameMode) {
14609       case MachinePlaysWhite:
14610         if (WhiteOnMove(forwardMostMove)) {
14611             DisplayError(_("Wait until your turn"), 0);
14612             return;
14613         }
14614         break;
14615       case BeginningOfGame:
14616       case MachinePlaysBlack:
14617         if (!WhiteOnMove(forwardMostMove)) {
14618             DisplayError(_("Wait until your turn"), 0);
14619             return;
14620         }
14621         break;
14622       case EditPosition:
14623         EditPositionDone(TRUE);
14624         break;
14625       case TwoMachinesPlay:
14626         return;
14627       default:
14628         break;
14629     }
14630     SendToProgram("bk\n", &first);
14631     bookOutput[0] = NULLCHAR;
14632     bookRequested = TRUE;
14633 }
14634
14635 void
14636 AboutGameEvent()
14637 {
14638     char *tags = PGNTags(&gameInfo);
14639     TagsPopUp(tags, CmailMsg());
14640     free(tags);
14641 }
14642
14643 /* end button procedures */
14644
14645 void
14646 PrintPosition(fp, move)
14647      FILE *fp;
14648      int move;
14649 {
14650     int i, j;
14651
14652     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14653         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14654             char c = PieceToChar(boards[move][i][j]);
14655             fputc(c == 'x' ? '.' : c, fp);
14656             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14657         }
14658     }
14659     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14660       fprintf(fp, "white to play\n");
14661     else
14662       fprintf(fp, "black to play\n");
14663 }
14664
14665 void
14666 PrintOpponents(fp)
14667      FILE *fp;
14668 {
14669     if (gameInfo.white != NULL) {
14670         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14671     } else {
14672         fprintf(fp, "\n");
14673     }
14674 }
14675
14676 /* Find last component of program's own name, using some heuristics */
14677 void
14678 TidyProgramName(prog, host, buf)
14679      char *prog, *host, buf[MSG_SIZ];
14680 {
14681     char *p, *q;
14682     int local = (strcmp(host, "localhost") == 0);
14683     while (!local && (p = strchr(prog, ';')) != NULL) {
14684         p++;
14685         while (*p == ' ') p++;
14686         prog = p;
14687     }
14688     if (*prog == '"' || *prog == '\'') {
14689         q = strchr(prog + 1, *prog);
14690     } else {
14691         q = strchr(prog, ' ');
14692     }
14693     if (q == NULL) q = prog + strlen(prog);
14694     p = q;
14695     while (p >= prog && *p != '/' && *p != '\\') p--;
14696     p++;
14697     if(p == prog && *p == '"') p++;
14698     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14699     memcpy(buf, p, q - p);
14700     buf[q - p] = NULLCHAR;
14701     if (!local) {
14702         strcat(buf, "@");
14703         strcat(buf, host);
14704     }
14705 }
14706
14707 char *
14708 TimeControlTagValue()
14709 {
14710     char buf[MSG_SIZ];
14711     if (!appData.clockMode) {
14712       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14713     } else if (movesPerSession > 0) {
14714       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14715     } else if (timeIncrement == 0) {
14716       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14717     } else {
14718       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14719     }
14720     return StrSave(buf);
14721 }
14722
14723 void
14724 SetGameInfo()
14725 {
14726     /* This routine is used only for certain modes */
14727     VariantClass v = gameInfo.variant;
14728     ChessMove r = GameUnfinished;
14729     char *p = NULL;
14730
14731     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14732         r = gameInfo.result;
14733         p = gameInfo.resultDetails;
14734         gameInfo.resultDetails = NULL;
14735     }
14736     ClearGameInfo(&gameInfo);
14737     gameInfo.variant = v;
14738
14739     switch (gameMode) {
14740       case MachinePlaysWhite:
14741         gameInfo.event = StrSave( appData.pgnEventHeader );
14742         gameInfo.site = StrSave(HostName());
14743         gameInfo.date = PGNDate();
14744         gameInfo.round = StrSave("-");
14745         gameInfo.white = StrSave(first.tidy);
14746         gameInfo.black = StrSave(UserName());
14747         gameInfo.timeControl = TimeControlTagValue();
14748         break;
14749
14750       case MachinePlaysBlack:
14751         gameInfo.event = StrSave( appData.pgnEventHeader );
14752         gameInfo.site = StrSave(HostName());
14753         gameInfo.date = PGNDate();
14754         gameInfo.round = StrSave("-");
14755         gameInfo.white = StrSave(UserName());
14756         gameInfo.black = StrSave(first.tidy);
14757         gameInfo.timeControl = TimeControlTagValue();
14758         break;
14759
14760       case TwoMachinesPlay:
14761         gameInfo.event = StrSave( appData.pgnEventHeader );
14762         gameInfo.site = StrSave(HostName());
14763         gameInfo.date = PGNDate();
14764         if (roundNr > 0) {
14765             char buf[MSG_SIZ];
14766             snprintf(buf, MSG_SIZ, "%d", roundNr);
14767             gameInfo.round = StrSave(buf);
14768         } else {
14769             gameInfo.round = StrSave("-");
14770         }
14771         if (first.twoMachinesColor[0] == 'w') {
14772             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14773             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14774         } else {
14775             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14776             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14777         }
14778         gameInfo.timeControl = TimeControlTagValue();
14779         break;
14780
14781       case EditGame:
14782         gameInfo.event = StrSave("Edited game");
14783         gameInfo.site = StrSave(HostName());
14784         gameInfo.date = PGNDate();
14785         gameInfo.round = StrSave("-");
14786         gameInfo.white = StrSave("-");
14787         gameInfo.black = StrSave("-");
14788         gameInfo.result = r;
14789         gameInfo.resultDetails = p;
14790         break;
14791
14792       case EditPosition:
14793         gameInfo.event = StrSave("Edited position");
14794         gameInfo.site = StrSave(HostName());
14795         gameInfo.date = PGNDate();
14796         gameInfo.round = StrSave("-");
14797         gameInfo.white = StrSave("-");
14798         gameInfo.black = StrSave("-");
14799         break;
14800
14801       case IcsPlayingWhite:
14802       case IcsPlayingBlack:
14803       case IcsObserving:
14804       case IcsExamining:
14805         break;
14806
14807       case PlayFromGameFile:
14808         gameInfo.event = StrSave("Game from non-PGN file");
14809         gameInfo.site = StrSave(HostName());
14810         gameInfo.date = PGNDate();
14811         gameInfo.round = StrSave("-");
14812         gameInfo.white = StrSave("?");
14813         gameInfo.black = StrSave("?");
14814         break;
14815
14816       default:
14817         break;
14818     }
14819 }
14820
14821 void
14822 ReplaceComment(index, text)
14823      int index;
14824      char *text;
14825 {
14826     int len;
14827     char *p;
14828     float score;
14829
14830     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14831        pvInfoList[index-1].depth == len &&
14832        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14833        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14834     while (*text == '\n') text++;
14835     len = strlen(text);
14836     while (len > 0 && text[len - 1] == '\n') len--;
14837
14838     if (commentList[index] != NULL)
14839       free(commentList[index]);
14840
14841     if (len == 0) {
14842         commentList[index] = NULL;
14843         return;
14844     }
14845   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14846       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14847       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14848     commentList[index] = (char *) malloc(len + 2);
14849     strncpy(commentList[index], text, len);
14850     commentList[index][len] = '\n';
14851     commentList[index][len + 1] = NULLCHAR;
14852   } else {
14853     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14854     char *p;
14855     commentList[index] = (char *) malloc(len + 7);
14856     safeStrCpy(commentList[index], "{\n", 3);
14857     safeStrCpy(commentList[index]+2, text, len+1);
14858     commentList[index][len+2] = NULLCHAR;
14859     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14860     strcat(commentList[index], "\n}\n");
14861   }
14862 }
14863
14864 void
14865 CrushCRs(text)
14866      char *text;
14867 {
14868   char *p = text;
14869   char *q = text;
14870   char ch;
14871
14872   do {
14873     ch = *p++;
14874     if (ch == '\r') continue;
14875     *q++ = ch;
14876   } while (ch != '\0');
14877 }
14878
14879 void
14880 AppendComment(index, text, addBraces)
14881      int index;
14882      char *text;
14883      Boolean addBraces; // [HGM] braces: tells if we should add {}
14884 {
14885     int oldlen, len;
14886     char *old;
14887
14888 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14889     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14890
14891     CrushCRs(text);
14892     while (*text == '\n') text++;
14893     len = strlen(text);
14894     while (len > 0 && text[len - 1] == '\n') len--;
14895
14896     if (len == 0) return;
14897
14898     if (commentList[index] != NULL) {
14899       Boolean addClosingBrace = addBraces;
14900         old = commentList[index];
14901         oldlen = strlen(old);
14902         while(commentList[index][oldlen-1] ==  '\n')
14903           commentList[index][--oldlen] = NULLCHAR;
14904         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14905         safeStrCpy(commentList[index], old, oldlen + len + 6);
14906         free(old);
14907         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14908         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14909           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14910           while (*text == '\n') { text++; len--; }
14911           commentList[index][--oldlen] = NULLCHAR;
14912       }
14913         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14914         else          strcat(commentList[index], "\n");
14915         strcat(commentList[index], text);
14916         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14917         else          strcat(commentList[index], "\n");
14918     } else {
14919         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14920         if(addBraces)
14921           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14922         else commentList[index][0] = NULLCHAR;
14923         strcat(commentList[index], text);
14924         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14925         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14926     }
14927 }
14928
14929 static char * FindStr( char * text, char * sub_text )
14930 {
14931     char * result = strstr( text, sub_text );
14932
14933     if( result != NULL ) {
14934         result += strlen( sub_text );
14935     }
14936
14937     return result;
14938 }
14939
14940 /* [AS] Try to extract PV info from PGN comment */
14941 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14942 char *GetInfoFromComment( int index, char * text )
14943 {
14944     char * sep = text, *p;
14945
14946     if( text != NULL && index > 0 ) {
14947         int score = 0;
14948         int depth = 0;
14949         int time = -1, sec = 0, deci;
14950         char * s_eval = FindStr( text, "[%eval " );
14951         char * s_emt = FindStr( text, "[%emt " );
14952
14953         if( s_eval != NULL || s_emt != NULL ) {
14954             /* New style */
14955             char delim;
14956
14957             if( s_eval != NULL ) {
14958                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14959                     return text;
14960                 }
14961
14962                 if( delim != ']' ) {
14963                     return text;
14964                 }
14965             }
14966
14967             if( s_emt != NULL ) {
14968             }
14969                 return text;
14970         }
14971         else {
14972             /* We expect something like: [+|-]nnn.nn/dd */
14973             int score_lo = 0;
14974
14975             if(*text != '{') return text; // [HGM] braces: must be normal comment
14976
14977             sep = strchr( text, '/' );
14978             if( sep == NULL || sep < (text+4) ) {
14979                 return text;
14980             }
14981
14982             p = text;
14983             if(p[1] == '(') { // comment starts with PV
14984                p = strchr(p, ')'); // locate end of PV
14985                if(p == NULL || sep < p+5) return text;
14986                // at this point we have something like "{(.*) +0.23/6 ..."
14987                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14988                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14989                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14990             }
14991             time = -1; sec = -1; deci = -1;
14992             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14993                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14994                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14995                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14996                 return text;
14997             }
14998
14999             if( score_lo < 0 || score_lo >= 100 ) {
15000                 return text;
15001             }
15002
15003             if(sec >= 0) time = 600*time + 10*sec; else
15004             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15005
15006             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15007
15008             /* [HGM] PV time: now locate end of PV info */
15009             while( *++sep >= '0' && *sep <= '9'); // strip depth
15010             if(time >= 0)
15011             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15012             if(sec >= 0)
15013             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15014             if(deci >= 0)
15015             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15016             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15017         }
15018
15019         if( depth <= 0 ) {
15020             return text;
15021         }
15022
15023         if( time < 0 ) {
15024             time = -1;
15025         }
15026
15027         pvInfoList[index-1].depth = depth;
15028         pvInfoList[index-1].score = score;
15029         pvInfoList[index-1].time  = 10*time; // centi-sec
15030         if(*sep == '}') *sep = 0; else *--sep = '{';
15031         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15032     }
15033     return sep;
15034 }
15035
15036 void
15037 SendToProgram(message, cps)
15038      char *message;
15039      ChessProgramState *cps;
15040 {
15041     int count, outCount, error;
15042     char buf[MSG_SIZ];
15043
15044     if (cps->pr == NoProc) return;
15045     Attention(cps);
15046
15047     if (appData.debugMode) {
15048         TimeMark now;
15049         GetTimeMark(&now);
15050         fprintf(debugFP, "%ld >%-6s: %s",
15051                 SubtractTimeMarks(&now, &programStartTime),
15052                 cps->which, message);
15053     }
15054
15055     count = strlen(message);
15056     outCount = OutputToProcess(cps->pr, message, count, &error);
15057     if (outCount < count && !exiting
15058                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15059       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15060       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15061         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15062             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15063                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15064                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15065                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15066             } else {
15067                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15068                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15069                 gameInfo.result = res;
15070             }
15071             gameInfo.resultDetails = StrSave(buf);
15072         }
15073         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15074         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15075     }
15076 }
15077
15078 void
15079 ReceiveFromProgram(isr, closure, message, count, error)
15080      InputSourceRef isr;
15081      VOIDSTAR closure;
15082      char *message;
15083      int count;
15084      int error;
15085 {
15086     char *end_str;
15087     char buf[MSG_SIZ];
15088     ChessProgramState *cps = (ChessProgramState *)closure;
15089
15090     if (isr != cps->isr) return; /* Killed intentionally */
15091     if (count <= 0) {
15092         if (count == 0) {
15093             RemoveInputSource(cps->isr);
15094             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15095             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15096                     _(cps->which), cps->program);
15097         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15098                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15099                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15100                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15101                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15102                 } else {
15103                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15104                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15105                     gameInfo.result = res;
15106                 }
15107                 gameInfo.resultDetails = StrSave(buf);
15108             }
15109             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15110             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15111         } else {
15112             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15113                     _(cps->which), cps->program);
15114             RemoveInputSource(cps->isr);
15115
15116             /* [AS] Program is misbehaving badly... kill it */
15117             if( count == -2 ) {
15118                 DestroyChildProcess( cps->pr, 9 );
15119                 cps->pr = NoProc;
15120             }
15121
15122             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15123         }
15124         return;
15125     }
15126
15127     if ((end_str = strchr(message, '\r')) != NULL)
15128       *end_str = NULLCHAR;
15129     if ((end_str = strchr(message, '\n')) != NULL)
15130       *end_str = NULLCHAR;
15131
15132     if (appData.debugMode) {
15133         TimeMark now; int print = 1;
15134         char *quote = ""; char c; int i;
15135
15136         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15137                 char start = message[0];
15138                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15139                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15140                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15141                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15142                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15143                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15144                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15145                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15146                    sscanf(message, "hint: %c", &c)!=1 && 
15147                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15148                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15149                     print = (appData.engineComments >= 2);
15150                 }
15151                 message[0] = start; // restore original message
15152         }
15153         if(print) {
15154                 GetTimeMark(&now);
15155                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15156                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15157                         quote,
15158                         message);
15159         }
15160     }
15161
15162     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15163     if (appData.icsEngineAnalyze) {
15164         if (strstr(message, "whisper") != NULL ||
15165              strstr(message, "kibitz") != NULL ||
15166             strstr(message, "tellics") != NULL) return;
15167     }
15168
15169     HandleMachineMove(message, cps);
15170 }
15171
15172
15173 void
15174 SendTimeControl(cps, mps, tc, inc, sd, st)
15175      ChessProgramState *cps;
15176      int mps, inc, sd, st;
15177      long tc;
15178 {
15179     char buf[MSG_SIZ];
15180     int seconds;
15181
15182     if( timeControl_2 > 0 ) {
15183         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15184             tc = timeControl_2;
15185         }
15186     }
15187     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15188     inc /= cps->timeOdds;
15189     st  /= cps->timeOdds;
15190
15191     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15192
15193     if (st > 0) {
15194       /* Set exact time per move, normally using st command */
15195       if (cps->stKludge) {
15196         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15197         seconds = st % 60;
15198         if (seconds == 0) {
15199           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15200         } else {
15201           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15202         }
15203       } else {
15204         snprintf(buf, MSG_SIZ, "st %d\n", st);
15205       }
15206     } else {
15207       /* Set conventional or incremental time control, using level command */
15208       if (seconds == 0) {
15209         /* Note old gnuchess bug -- minutes:seconds used to not work.
15210            Fixed in later versions, but still avoid :seconds
15211            when seconds is 0. */
15212         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15213       } else {
15214         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15215                  seconds, inc/1000.);
15216       }
15217     }
15218     SendToProgram(buf, cps);
15219
15220     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15221     /* Orthogonally, limit search to given depth */
15222     if (sd > 0) {
15223       if (cps->sdKludge) {
15224         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15225       } else {
15226         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15227       }
15228       SendToProgram(buf, cps);
15229     }
15230
15231     if(cps->nps >= 0) { /* [HGM] nps */
15232         if(cps->supportsNPS == FALSE)
15233           cps->nps = -1; // don't use if engine explicitly says not supported!
15234         else {
15235           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15236           SendToProgram(buf, cps);
15237         }
15238     }
15239 }
15240
15241 ChessProgramState *WhitePlayer()
15242 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15243 {
15244     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15245        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15246         return &second;
15247     return &first;
15248 }
15249
15250 void
15251 SendTimeRemaining(cps, machineWhite)
15252      ChessProgramState *cps;
15253      int /*boolean*/ machineWhite;
15254 {
15255     char message[MSG_SIZ];
15256     long time, otime;
15257
15258     /* Note: this routine must be called when the clocks are stopped
15259        or when they have *just* been set or switched; otherwise
15260        it will be off by the time since the current tick started.
15261     */
15262     if (machineWhite) {
15263         time = whiteTimeRemaining / 10;
15264         otime = blackTimeRemaining / 10;
15265     } else {
15266         time = blackTimeRemaining / 10;
15267         otime = whiteTimeRemaining / 10;
15268     }
15269     /* [HGM] translate opponent's time by time-odds factor */
15270     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15271     if (appData.debugMode) {
15272         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15273     }
15274
15275     if (time <= 0) time = 1;
15276     if (otime <= 0) otime = 1;
15277
15278     snprintf(message, MSG_SIZ, "time %ld\n", time);
15279     SendToProgram(message, cps);
15280
15281     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15282     SendToProgram(message, cps);
15283 }
15284
15285 int
15286 BoolFeature(p, name, loc, cps)
15287      char **p;
15288      char *name;
15289      int *loc;
15290      ChessProgramState *cps;
15291 {
15292   char buf[MSG_SIZ];
15293   int len = strlen(name);
15294   int val;
15295
15296   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15297     (*p) += len + 1;
15298     sscanf(*p, "%d", &val);
15299     *loc = (val != 0);
15300     while (**p && **p != ' ')
15301       (*p)++;
15302     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15303     SendToProgram(buf, cps);
15304     return TRUE;
15305   }
15306   return FALSE;
15307 }
15308
15309 int
15310 IntFeature(p, name, loc, cps)
15311      char **p;
15312      char *name;
15313      int *loc;
15314      ChessProgramState *cps;
15315 {
15316   char buf[MSG_SIZ];
15317   int len = strlen(name);
15318   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15319     (*p) += len + 1;
15320     sscanf(*p, "%d", loc);
15321     while (**p && **p != ' ') (*p)++;
15322     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15323     SendToProgram(buf, cps);
15324     return TRUE;
15325   }
15326   return FALSE;
15327 }
15328
15329 int
15330 StringFeature(p, name, loc, cps)
15331      char **p;
15332      char *name;
15333      char loc[];
15334      ChessProgramState *cps;
15335 {
15336   char buf[MSG_SIZ];
15337   int len = strlen(name);
15338   if (strncmp((*p), name, len) == 0
15339       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15340     (*p) += len + 2;
15341     sscanf(*p, "%[^\"]", loc);
15342     while (**p && **p != '\"') (*p)++;
15343     if (**p == '\"') (*p)++;
15344     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15345     SendToProgram(buf, cps);
15346     return TRUE;
15347   }
15348   return FALSE;
15349 }
15350
15351 int
15352 ParseOption(Option *opt, ChessProgramState *cps)
15353 // [HGM] options: process the string that defines an engine option, and determine
15354 // name, type, default value, and allowed value range
15355 {
15356         char *p, *q, buf[MSG_SIZ];
15357         int n, min = (-1)<<31, max = 1<<31, def;
15358
15359         if(p = strstr(opt->name, " -spin ")) {
15360             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15361             if(max < min) max = min; // enforce consistency
15362             if(def < min) def = min;
15363             if(def > max) def = max;
15364             opt->value = def;
15365             opt->min = min;
15366             opt->max = max;
15367             opt->type = Spin;
15368         } else if((p = strstr(opt->name, " -slider "))) {
15369             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15370             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15371             if(max < min) max = min; // enforce consistency
15372             if(def < min) def = min;
15373             if(def > max) def = max;
15374             opt->value = def;
15375             opt->min = min;
15376             opt->max = max;
15377             opt->type = Spin; // Slider;
15378         } else if((p = strstr(opt->name, " -string "))) {
15379             opt->textValue = p+9;
15380             opt->type = TextBox;
15381         } else if((p = strstr(opt->name, " -file "))) {
15382             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15383             opt->textValue = p+7;
15384             opt->type = FileName; // FileName;
15385         } else if((p = strstr(opt->name, " -path "))) {
15386             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15387             opt->textValue = p+7;
15388             opt->type = PathName; // PathName;
15389         } else if(p = strstr(opt->name, " -check ")) {
15390             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15391             opt->value = (def != 0);
15392             opt->type = CheckBox;
15393         } else if(p = strstr(opt->name, " -combo ")) {
15394             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15395             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15396             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15397             opt->value = n = 0;
15398             while(q = StrStr(q, " /// ")) {
15399                 n++; *q = 0;    // count choices, and null-terminate each of them
15400                 q += 5;
15401                 if(*q == '*') { // remember default, which is marked with * prefix
15402                     q++;
15403                     opt->value = n;
15404                 }
15405                 cps->comboList[cps->comboCnt++] = q;
15406             }
15407             cps->comboList[cps->comboCnt++] = NULL;
15408             opt->max = n + 1;
15409             opt->type = ComboBox;
15410         } else if(p = strstr(opt->name, " -button")) {
15411             opt->type = Button;
15412         } else if(p = strstr(opt->name, " -save")) {
15413             opt->type = SaveButton;
15414         } else return FALSE;
15415         *p = 0; // terminate option name
15416         // now look if the command-line options define a setting for this engine option.
15417         if(cps->optionSettings && cps->optionSettings[0])
15418             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15419         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15420           snprintf(buf, MSG_SIZ, "option %s", p);
15421                 if(p = strstr(buf, ",")) *p = 0;
15422                 if(q = strchr(buf, '=')) switch(opt->type) {
15423                     case ComboBox:
15424                         for(n=0; n<opt->max; n++)
15425                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15426                         break;
15427                     case TextBox:
15428                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15429                         break;
15430                     case Spin:
15431                     case CheckBox:
15432                         opt->value = atoi(q+1);
15433                     default:
15434                         break;
15435                 }
15436                 strcat(buf, "\n");
15437                 SendToProgram(buf, cps);
15438         }
15439         return TRUE;
15440 }
15441
15442 void
15443 FeatureDone(cps, val)
15444      ChessProgramState* cps;
15445      int val;
15446 {
15447   DelayedEventCallback cb = GetDelayedEvent();
15448   if ((cb == InitBackEnd3 && cps == &first) ||
15449       (cb == SettingsMenuIfReady && cps == &second) ||
15450       (cb == LoadEngine) ||
15451       (cb == TwoMachinesEventIfReady)) {
15452     CancelDelayedEvent();
15453     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15454   }
15455   cps->initDone = val;
15456 }
15457
15458 /* Parse feature command from engine */
15459 void
15460 ParseFeatures(args, cps)
15461      char* args;
15462      ChessProgramState *cps;
15463 {
15464   char *p = args;
15465   char *q;
15466   int val;
15467   char buf[MSG_SIZ];
15468
15469   for (;;) {
15470     while (*p == ' ') p++;
15471     if (*p == NULLCHAR) return;
15472
15473     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15474     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15475     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15476     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15477     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15478     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15479     if (BoolFeature(&p, "reuse", &val, cps)) {
15480       /* Engine can disable reuse, but can't enable it if user said no */
15481       if (!val) cps->reuse = FALSE;
15482       continue;
15483     }
15484     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15485     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15486       if (gameMode == TwoMachinesPlay) {
15487         DisplayTwoMachinesTitle();
15488       } else {
15489         DisplayTitle("");
15490       }
15491       continue;
15492     }
15493     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15494     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15495     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15496     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15497     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15498     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15499     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15500     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15501     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15502     if (IntFeature(&p, "done", &val, cps)) {
15503       FeatureDone(cps, val);
15504       continue;
15505     }
15506     /* Added by Tord: */
15507     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15508     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15509     /* End of additions by Tord */
15510
15511     /* [HGM] added features: */
15512     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15513     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15514     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15515     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15516     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15517     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15518     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15519         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15520           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15521             SendToProgram(buf, cps);
15522             continue;
15523         }
15524         if(cps->nrOptions >= MAX_OPTIONS) {
15525             cps->nrOptions--;
15526             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15527             DisplayError(buf, 0);
15528         }
15529         continue;
15530     }
15531     /* End of additions by HGM */
15532
15533     /* unknown feature: complain and skip */
15534     q = p;
15535     while (*q && *q != '=') q++;
15536     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15537     SendToProgram(buf, cps);
15538     p = q;
15539     if (*p == '=') {
15540       p++;
15541       if (*p == '\"') {
15542         p++;
15543         while (*p && *p != '\"') p++;
15544         if (*p == '\"') p++;
15545       } else {
15546         while (*p && *p != ' ') p++;
15547       }
15548     }
15549   }
15550
15551 }
15552
15553 void
15554 PeriodicUpdatesEvent(newState)
15555      int newState;
15556 {
15557     if (newState == appData.periodicUpdates)
15558       return;
15559
15560     appData.periodicUpdates=newState;
15561
15562     /* Display type changes, so update it now */
15563 //    DisplayAnalysis();
15564
15565     /* Get the ball rolling again... */
15566     if (newState) {
15567         AnalysisPeriodicEvent(1);
15568         StartAnalysisClock();
15569     }
15570 }
15571
15572 void
15573 PonderNextMoveEvent(newState)
15574      int newState;
15575 {
15576     if (newState == appData.ponderNextMove) return;
15577     if (gameMode == EditPosition) EditPositionDone(TRUE);
15578     if (newState) {
15579         SendToProgram("hard\n", &first);
15580         if (gameMode == TwoMachinesPlay) {
15581             SendToProgram("hard\n", &second);
15582         }
15583     } else {
15584         SendToProgram("easy\n", &first);
15585         thinkOutput[0] = NULLCHAR;
15586         if (gameMode == TwoMachinesPlay) {
15587             SendToProgram("easy\n", &second);
15588         }
15589     }
15590     appData.ponderNextMove = newState;
15591 }
15592
15593 void
15594 NewSettingEvent(option, feature, command, value)
15595      char *command;
15596      int option, value, *feature;
15597 {
15598     char buf[MSG_SIZ];
15599
15600     if (gameMode == EditPosition) EditPositionDone(TRUE);
15601     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15602     if(feature == NULL || *feature) SendToProgram(buf, &first);
15603     if (gameMode == TwoMachinesPlay) {
15604         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15605     }
15606 }
15607
15608 void
15609 ShowThinkingEvent()
15610 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15611 {
15612     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15613     int newState = appData.showThinking
15614         // [HGM] thinking: other features now need thinking output as well
15615         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15616
15617     if (oldState == newState) return;
15618     oldState = newState;
15619     if (gameMode == EditPosition) EditPositionDone(TRUE);
15620     if (oldState) {
15621         SendToProgram("post\n", &first);
15622         if (gameMode == TwoMachinesPlay) {
15623             SendToProgram("post\n", &second);
15624         }
15625     } else {
15626         SendToProgram("nopost\n", &first);
15627         thinkOutput[0] = NULLCHAR;
15628         if (gameMode == TwoMachinesPlay) {
15629             SendToProgram("nopost\n", &second);
15630         }
15631     }
15632 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15633 }
15634
15635 void
15636 AskQuestionEvent(title, question, replyPrefix, which)
15637      char *title; char *question; char *replyPrefix; char *which;
15638 {
15639   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15640   if (pr == NoProc) return;
15641   AskQuestion(title, question, replyPrefix, pr);
15642 }
15643
15644 void
15645 TypeInEvent(char firstChar)
15646 {
15647     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15648         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15649         gameMode == AnalyzeMode || gameMode == EditGame || 
15650         gameMode == EditPosition || gameMode == IcsExamining ||
15651         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15652         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15653                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15654                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15655         gameMode == Training) PopUpMoveDialog(firstChar);
15656 }
15657
15658 void
15659 TypeInDoneEvent(char *move)
15660 {
15661         Board board;
15662         int n, fromX, fromY, toX, toY;
15663         char promoChar;
15664         ChessMove moveType;
15665
15666         // [HGM] FENedit
15667         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15668                 EditPositionPasteFEN(move);
15669                 return;
15670         }
15671         // [HGM] movenum: allow move number to be typed in any mode
15672         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15673           ToNrEvent(2*n-1);
15674           return;
15675         }
15676
15677       if (gameMode != EditGame && currentMove != forwardMostMove && 
15678         gameMode != Training) {
15679         DisplayMoveError(_("Displayed move is not current"));
15680       } else {
15681         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15682           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15683         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15684         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15685           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15686           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15687         } else {
15688           DisplayMoveError(_("Could not parse move"));
15689         }
15690       }
15691 }
15692
15693 void
15694 DisplayMove(moveNumber)
15695      int moveNumber;
15696 {
15697     char message[MSG_SIZ];
15698     char res[MSG_SIZ];
15699     char cpThinkOutput[MSG_SIZ];
15700
15701     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15702
15703     if (moveNumber == forwardMostMove - 1 ||
15704         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15705
15706         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15707
15708         if (strchr(cpThinkOutput, '\n')) {
15709             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15710         }
15711     } else {
15712         *cpThinkOutput = NULLCHAR;
15713     }
15714
15715     /* [AS] Hide thinking from human user */
15716     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15717         *cpThinkOutput = NULLCHAR;
15718         if( thinkOutput[0] != NULLCHAR ) {
15719             int i;
15720
15721             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15722                 cpThinkOutput[i] = '.';
15723             }
15724             cpThinkOutput[i] = NULLCHAR;
15725             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15726         }
15727     }
15728
15729     if (moveNumber == forwardMostMove - 1 &&
15730         gameInfo.resultDetails != NULL) {
15731         if (gameInfo.resultDetails[0] == NULLCHAR) {
15732           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15733         } else {
15734           snprintf(res, MSG_SIZ, " {%s} %s",
15735                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15736         }
15737     } else {
15738         res[0] = NULLCHAR;
15739     }
15740
15741     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15742         DisplayMessage(res, cpThinkOutput);
15743     } else {
15744       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15745                 WhiteOnMove(moveNumber) ? " " : ".. ",
15746                 parseList[moveNumber], res);
15747         DisplayMessage(message, cpThinkOutput);
15748     }
15749 }
15750
15751 void
15752 DisplayComment(moveNumber, text)
15753      int moveNumber;
15754      char *text;
15755 {
15756     char title[MSG_SIZ];
15757
15758     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15759       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15760     } else {
15761       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15762               WhiteOnMove(moveNumber) ? " " : ".. ",
15763               parseList[moveNumber]);
15764     }
15765     if (text != NULL && (appData.autoDisplayComment || commentUp))
15766         CommentPopUp(title, text);
15767 }
15768
15769 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15770  * might be busy thinking or pondering.  It can be omitted if your
15771  * gnuchess is configured to stop thinking immediately on any user
15772  * input.  However, that gnuchess feature depends on the FIONREAD
15773  * ioctl, which does not work properly on some flavors of Unix.
15774  */
15775 void
15776 Attention(cps)
15777      ChessProgramState *cps;
15778 {
15779 #if ATTENTION
15780     if (!cps->useSigint) return;
15781     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15782     switch (gameMode) {
15783       case MachinePlaysWhite:
15784       case MachinePlaysBlack:
15785       case TwoMachinesPlay:
15786       case IcsPlayingWhite:
15787       case IcsPlayingBlack:
15788       case AnalyzeMode:
15789       case AnalyzeFile:
15790         /* Skip if we know it isn't thinking */
15791         if (!cps->maybeThinking) return;
15792         if (appData.debugMode)
15793           fprintf(debugFP, "Interrupting %s\n", cps->which);
15794         InterruptChildProcess(cps->pr);
15795         cps->maybeThinking = FALSE;
15796         break;
15797       default:
15798         break;
15799     }
15800 #endif /*ATTENTION*/
15801 }
15802
15803 int
15804 CheckFlags()
15805 {
15806     if (whiteTimeRemaining <= 0) {
15807         if (!whiteFlag) {
15808             whiteFlag = TRUE;
15809             if (appData.icsActive) {
15810                 if (appData.autoCallFlag &&
15811                     gameMode == IcsPlayingBlack && !blackFlag) {
15812                   SendToICS(ics_prefix);
15813                   SendToICS("flag\n");
15814                 }
15815             } else {
15816                 if (blackFlag) {
15817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15818                 } else {
15819                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15820                     if (appData.autoCallFlag) {
15821                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15822                         return TRUE;
15823                     }
15824                 }
15825             }
15826         }
15827     }
15828     if (blackTimeRemaining <= 0) {
15829         if (!blackFlag) {
15830             blackFlag = TRUE;
15831             if (appData.icsActive) {
15832                 if (appData.autoCallFlag &&
15833                     gameMode == IcsPlayingWhite && !whiteFlag) {
15834                   SendToICS(ics_prefix);
15835                   SendToICS("flag\n");
15836                 }
15837             } else {
15838                 if (whiteFlag) {
15839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15840                 } else {
15841                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15842                     if (appData.autoCallFlag) {
15843                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15844                         return TRUE;
15845                     }
15846                 }
15847             }
15848         }
15849     }
15850     return FALSE;
15851 }
15852
15853 void
15854 CheckTimeControl()
15855 {
15856     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15857         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15858
15859     /*
15860      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15861      */
15862     if ( !WhiteOnMove(forwardMostMove) ) {
15863         /* White made time control */
15864         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15865         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15866         /* [HGM] time odds: correct new time quota for time odds! */
15867                                             / WhitePlayer()->timeOdds;
15868         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15869     } else {
15870         lastBlack -= blackTimeRemaining;
15871         /* Black made time control */
15872         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15873                                             / WhitePlayer()->other->timeOdds;
15874         lastWhite = whiteTimeRemaining;
15875     }
15876 }
15877
15878 void
15879 DisplayBothClocks()
15880 {
15881     int wom = gameMode == EditPosition ?
15882       !blackPlaysFirst : WhiteOnMove(currentMove);
15883     DisplayWhiteClock(whiteTimeRemaining, wom);
15884     DisplayBlackClock(blackTimeRemaining, !wom);
15885 }
15886
15887
15888 /* Timekeeping seems to be a portability nightmare.  I think everyone
15889    has ftime(), but I'm really not sure, so I'm including some ifdefs
15890    to use other calls if you don't.  Clocks will be less accurate if
15891    you have neither ftime nor gettimeofday.
15892 */
15893
15894 /* VS 2008 requires the #include outside of the function */
15895 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15896 #include <sys/timeb.h>
15897 #endif
15898
15899 /* Get the current time as a TimeMark */
15900 void
15901 GetTimeMark(tm)
15902      TimeMark *tm;
15903 {
15904 #if HAVE_GETTIMEOFDAY
15905
15906     struct timeval timeVal;
15907     struct timezone timeZone;
15908
15909     gettimeofday(&timeVal, &timeZone);
15910     tm->sec = (long) timeVal.tv_sec;
15911     tm->ms = (int) (timeVal.tv_usec / 1000L);
15912
15913 #else /*!HAVE_GETTIMEOFDAY*/
15914 #if HAVE_FTIME
15915
15916 // include <sys/timeb.h> / moved to just above start of function
15917     struct timeb timeB;
15918
15919     ftime(&timeB);
15920     tm->sec = (long) timeB.time;
15921     tm->ms = (int) timeB.millitm;
15922
15923 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15924     tm->sec = (long) time(NULL);
15925     tm->ms = 0;
15926 #endif
15927 #endif
15928 }
15929
15930 /* Return the difference in milliseconds between two
15931    time marks.  We assume the difference will fit in a long!
15932 */
15933 long
15934 SubtractTimeMarks(tm2, tm1)
15935      TimeMark *tm2, *tm1;
15936 {
15937     return 1000L*(tm2->sec - tm1->sec) +
15938            (long) (tm2->ms - tm1->ms);
15939 }
15940
15941
15942 /*
15943  * Code to manage the game clocks.
15944  *
15945  * In tournament play, black starts the clock and then white makes a move.
15946  * We give the human user a slight advantage if he is playing white---the
15947  * clocks don't run until he makes his first move, so it takes zero time.
15948  * Also, we don't account for network lag, so we could get out of sync
15949  * with GNU Chess's clock -- but then, referees are always right.
15950  */
15951
15952 static TimeMark tickStartTM;
15953 static long intendedTickLength;
15954
15955 long
15956 NextTickLength(timeRemaining)
15957      long timeRemaining;
15958 {
15959     long nominalTickLength, nextTickLength;
15960
15961     if (timeRemaining > 0L && timeRemaining <= 10000L)
15962       nominalTickLength = 100L;
15963     else
15964       nominalTickLength = 1000L;
15965     nextTickLength = timeRemaining % nominalTickLength;
15966     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15967
15968     return nextTickLength;
15969 }
15970
15971 /* Adjust clock one minute up or down */
15972 void
15973 AdjustClock(Boolean which, int dir)
15974 {
15975     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15976     if(which) blackTimeRemaining += 60000*dir;
15977     else      whiteTimeRemaining += 60000*dir;
15978     DisplayBothClocks();
15979     adjustedClock = TRUE;
15980 }
15981
15982 /* Stop clocks and reset to a fresh time control */
15983 void
15984 ResetClocks()
15985 {
15986     (void) StopClockTimer();
15987     if (appData.icsActive) {
15988         whiteTimeRemaining = blackTimeRemaining = 0;
15989     } else if (searchTime) {
15990         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15991         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15992     } else { /* [HGM] correct new time quote for time odds */
15993         whiteTC = blackTC = fullTimeControlString;
15994         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15995         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15996     }
15997     if (whiteFlag || blackFlag) {
15998         DisplayTitle("");
15999         whiteFlag = blackFlag = FALSE;
16000     }
16001     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16002     DisplayBothClocks();
16003     adjustedClock = FALSE;
16004 }
16005
16006 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16007
16008 /* Decrement running clock by amount of time that has passed */
16009 void
16010 DecrementClocks()
16011 {
16012     long timeRemaining;
16013     long lastTickLength, fudge;
16014     TimeMark now;
16015
16016     if (!appData.clockMode) return;
16017     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16018
16019     GetTimeMark(&now);
16020
16021     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16022
16023     /* Fudge if we woke up a little too soon */
16024     fudge = intendedTickLength - lastTickLength;
16025     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16026
16027     if (WhiteOnMove(forwardMostMove)) {
16028         if(whiteNPS >= 0) lastTickLength = 0;
16029         timeRemaining = whiteTimeRemaining -= lastTickLength;
16030         if(timeRemaining < 0 && !appData.icsActive) {
16031             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16032             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16033                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16034                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16035             }
16036         }
16037         DisplayWhiteClock(whiteTimeRemaining - fudge,
16038                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16039     } else {
16040         if(blackNPS >= 0) lastTickLength = 0;
16041         timeRemaining = blackTimeRemaining -= lastTickLength;
16042         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16043             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16044             if(suddenDeath) {
16045                 blackStartMove = forwardMostMove;
16046                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16047             }
16048         }
16049         DisplayBlackClock(blackTimeRemaining - fudge,
16050                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16051     }
16052     if (CheckFlags()) return;
16053
16054     tickStartTM = now;
16055     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16056     StartClockTimer(intendedTickLength);
16057
16058     /* if the time remaining has fallen below the alarm threshold, sound the
16059      * alarm. if the alarm has sounded and (due to a takeback or time control
16060      * with increment) the time remaining has increased to a level above the
16061      * threshold, reset the alarm so it can sound again.
16062      */
16063
16064     if (appData.icsActive && appData.icsAlarm) {
16065
16066         /* make sure we are dealing with the user's clock */
16067         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16068                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16069            )) return;
16070
16071         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16072             alarmSounded = FALSE;
16073         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16074             PlayAlarmSound();
16075             alarmSounded = TRUE;
16076         }
16077     }
16078 }
16079
16080
16081 /* A player has just moved, so stop the previously running
16082    clock and (if in clock mode) start the other one.
16083    We redisplay both clocks in case we're in ICS mode, because
16084    ICS gives us an update to both clocks after every move.
16085    Note that this routine is called *after* forwardMostMove
16086    is updated, so the last fractional tick must be subtracted
16087    from the color that is *not* on move now.
16088 */
16089 void
16090 SwitchClocks(int newMoveNr)
16091 {
16092     long lastTickLength;
16093     TimeMark now;
16094     int flagged = FALSE;
16095
16096     GetTimeMark(&now);
16097
16098     if (StopClockTimer() && appData.clockMode) {
16099         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16100         if (!WhiteOnMove(forwardMostMove)) {
16101             if(blackNPS >= 0) lastTickLength = 0;
16102             blackTimeRemaining -= lastTickLength;
16103            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16104 //         if(pvInfoList[forwardMostMove].time == -1)
16105                  pvInfoList[forwardMostMove].time =               // use GUI time
16106                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16107         } else {
16108            if(whiteNPS >= 0) lastTickLength = 0;
16109            whiteTimeRemaining -= lastTickLength;
16110            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16111 //         if(pvInfoList[forwardMostMove].time == -1)
16112                  pvInfoList[forwardMostMove].time =
16113                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16114         }
16115         flagged = CheckFlags();
16116     }
16117     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16118     CheckTimeControl();
16119
16120     if (flagged || !appData.clockMode) return;
16121
16122     switch (gameMode) {
16123       case MachinePlaysBlack:
16124       case MachinePlaysWhite:
16125       case BeginningOfGame:
16126         if (pausing) return;
16127         break;
16128
16129       case EditGame:
16130       case PlayFromGameFile:
16131       case IcsExamining:
16132         return;
16133
16134       default:
16135         break;
16136     }
16137
16138     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16139         if(WhiteOnMove(forwardMostMove))
16140              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16141         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16142     }
16143
16144     tickStartTM = now;
16145     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16146       whiteTimeRemaining : blackTimeRemaining);
16147     StartClockTimer(intendedTickLength);
16148 }
16149
16150
16151 /* Stop both clocks */
16152 void
16153 StopClocks()
16154 {
16155     long lastTickLength;
16156     TimeMark now;
16157
16158     if (!StopClockTimer()) return;
16159     if (!appData.clockMode) return;
16160
16161     GetTimeMark(&now);
16162
16163     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16164     if (WhiteOnMove(forwardMostMove)) {
16165         if(whiteNPS >= 0) lastTickLength = 0;
16166         whiteTimeRemaining -= lastTickLength;
16167         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16168     } else {
16169         if(blackNPS >= 0) lastTickLength = 0;
16170         blackTimeRemaining -= lastTickLength;
16171         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16172     }
16173     CheckFlags();
16174 }
16175
16176 /* Start clock of player on move.  Time may have been reset, so
16177    if clock is already running, stop and restart it. */
16178 void
16179 StartClocks()
16180 {
16181     (void) StopClockTimer(); /* in case it was running already */
16182     DisplayBothClocks();
16183     if (CheckFlags()) return;
16184
16185     if (!appData.clockMode) return;
16186     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16187
16188     GetTimeMark(&tickStartTM);
16189     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16190       whiteTimeRemaining : blackTimeRemaining);
16191
16192    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16193     whiteNPS = blackNPS = -1;
16194     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16195        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16196         whiteNPS = first.nps;
16197     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16198        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16199         blackNPS = first.nps;
16200     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16201         whiteNPS = second.nps;
16202     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16203         blackNPS = second.nps;
16204     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16205
16206     StartClockTimer(intendedTickLength);
16207 }
16208
16209 char *
16210 TimeString(ms)
16211      long ms;
16212 {
16213     long second, minute, hour, day;
16214     char *sign = "";
16215     static char buf[32];
16216
16217     if (ms > 0 && ms <= 9900) {
16218       /* convert milliseconds to tenths, rounding up */
16219       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16220
16221       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16222       return buf;
16223     }
16224
16225     /* convert milliseconds to seconds, rounding up */
16226     /* use floating point to avoid strangeness of integer division
16227        with negative dividends on many machines */
16228     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16229
16230     if (second < 0) {
16231         sign = "-";
16232         second = -second;
16233     }
16234
16235     day = second / (60 * 60 * 24);
16236     second = second % (60 * 60 * 24);
16237     hour = second / (60 * 60);
16238     second = second % (60 * 60);
16239     minute = second / 60;
16240     second = second % 60;
16241
16242     if (day > 0)
16243       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16244               sign, day, hour, minute, second);
16245     else if (hour > 0)
16246       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16247     else
16248       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16249
16250     return buf;
16251 }
16252
16253
16254 /*
16255  * This is necessary because some C libraries aren't ANSI C compliant yet.
16256  */
16257 char *
16258 StrStr(string, match)
16259      char *string, *match;
16260 {
16261     int i, length;
16262
16263     length = strlen(match);
16264
16265     for (i = strlen(string) - length; i >= 0; i--, string++)
16266       if (!strncmp(match, string, length))
16267         return string;
16268
16269     return NULL;
16270 }
16271
16272 char *
16273 StrCaseStr(string, match)
16274      char *string, *match;
16275 {
16276     int i, j, length;
16277
16278     length = strlen(match);
16279
16280     for (i = strlen(string) - length; i >= 0; i--, string++) {
16281         for (j = 0; j < length; j++) {
16282             if (ToLower(match[j]) != ToLower(string[j]))
16283               break;
16284         }
16285         if (j == length) return string;
16286     }
16287
16288     return NULL;
16289 }
16290
16291 #ifndef _amigados
16292 int
16293 StrCaseCmp(s1, s2)
16294      char *s1, *s2;
16295 {
16296     char c1, c2;
16297
16298     for (;;) {
16299         c1 = ToLower(*s1++);
16300         c2 = ToLower(*s2++);
16301         if (c1 > c2) return 1;
16302         if (c1 < c2) return -1;
16303         if (c1 == NULLCHAR) return 0;
16304     }
16305 }
16306
16307
16308 int
16309 ToLower(c)
16310      int c;
16311 {
16312     return isupper(c) ? tolower(c) : c;
16313 }
16314
16315
16316 int
16317 ToUpper(c)
16318      int c;
16319 {
16320     return islower(c) ? toupper(c) : c;
16321 }
16322 #endif /* !_amigados    */
16323
16324 char *
16325 StrSave(s)
16326      char *s;
16327 {
16328   char *ret;
16329
16330   if ((ret = (char *) malloc(strlen(s) + 1)))
16331     {
16332       safeStrCpy(ret, s, strlen(s)+1);
16333     }
16334   return ret;
16335 }
16336
16337 char *
16338 StrSavePtr(s, savePtr)
16339      char *s, **savePtr;
16340 {
16341     if (*savePtr) {
16342         free(*savePtr);
16343     }
16344     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16345       safeStrCpy(*savePtr, s, strlen(s)+1);
16346     }
16347     return(*savePtr);
16348 }
16349
16350 char *
16351 PGNDate()
16352 {
16353     time_t clock;
16354     struct tm *tm;
16355     char buf[MSG_SIZ];
16356
16357     clock = time((time_t *)NULL);
16358     tm = localtime(&clock);
16359     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16360             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16361     return StrSave(buf);
16362 }
16363
16364
16365 char *
16366 PositionToFEN(move, overrideCastling)
16367      int move;
16368      char *overrideCastling;
16369 {
16370     int i, j, fromX, fromY, toX, toY;
16371     int whiteToPlay;
16372     char buf[MSG_SIZ];
16373     char *p, *q;
16374     int emptycount;
16375     ChessSquare piece;
16376
16377     whiteToPlay = (gameMode == EditPosition) ?
16378       !blackPlaysFirst : (move % 2 == 0);
16379     p = buf;
16380
16381     /* Piece placement data */
16382     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16383         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16384         emptycount = 0;
16385         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16386             if (boards[move][i][j] == EmptySquare) {
16387                 emptycount++;
16388             } else { ChessSquare piece = boards[move][i][j];
16389                 if (emptycount > 0) {
16390                     if(emptycount<10) /* [HGM] can be >= 10 */
16391                         *p++ = '0' + emptycount;
16392                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16393                     emptycount = 0;
16394                 }
16395                 if(PieceToChar(piece) == '+') {
16396                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16397                     *p++ = '+';
16398                     piece = (ChessSquare)(DEMOTED piece);
16399                 }
16400                 *p++ = PieceToChar(piece);
16401                 if(p[-1] == '~') {
16402                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16403                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16404                     *p++ = '~';
16405                 }
16406             }
16407         }
16408         if (emptycount > 0) {
16409             if(emptycount<10) /* [HGM] can be >= 10 */
16410                 *p++ = '0' + emptycount;
16411             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16412             emptycount = 0;
16413         }
16414         *p++ = '/';
16415     }
16416     *(p - 1) = ' ';
16417
16418     /* [HGM] print Crazyhouse or Shogi holdings */
16419     if( gameInfo.holdingsWidth ) {
16420         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16421         q = p;
16422         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16423             piece = boards[move][i][BOARD_WIDTH-1];
16424             if( piece != EmptySquare )
16425               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16426                   *p++ = PieceToChar(piece);
16427         }
16428         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16429             piece = boards[move][BOARD_HEIGHT-i-1][0];
16430             if( piece != EmptySquare )
16431               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16432                   *p++ = PieceToChar(piece);
16433         }
16434
16435         if( q == p ) *p++ = '-';
16436         *p++ = ']';
16437         *p++ = ' ';
16438     }
16439
16440     /* Active color */
16441     *p++ = whiteToPlay ? 'w' : 'b';
16442     *p++ = ' ';
16443
16444   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16445     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16446   } else {
16447   if(nrCastlingRights) {
16448      q = p;
16449      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16450        /* [HGM] write directly from rights */
16451            if(boards[move][CASTLING][2] != NoRights &&
16452               boards[move][CASTLING][0] != NoRights   )
16453                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16454            if(boards[move][CASTLING][2] != NoRights &&
16455               boards[move][CASTLING][1] != NoRights   )
16456                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16457            if(boards[move][CASTLING][5] != NoRights &&
16458               boards[move][CASTLING][3] != NoRights   )
16459                 *p++ = boards[move][CASTLING][3] + AAA;
16460            if(boards[move][CASTLING][5] != NoRights &&
16461               boards[move][CASTLING][4] != NoRights   )
16462                 *p++ = boards[move][CASTLING][4] + AAA;
16463      } else {
16464
16465         /* [HGM] write true castling rights */
16466         if( nrCastlingRights == 6 ) {
16467             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16468                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16469             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16470                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16471             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16472                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16473             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16474                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16475         }
16476      }
16477      if (q == p) *p++ = '-'; /* No castling rights */
16478      *p++ = ' ';
16479   }
16480
16481   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16482      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16483     /* En passant target square */
16484     if (move > backwardMostMove) {
16485         fromX = moveList[move - 1][0] - AAA;
16486         fromY = moveList[move - 1][1] - ONE;
16487         toX = moveList[move - 1][2] - AAA;
16488         toY = moveList[move - 1][3] - ONE;
16489         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16490             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16491             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16492             fromX == toX) {
16493             /* 2-square pawn move just happened */
16494             *p++ = toX + AAA;
16495             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16496         } else {
16497             *p++ = '-';
16498         }
16499     } else if(move == backwardMostMove) {
16500         // [HGM] perhaps we should always do it like this, and forget the above?
16501         if((signed char)boards[move][EP_STATUS] >= 0) {
16502             *p++ = boards[move][EP_STATUS] + AAA;
16503             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16504         } else {
16505             *p++ = '-';
16506         }
16507     } else {
16508         *p++ = '-';
16509     }
16510     *p++ = ' ';
16511   }
16512   }
16513
16514     /* [HGM] find reversible plies */
16515     {   int i = 0, j=move;
16516
16517         if (appData.debugMode) { int k;
16518             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16519             for(k=backwardMostMove; k<=forwardMostMove; k++)
16520                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16521
16522         }
16523
16524         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16525         if( j == backwardMostMove ) i += initialRulePlies;
16526         sprintf(p, "%d ", i);
16527         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16528     }
16529     /* Fullmove number */
16530     sprintf(p, "%d", (move / 2) + 1);
16531
16532     return StrSave(buf);
16533 }
16534
16535 Boolean
16536 ParseFEN(board, blackPlaysFirst, fen)
16537     Board board;
16538      int *blackPlaysFirst;
16539      char *fen;
16540 {
16541     int i, j;
16542     char *p, c;
16543     int emptycount;
16544     ChessSquare piece;
16545
16546     p = fen;
16547
16548     /* [HGM] by default clear Crazyhouse holdings, if present */
16549     if(gameInfo.holdingsWidth) {
16550        for(i=0; i<BOARD_HEIGHT; i++) {
16551            board[i][0]             = EmptySquare; /* black holdings */
16552            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16553            board[i][1]             = (ChessSquare) 0; /* black counts */
16554            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16555        }
16556     }
16557
16558     /* Piece placement data */
16559     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16560         j = 0;
16561         for (;;) {
16562             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16563                 if (*p == '/') p++;
16564                 emptycount = gameInfo.boardWidth - j;
16565                 while (emptycount--)
16566                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16567                 break;
16568 #if(BOARD_FILES >= 10)
16569             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16570                 p++; emptycount=10;
16571                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16572                 while (emptycount--)
16573                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16574 #endif
16575             } else if (isdigit(*p)) {
16576                 emptycount = *p++ - '0';
16577                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16578                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16579                 while (emptycount--)
16580                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16581             } else if (*p == '+' || isalpha(*p)) {
16582                 if (j >= gameInfo.boardWidth) return FALSE;
16583                 if(*p=='+') {
16584                     piece = CharToPiece(*++p);
16585                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16586                     piece = (ChessSquare) (PROMOTED piece ); p++;
16587                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16588                 } else piece = CharToPiece(*p++);
16589
16590                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16591                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16592                     piece = (ChessSquare) (PROMOTED piece);
16593                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16594                     p++;
16595                 }
16596                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16597             } else {
16598                 return FALSE;
16599             }
16600         }
16601     }
16602     while (*p == '/' || *p == ' ') p++;
16603
16604     /* [HGM] look for Crazyhouse holdings here */
16605     while(*p==' ') p++;
16606     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16607         if(*p == '[') p++;
16608         if(*p == '-' ) p++; /* empty holdings */ else {
16609             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16610             /* if we would allow FEN reading to set board size, we would   */
16611             /* have to add holdings and shift the board read so far here   */
16612             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16613                 p++;
16614                 if((int) piece >= (int) BlackPawn ) {
16615                     i = (int)piece - (int)BlackPawn;
16616                     i = PieceToNumber((ChessSquare)i);
16617                     if( i >= gameInfo.holdingsSize ) return FALSE;
16618                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16619                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16620                 } else {
16621                     i = (int)piece - (int)WhitePawn;
16622                     i = PieceToNumber((ChessSquare)i);
16623                     if( i >= gameInfo.holdingsSize ) return FALSE;
16624                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16625                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16626                 }
16627             }
16628         }
16629         if(*p == ']') p++;
16630     }
16631
16632     while(*p == ' ') p++;
16633
16634     /* Active color */
16635     c = *p++;
16636     if(appData.colorNickNames) {
16637       if( c == appData.colorNickNames[0] ) c = 'w'; else
16638       if( c == appData.colorNickNames[1] ) c = 'b';
16639     }
16640     switch (c) {
16641       case 'w':
16642         *blackPlaysFirst = FALSE;
16643         break;
16644       case 'b':
16645         *blackPlaysFirst = TRUE;
16646         break;
16647       default:
16648         return FALSE;
16649     }
16650
16651     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16652     /* return the extra info in global variiables             */
16653
16654     /* set defaults in case FEN is incomplete */
16655     board[EP_STATUS] = EP_UNKNOWN;
16656     for(i=0; i<nrCastlingRights; i++ ) {
16657         board[CASTLING][i] =
16658             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16659     }   /* assume possible unless obviously impossible */
16660     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16661     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16662     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16663                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16664     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16665     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16666     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16667                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16668     FENrulePlies = 0;
16669
16670     while(*p==' ') p++;
16671     if(nrCastlingRights) {
16672       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16673           /* castling indicator present, so default becomes no castlings */
16674           for(i=0; i<nrCastlingRights; i++ ) {
16675                  board[CASTLING][i] = NoRights;
16676           }
16677       }
16678       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16679              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16680              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16681              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16682         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16683
16684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16685             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16686             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16687         }
16688         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16689             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16690         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16691                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16692         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16693                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16694         switch(c) {
16695           case'K':
16696               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16697               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16698               board[CASTLING][2] = whiteKingFile;
16699               break;
16700           case'Q':
16701               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16702               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16703               board[CASTLING][2] = whiteKingFile;
16704               break;
16705           case'k':
16706               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16707               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16708               board[CASTLING][5] = blackKingFile;
16709               break;
16710           case'q':
16711               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16712               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16713               board[CASTLING][5] = blackKingFile;
16714           case '-':
16715               break;
16716           default: /* FRC castlings */
16717               if(c >= 'a') { /* black rights */
16718                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16719                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16720                   if(i == BOARD_RGHT) break;
16721                   board[CASTLING][5] = i;
16722                   c -= AAA;
16723                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16724                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16725                   if(c > i)
16726                       board[CASTLING][3] = c;
16727                   else
16728                       board[CASTLING][4] = c;
16729               } else { /* white rights */
16730                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16731                     if(board[0][i] == WhiteKing) break;
16732                   if(i == BOARD_RGHT) break;
16733                   board[CASTLING][2] = i;
16734                   c -= AAA - 'a' + 'A';
16735                   if(board[0][c] >= WhiteKing) break;
16736                   if(c > i)
16737                       board[CASTLING][0] = c;
16738                   else
16739                       board[CASTLING][1] = c;
16740               }
16741         }
16742       }
16743       for(i=0; i<nrCastlingRights; i++)
16744         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16745     if (appData.debugMode) {
16746         fprintf(debugFP, "FEN castling rights:");
16747         for(i=0; i<nrCastlingRights; i++)
16748         fprintf(debugFP, " %d", board[CASTLING][i]);
16749         fprintf(debugFP, "\n");
16750     }
16751
16752       while(*p==' ') p++;
16753     }
16754
16755     /* read e.p. field in games that know e.p. capture */
16756     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16757        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16758       if(*p=='-') {
16759         p++; board[EP_STATUS] = EP_NONE;
16760       } else {
16761          char c = *p++ - AAA;
16762
16763          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16764          if(*p >= '0' && *p <='9') p++;
16765          board[EP_STATUS] = c;
16766       }
16767     }
16768
16769
16770     if(sscanf(p, "%d", &i) == 1) {
16771         FENrulePlies = i; /* 50-move ply counter */
16772         /* (The move number is still ignored)    */
16773     }
16774
16775     return TRUE;
16776 }
16777
16778 void
16779 EditPositionPasteFEN(char *fen)
16780 {
16781   if (fen != NULL) {
16782     Board initial_position;
16783
16784     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16785       DisplayError(_("Bad FEN position in clipboard"), 0);
16786       return ;
16787     } else {
16788       int savedBlackPlaysFirst = blackPlaysFirst;
16789       EditPositionEvent();
16790       blackPlaysFirst = savedBlackPlaysFirst;
16791       CopyBoard(boards[0], initial_position);
16792       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16793       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16794       DisplayBothClocks();
16795       DrawPosition(FALSE, boards[currentMove]);
16796     }
16797   }
16798 }
16799
16800 static char cseq[12] = "\\   ";
16801
16802 Boolean set_cont_sequence(char *new_seq)
16803 {
16804     int len;
16805     Boolean ret;
16806
16807     // handle bad attempts to set the sequence
16808         if (!new_seq)
16809                 return 0; // acceptable error - no debug
16810
16811     len = strlen(new_seq);
16812     ret = (len > 0) && (len < sizeof(cseq));
16813     if (ret)
16814       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16815     else if (appData.debugMode)
16816       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16817     return ret;
16818 }
16819
16820 /*
16821     reformat a source message so words don't cross the width boundary.  internal
16822     newlines are not removed.  returns the wrapped size (no null character unless
16823     included in source message).  If dest is NULL, only calculate the size required
16824     for the dest buffer.  lp argument indicats line position upon entry, and it's
16825     passed back upon exit.
16826 */
16827 int wrap(char *dest, char *src, int count, int width, int *lp)
16828 {
16829     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16830
16831     cseq_len = strlen(cseq);
16832     old_line = line = *lp;
16833     ansi = len = clen = 0;
16834
16835     for (i=0; i < count; i++)
16836     {
16837         if (src[i] == '\033')
16838             ansi = 1;
16839
16840         // if we hit the width, back up
16841         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16842         {
16843             // store i & len in case the word is too long
16844             old_i = i, old_len = len;
16845
16846             // find the end of the last word
16847             while (i && src[i] != ' ' && src[i] != '\n')
16848             {
16849                 i--;
16850                 len--;
16851             }
16852
16853             // word too long?  restore i & len before splitting it
16854             if ((old_i-i+clen) >= width)
16855             {
16856                 i = old_i;
16857                 len = old_len;
16858             }
16859
16860             // extra space?
16861             if (i && src[i-1] == ' ')
16862                 len--;
16863
16864             if (src[i] != ' ' && src[i] != '\n')
16865             {
16866                 i--;
16867                 if (len)
16868                     len--;
16869             }
16870
16871             // now append the newline and continuation sequence
16872             if (dest)
16873                 dest[len] = '\n';
16874             len++;
16875             if (dest)
16876                 strncpy(dest+len, cseq, cseq_len);
16877             len += cseq_len;
16878             line = cseq_len;
16879             clen = cseq_len;
16880             continue;
16881         }
16882
16883         if (dest)
16884             dest[len] = src[i];
16885         len++;
16886         if (!ansi)
16887             line++;
16888         if (src[i] == '\n')
16889             line = 0;
16890         if (src[i] == 'm')
16891             ansi = 0;
16892     }
16893     if (dest && appData.debugMode)
16894     {
16895         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16896             count, width, line, len, *lp);
16897         show_bytes(debugFP, src, count);
16898         fprintf(debugFP, "\ndest: ");
16899         show_bytes(debugFP, dest, len);
16900         fprintf(debugFP, "\n");
16901     }
16902     *lp = dest ? line : old_line;
16903
16904     return len;
16905 }
16906
16907 // [HGM] vari: routines for shelving variations
16908 Boolean modeRestore = FALSE;
16909
16910 void
16911 PushInner(int firstMove, int lastMove)
16912 {
16913         int i, j, nrMoves = lastMove - firstMove;
16914
16915         // push current tail of game on stack
16916         savedResult[storedGames] = gameInfo.result;
16917         savedDetails[storedGames] = gameInfo.resultDetails;
16918         gameInfo.resultDetails = NULL;
16919         savedFirst[storedGames] = firstMove;
16920         savedLast [storedGames] = lastMove;
16921         savedFramePtr[storedGames] = framePtr;
16922         framePtr -= nrMoves; // reserve space for the boards
16923         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16924             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16925             for(j=0; j<MOVE_LEN; j++)
16926                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16927             for(j=0; j<2*MOVE_LEN; j++)
16928                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16929             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16930             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16931             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16932             pvInfoList[firstMove+i-1].depth = 0;
16933             commentList[framePtr+i] = commentList[firstMove+i];
16934             commentList[firstMove+i] = NULL;
16935         }
16936
16937         storedGames++;
16938         forwardMostMove = firstMove; // truncate game so we can start variation
16939 }
16940
16941 void
16942 PushTail(int firstMove, int lastMove)
16943 {
16944         if(appData.icsActive) { // only in local mode
16945                 forwardMostMove = currentMove; // mimic old ICS behavior
16946                 return;
16947         }
16948         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16949
16950         PushInner(firstMove, lastMove);
16951         if(storedGames == 1) GreyRevert(FALSE);
16952         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16953 }
16954
16955 void
16956 PopInner(Boolean annotate)
16957 {
16958         int i, j, nrMoves;
16959         char buf[8000], moveBuf[20];
16960
16961         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16962         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16963         nrMoves = savedLast[storedGames] - currentMove;
16964         if(annotate) {
16965                 int cnt = 10;
16966                 if(!WhiteOnMove(currentMove))
16967                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16968                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16969                 for(i=currentMove; i<forwardMostMove; i++) {
16970                         if(WhiteOnMove(i))
16971                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16972                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16973                         strcat(buf, moveBuf);
16974                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16975                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16976                 }
16977                 strcat(buf, ")");
16978         }
16979         for(i=1; i<=nrMoves; i++) { // copy last variation back
16980             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16981             for(j=0; j<MOVE_LEN; j++)
16982                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16983             for(j=0; j<2*MOVE_LEN; j++)
16984                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16985             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16986             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16987             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16988             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16989             commentList[currentMove+i] = commentList[framePtr+i];
16990             commentList[framePtr+i] = NULL;
16991         }
16992         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16993         framePtr = savedFramePtr[storedGames];
16994         gameInfo.result = savedResult[storedGames];
16995         if(gameInfo.resultDetails != NULL) {
16996             free(gameInfo.resultDetails);
16997       }
16998         gameInfo.resultDetails = savedDetails[storedGames];
16999         forwardMostMove = currentMove + nrMoves;
17000 }
17001
17002 Boolean
17003 PopTail(Boolean annotate)
17004 {
17005         if(appData.icsActive) return FALSE; // only in local mode
17006         if(!storedGames) return FALSE; // sanity
17007         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17008
17009         PopInner(annotate);
17010         if(currentMove < forwardMostMove) ForwardEvent(); else
17011         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17012
17013         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17014         return TRUE;
17015 }
17016
17017 void
17018 CleanupTail()
17019 {       // remove all shelved variations
17020         int i;
17021         for(i=0; i<storedGames; i++) {
17022             if(savedDetails[i])
17023                 free(savedDetails[i]);
17024             savedDetails[i] = NULL;
17025         }
17026         for(i=framePtr; i<MAX_MOVES; i++) {
17027                 if(commentList[i]) free(commentList[i]);
17028                 commentList[i] = NULL;
17029         }
17030         framePtr = MAX_MOVES-1;
17031         storedGames = 0;
17032 }
17033
17034 void
17035 LoadVariation(int index, char *text)
17036 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17037         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17038         int level = 0, move;
17039
17040         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17041         // first find outermost bracketing variation
17042         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17043             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17044                 if(*p == '{') wait = '}'; else
17045                 if(*p == '[') wait = ']'; else
17046                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17047                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17048             }
17049             if(*p == wait) wait = NULLCHAR; // closing ]} found
17050             p++;
17051         }
17052         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17053         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17054         end[1] = NULLCHAR; // clip off comment beyond variation
17055         ToNrEvent(currentMove-1);
17056         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17057         // kludge: use ParsePV() to append variation to game
17058         move = currentMove;
17059         ParsePV(start, TRUE, TRUE);
17060         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17061         ClearPremoveHighlights();
17062         CommentPopDown();
17063         ToNrEvent(currentMove+1);
17064 }
17065