508ddf7464201eade14b762e7300bcb261f0e346
[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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397     flags &= ~F_ALL_CASTLE_OK;
398     break;
399   default:
400     break;
401   }
402   return flags;
403 }
404
405 FILE *gameFileFP, *debugFP;
406
407 /*
408     [AS] Note: sometimes, the sscanf() function is used to parse the input
409     into a fixed-size buffer. Because of this, we must be prepared to
410     receive strings as long as the size of the input buffer, which is currently
411     set to 4K for Windows and 8K for the rest.
412     So, we must either allocate sufficiently large buffers here, or
413     reduce the size of the input buffer in the input reading part.
414 */
415
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
419
420 ChessProgramState first, second;
421
422 /* premove variables */
423 int premoveToX = 0;
424 int premoveToY = 0;
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
428 int gotPremove = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
431
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
434
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
462
463 int have_sent_ICS_logon = 0;
464 int movesPerSession;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 int shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
570 };
571
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
577 };
578
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
584 };
585
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
591 };
592
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating(str)
649   char *str;
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine(ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions(ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739 "first",
740 "second"
741 };
742
743 void
744 InitEngine(ChessProgramState *cps, int n)
745 {   // [HGM] all engine initialiation put in a function that does one engine
746
747     ClearOptions(cps);
748
749     cps->which = engineNames[n];
750     cps->maybeThinking = FALSE;
751     cps->pr = NoProc;
752     cps->isr = NULL;
753     cps->sendTime = 2;
754     cps->sendDrawOffers = 1;
755
756     cps->program = appData.chessProgram[n];
757     cps->host = appData.host[n];
758     cps->dir = appData.directory[n];
759     cps->initString = appData.engInitString[n];
760     cps->computerString = appData.computerString[n];
761     cps->useSigint  = TRUE;
762     cps->useSigterm = TRUE;
763     cps->reuse = appData.reuse[n];
764     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
765     cps->useSetboard = FALSE;
766     cps->useSAN = FALSE;
767     cps->usePing = FALSE;
768     cps->lastPing = 0;
769     cps->lastPong = 0;
770     cps->usePlayother = FALSE;
771     cps->useColors = TRUE;
772     cps->useUsermove = FALSE;
773     cps->sendICS = FALSE;
774     cps->sendName = appData.icsActive;
775     cps->sdKludge = FALSE;
776     cps->stKludge = FALSE;
777     TidyProgramName(cps->program, cps->host, cps->tidy);
778     cps->matchWins = 0;
779     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780     cps->analysisSupport = 2; /* detect */
781     cps->analyzing = FALSE;
782     cps->initDone = FALSE;
783
784     /* New features added by Tord: */
785     cps->useFEN960 = FALSE;
786     cps->useOOCastle = TRUE;
787     /* End of new features added by Tord. */
788     cps->fenOverride  = appData.fenOverride[n];
789
790     /* [HGM] time odds: set factor for each machine */
791     cps->timeOdds  = appData.timeOdds[n];
792
793     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794     cps->accumulateTC = appData.accumulateTC[n];
795     cps->maxNrOfSessions = 1;
796
797     /* [HGM] debug */
798     cps->debug = FALSE;
799
800     cps->supportsNPS = UNKNOWN;
801     cps->memSize = FALSE;
802     cps->maxCores = FALSE;
803     cps->egtFormats[0] = NULLCHAR;
804
805     /* [HGM] options */
806     cps->optionSettings  = appData.engOptions[n];
807
808     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
809     cps->isUCI = appData.isUCI[n]; /* [AS] */
810     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
811
812     if (appData.protocolVersion[n] > PROTOVER
813         || appData.protocolVersion[n] < 1)
814       {
815         char buf[MSG_SIZ];
816         int len;
817
818         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
819                        appData.protocolVersion[n]);
820         if( (len > MSG_SIZ) && appData.debugMode )
821           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
822
823         DisplayFatalError(buf, 0, 2);
824       }
825     else
826       {
827         cps->protocolVersion = appData.protocolVersion[n];
828       }
829
830     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
831 }
832
833 ChessProgramState *savCps;
834
835 void
836 LoadEngine()
837 {
838     int i;
839     if(WaitForEngine(savCps, LoadEngine)) return;
840     CommonEngineInit(); // recalculate time odds
841     if(gameInfo.variant != StringToVariant(appData.variant)) {
842         // we changed variant when loading the engine; this forces us to reset
843         Reset(TRUE, savCps != &first);
844         EditGameEvent(); // for consistency with other path, as Reset changes mode
845     }
846     InitChessProgram(savCps, FALSE);
847     SendToProgram("force\n", savCps);
848     DisplayMessage("", "");
849     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
850     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
851     ThawUI();
852     SetGNUMode();
853 }
854
855 void
856 ReplaceEngine(ChessProgramState *cps, int n)
857 {
858     EditGameEvent();
859     UnloadEngine(cps);
860     appData.noChessProgram = FALSE;
861     appData.clockMode = TRUE;
862     InitEngine(cps, n);
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         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load(ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
879     if(engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
883         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         snprintf(command, MSG_SIZ, "%s %s", p, params);
900         p = command;
901     }
902     appData.chessProgram[i] = strdup(p);
903     appData.isUCI[i] = isUCI;
904     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
905     appData.hasOwnBookUCI[i] = hasBook;
906     if(!nickName[0]) useNick = FALSE;
907     if(useNick) ASSIGN(appData.pgnName[i], nickName);
908     if(addToList) {
909         int len;
910         q = firstChessProgramNames;
911         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
912         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
913                         useNick ? " -fn \"" : "",
914                         useNick ? nickName : "",
915                         useNick ? "\"" : "",
916                         v1 ? " -firstProtocolVersion 1" : "",
917                         hasBook ? "" : " -fNoOwnBookUCI",
918                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
919                         storeVariant ? " -variant " : "",
920                         storeVariant ? VariantName(gameInfo.variant) : "");
921         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
922         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
923         if(q)   free(q);
924     }
925     ReplaceEngine(cps, i);
926 }
927
928 void
929 InitTimeControls()
930 {
931     int matched, min, sec;
932     /*
933      * Parse timeControl resource
934      */
935     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
936                           appData.movesPerSession)) {
937         char buf[MSG_SIZ];
938         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
939         DisplayFatalError(buf, 0, 2);
940     }
941
942     /*
943      * Parse searchTime resource
944      */
945     if (*appData.searchTime != NULLCHAR) {
946         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
947         if (matched == 1) {
948             searchTime = min * 60;
949         } else if (matched == 2) {
950             searchTime = min * 60 + sec;
951         } else {
952             char buf[MSG_SIZ];
953             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
954             DisplayFatalError(buf, 0, 2);
955         }
956     }
957 }
958
959 void
960 InitBackEnd1()
961 {
962
963     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
964     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
965
966     GetTimeMark(&programStartTime);
967     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
968     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
969
970     ClearProgramStats();
971     programStats.ok_to_send = 1;
972     programStats.seen_stat = 0;
973
974     /*
975      * Initialize game list
976      */
977     ListNew(&gameList);
978
979
980     /*
981      * Internet chess server status
982      */
983     if (appData.icsActive) {
984         appData.matchMode = FALSE;
985         appData.matchGames = 0;
986 #if ZIPPY
987         appData.noChessProgram = !appData.zippyPlay;
988 #else
989         appData.zippyPlay = FALSE;
990         appData.zippyTalk = FALSE;
991         appData.noChessProgram = TRUE;
992 #endif
993         if (*appData.icsHelper != NULLCHAR) {
994             appData.useTelnet = TRUE;
995             appData.telnetProgram = appData.icsHelper;
996         }
997     } else {
998         appData.zippyTalk = appData.zippyPlay = FALSE;
999     }
1000
1001     /* [AS] Initialize pv info list [HGM] and game state */
1002     {
1003         int i, j;
1004
1005         for( i=0; i<=framePtr; i++ ) {
1006             pvInfoList[i].depth = -1;
1007             boards[i][EP_STATUS] = EP_NONE;
1008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1009         }
1010     }
1011
1012     InitTimeControls();
1013
1014     /* [AS] Adjudication threshold */
1015     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1016
1017     InitEngine(&first, 0);
1018     InitEngine(&second, 1);
1019     CommonEngineInit();
1020
1021     if (appData.icsActive) {
1022         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1023     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1024         appData.clockMode = FALSE;
1025         first.sendTime = second.sendTime = 0;
1026     }
1027
1028 #if ZIPPY
1029     /* Override some settings from environment variables, for backward
1030        compatibility.  Unfortunately it's not feasible to have the env
1031        vars just set defaults, at least in xboard.  Ugh.
1032     */
1033     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1034       ZippyInit();
1035     }
1036 #endif
1037
1038     if (!appData.icsActive) {
1039       char buf[MSG_SIZ];
1040       int len;
1041
1042       /* Check for variants that are supported only in ICS mode,
1043          or not at all.  Some that are accepted here nevertheless
1044          have bugs; see comments below.
1045       */
1046       VariantClass variant = StringToVariant(appData.variant);
1047       switch (variant) {
1048       case VariantBughouse:     /* need four players and two boards */
1049       case VariantKriegspiel:   /* need to hide pieces and move details */
1050         /* case VariantFischeRandom: (Fabien: moved below) */
1051         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1052         if( (len > MSG_SIZ) && appData.debugMode )
1053           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1054
1055         DisplayFatalError(buf, 0, 2);
1056         return;
1057
1058       case VariantUnknown:
1059       case VariantLoadable:
1060       case Variant29:
1061       case Variant30:
1062       case Variant31:
1063       case Variant32:
1064       case Variant33:
1065       case Variant34:
1066       case Variant35:
1067       case Variant36:
1068       default:
1069         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1070         if( (len > MSG_SIZ) && appData.debugMode )
1071           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1072
1073         DisplayFatalError(buf, 0, 2);
1074         return;
1075
1076       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1077       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1078       case VariantGothic:     /* [HGM] should work */
1079       case VariantCapablanca: /* [HGM] should work */
1080       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1081       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1082       case VariantKnightmate: /* [HGM] should work */
1083       case VariantCylinder:   /* [HGM] untested */
1084       case VariantFalcon:     /* [HGM] untested */
1085       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1086                                  offboard interposition not understood */
1087       case VariantNormal:     /* definitely works! */
1088       case VariantWildCastle: /* pieces not automatically shuffled */
1089       case VariantNoCastle:   /* pieces not automatically shuffled */
1090       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1091       case VariantLosers:     /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantSuicide:    /* should work except for win condition,
1094                                  and doesn't know captures are mandatory */
1095       case VariantGiveaway:   /* should work except for win condition,
1096                                  and doesn't know captures are mandatory */
1097       case VariantTwoKings:   /* should work */
1098       case VariantAtomic:     /* should work except for win condition */
1099       case Variant3Check:     /* should work except for win condition */
1100       case VariantShatranj:   /* should work except for all win conditions */
1101       case VariantMakruk:     /* should work except for daw countdown */
1102       case VariantBerolina:   /* might work if TestLegality is off */
1103       case VariantCapaRandom: /* should work */
1104       case VariantJanus:      /* should work */
1105       case VariantSuper:      /* experimental */
1106       case VariantGreat:      /* experimental, requires legality testing to be off */
1107       case VariantSChess:     /* S-Chess, should work */
1108       case VariantSpartan:    /* should work */
1109         break;
1110       }
1111     }
1112
1113 }
1114
1115 int NextIntegerFromString( char ** str, long * value )
1116 {
1117     int result = -1;
1118     char * s = *str;
1119
1120     while( *s == ' ' || *s == '\t' ) {
1121         s++;
1122     }
1123
1124     *value = 0;
1125
1126     if( *s >= '0' && *s <= '9' ) {
1127         while( *s >= '0' && *s <= '9' ) {
1128             *value = *value * 10 + (*s - '0');
1129             s++;
1130         }
1131
1132         result = 0;
1133     }
1134
1135     *str = s;
1136
1137     return result;
1138 }
1139
1140 int NextTimeControlFromString( char ** str, long * value )
1141 {
1142     long temp;
1143     int result = NextIntegerFromString( str, &temp );
1144
1145     if( result == 0 ) {
1146         *value = temp * 60; /* Minutes */
1147         if( **str == ':' ) {
1148             (*str)++;
1149             result = NextIntegerFromString( str, &temp );
1150             *value += temp; /* Seconds */
1151         }
1152     }
1153
1154     return result;
1155 }
1156
1157 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1158 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1159     int result = -1, type = 0; long temp, temp2;
1160
1161     if(**str != ':') return -1; // old params remain in force!
1162     (*str)++;
1163     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1164     if( NextIntegerFromString( str, &temp ) ) return -1;
1165     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1166
1167     if(**str != '/') {
1168         /* time only: incremental or sudden-death time control */
1169         if(**str == '+') { /* increment follows; read it */
1170             (*str)++;
1171             if(**str == '!') type = *(*str)++; // Bronstein TC
1172             if(result = NextIntegerFromString( str, &temp2)) return -1;
1173             *inc = temp2 * 1000;
1174             if(**str == '.') { // read fraction of increment
1175                 char *start = ++(*str);
1176                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1177                 temp2 *= 1000;
1178                 while(start++ < *str) temp2 /= 10;
1179                 *inc += temp2;
1180             }
1181         } else *inc = 0;
1182         *moves = 0; *tc = temp * 1000; *incType = type;
1183         return 0;
1184     }
1185
1186     (*str)++; /* classical time control */
1187     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1188
1189     if(result == 0) {
1190         *moves = temp;
1191         *tc    = temp2 * 1000;
1192         *inc   = 0;
1193         *incType = type;
1194     }
1195     return result;
1196 }
1197
1198 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1199 {   /* [HGM] get time to add from the multi-session time-control string */
1200     int incType, moves=1; /* kludge to force reading of first session */
1201     long time, increment;
1202     char *s = tcString;
1203
1204     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1205     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1206     do {
1207         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1208         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1209         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1210         if(movenr == -1) return time;    /* last move before new session     */
1211         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1212         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1213         if(!moves) return increment;     /* current session is incremental   */
1214         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1215     } while(movenr >= -1);               /* try again for next session       */
1216
1217     return 0; // no new time quota on this move
1218 }
1219
1220 int
1221 ParseTimeControl(tc, ti, mps)
1222      char *tc;
1223      float ti;
1224      int mps;
1225 {
1226   long tc1;
1227   long tc2;
1228   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1229   int min, sec=0;
1230
1231   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1232   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1233       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1234   if(ti > 0) {
1235
1236     if(mps)
1237       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1238     else 
1239       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1240   } else {
1241     if(mps)
1242       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1243     else 
1244       snprintf(buf, MSG_SIZ, ":%s", mytc);
1245   }
1246   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1247   
1248   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1249     return FALSE;
1250   }
1251
1252   if( *tc == '/' ) {
1253     /* Parse second time control */
1254     tc++;
1255
1256     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1257       return FALSE;
1258     }
1259
1260     if( tc2 == 0 ) {
1261       return FALSE;
1262     }
1263
1264     timeControl_2 = tc2 * 1000;
1265   }
1266   else {
1267     timeControl_2 = 0;
1268   }
1269
1270   if( tc1 == 0 ) {
1271     return FALSE;
1272   }
1273
1274   timeControl = tc1 * 1000;
1275
1276   if (ti >= 0) {
1277     timeIncrement = ti * 1000;  /* convert to ms */
1278     movesPerSession = 0;
1279   } else {
1280     timeIncrement = 0;
1281     movesPerSession = mps;
1282   }
1283   return TRUE;
1284 }
1285
1286 void
1287 InitBackEnd2()
1288 {
1289     if (appData.debugMode) {
1290         fprintf(debugFP, "%s\n", programVersion);
1291     }
1292
1293     set_cont_sequence(appData.wrapContSeq);
1294     if (appData.matchGames > 0) {
1295         appData.matchMode = TRUE;
1296     } else if (appData.matchMode) {
1297         appData.matchGames = 1;
1298     }
1299     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1300         appData.matchGames = appData.sameColorGames;
1301     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1302         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1303         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1304     }
1305     Reset(TRUE, FALSE);
1306     if (appData.noChessProgram || first.protocolVersion == 1) {
1307       InitBackEnd3();
1308     } else {
1309       /* kludge: allow timeout for initial "feature" commands */
1310       FreezeUI();
1311       DisplayMessage("", _("Starting chess program"));
1312       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1313     }
1314 }
1315
1316 int
1317 CalculateIndex(int index, int gameNr)
1318 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1319     int res;
1320     if(index > 0) return index; // fixed nmber
1321     if(index == 0) return 1;
1322     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1323     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1324     return res;
1325 }
1326
1327 int
1328 LoadGameOrPosition(int gameNr)
1329 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1330     if (*appData.loadGameFile != NULLCHAR) {
1331         if (!LoadGameFromFile(appData.loadGameFile,
1332                 CalculateIndex(appData.loadGameIndex, gameNr),
1333                               appData.loadGameFile, FALSE)) {
1334             DisplayFatalError(_("Bad game file"), 0, 1);
1335             return 0;
1336         }
1337     } else if (*appData.loadPositionFile != NULLCHAR) {
1338         if (!LoadPositionFromFile(appData.loadPositionFile,
1339                 CalculateIndex(appData.loadPositionIndex, gameNr),
1340                                   appData.loadPositionFile)) {
1341             DisplayFatalError(_("Bad position file"), 0, 1);
1342             return 0;
1343         }
1344     }
1345     return 1;
1346 }
1347
1348 void
1349 ReserveGame(int gameNr, char resChar)
1350 {
1351     FILE *tf = fopen(appData.tourneyFile, "r+");
1352     char *p, *q, c, buf[MSG_SIZ];
1353     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1354     safeStrCpy(buf, lastMsg, MSG_SIZ);
1355     DisplayMessage(_("Pick new game"), "");
1356     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1357     ParseArgsFromFile(tf);
1358     p = q = appData.results;
1359     if(appData.debugMode) {
1360       char *r = appData.participants;
1361       fprintf(debugFP, "results = '%s'\n", p);
1362       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1363       fprintf(debugFP, "\n");
1364     }
1365     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1366     nextGame = q - p;
1367     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1368     safeStrCpy(q, p, strlen(p) + 2);
1369     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1370     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1371     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1372         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1373         q[nextGame] = '*';
1374     }
1375     fseek(tf, -(strlen(p)+4), SEEK_END);
1376     c = fgetc(tf);
1377     if(c != '"') // depending on DOS or Unix line endings we can be one off
1378          fseek(tf, -(strlen(p)+2), SEEK_END);
1379     else fseek(tf, -(strlen(p)+3), SEEK_END);
1380     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1381     DisplayMessage(buf, "");
1382     free(p); appData.results = q;
1383     if(nextGame <= appData.matchGames && resChar != ' ' &&
1384        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1385         UnloadEngine(&first);  // next game belongs to other pairing;
1386         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1387     }
1388 }
1389
1390 void
1391 MatchEvent(int mode)
1392 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1393         int dummy;
1394         if(matchMode) { // already in match mode: switch it off
1395             abortMatch = TRUE;
1396             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1397             ModeHighlight(); // kludgey way to remove checkmark...
1398             return;
1399         }
1400 //      if(gameMode != BeginningOfGame) {
1401 //          DisplayError(_("You can only start a match from the initial position."), 0);
1402 //          return;
1403 //      }
1404         abortMatch = FALSE;
1405         appData.matchGames = appData.defaultMatchGames;
1406         /* Set up machine vs. machine match */
1407         nextGame = 0;
1408         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1409         if(appData.tourneyFile[0]) {
1410             ReserveGame(-1, 0);
1411             if(nextGame > appData.matchGames) {
1412                 char buf[MSG_SIZ];
1413                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1414                 DisplayError(buf, 0);
1415                 appData.tourneyFile[0] = 0;
1416                 return;
1417             }
1418         } else
1419         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1420             DisplayFatalError(_("Can't have a match with no chess programs"),
1421                               0, 2);
1422             return;
1423         }
1424         matchMode = mode;
1425         matchGame = roundNr = 1;
1426         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1427         NextMatchGame();
1428 }
1429
1430 void
1431 InitBackEnd3 P((void))
1432 {
1433     GameMode initialMode;
1434     char buf[MSG_SIZ];
1435     int err, len;
1436
1437     InitChessProgram(&first, startedFromSetupPosition);
1438
1439     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1440         free(programVersion);
1441         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1442         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1443     }
1444
1445     if (appData.icsActive) {
1446 #ifdef WIN32
1447         /* [DM] Make a console window if needed [HGM] merged ifs */
1448         ConsoleCreate();
1449 #endif
1450         err = establish();
1451         if (err != 0)
1452           {
1453             if (*appData.icsCommPort != NULLCHAR)
1454               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1455                              appData.icsCommPort);
1456             else
1457               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1458                         appData.icsHost, appData.icsPort);
1459
1460             if( (len > MSG_SIZ) && appData.debugMode )
1461               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1462
1463             DisplayFatalError(buf, err, 1);
1464             return;
1465         }
1466         SetICSMode();
1467         telnetISR =
1468           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1469         fromUserISR =
1470           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1471         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1472             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1473     } else if (appData.noChessProgram) {
1474         SetNCPMode();
1475     } else {
1476         SetGNUMode();
1477     }
1478
1479     if (*appData.cmailGameName != NULLCHAR) {
1480         SetCmailMode();
1481         OpenLoopback(&cmailPR);
1482         cmailISR =
1483           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1484     }
1485
1486     ThawUI();
1487     DisplayMessage("", "");
1488     if (StrCaseCmp(appData.initialMode, "") == 0) {
1489       initialMode = BeginningOfGame;
1490       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1491         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1492         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1493         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1494         ModeHighlight();
1495       }
1496     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1497       initialMode = TwoMachinesPlay;
1498     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1499       initialMode = AnalyzeFile;
1500     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1501       initialMode = AnalyzeMode;
1502     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1503       initialMode = MachinePlaysWhite;
1504     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1505       initialMode = MachinePlaysBlack;
1506     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1507       initialMode = EditGame;
1508     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1509       initialMode = EditPosition;
1510     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1511       initialMode = Training;
1512     } else {
1513       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1514       if( (len > MSG_SIZ) && appData.debugMode )
1515         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1516
1517       DisplayFatalError(buf, 0, 2);
1518       return;
1519     }
1520
1521     if (appData.matchMode) {
1522         if(appData.tourneyFile[0]) { // start tourney from command line
1523             FILE *f;
1524             if(f = fopen(appData.tourneyFile, "r")) {
1525                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1526                 fclose(f);
1527             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1528         }
1529         MatchEvent(TRUE);
1530     } else if (*appData.cmailGameName != NULLCHAR) {
1531         /* Set up cmail mode */
1532         ReloadCmailMsgEvent(TRUE);
1533     } else {
1534         /* Set up other modes */
1535         if (initialMode == AnalyzeFile) {
1536           if (*appData.loadGameFile == NULLCHAR) {
1537             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1538             return;
1539           }
1540         }
1541         if (*appData.loadGameFile != NULLCHAR) {
1542             (void) LoadGameFromFile(appData.loadGameFile,
1543                                     appData.loadGameIndex,
1544                                     appData.loadGameFile, TRUE);
1545         } else if (*appData.loadPositionFile != NULLCHAR) {
1546             (void) LoadPositionFromFile(appData.loadPositionFile,
1547                                         appData.loadPositionIndex,
1548                                         appData.loadPositionFile);
1549             /* [HGM] try to make self-starting even after FEN load */
1550             /* to allow automatic setup of fairy variants with wtm */
1551             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1552                 gameMode = BeginningOfGame;
1553                 setboardSpoiledMachineBlack = 1;
1554             }
1555             /* [HGM] loadPos: make that every new game uses the setup */
1556             /* from file as long as we do not switch variant          */
1557             if(!blackPlaysFirst) {
1558                 startedFromPositionFile = TRUE;
1559                 CopyBoard(filePosition, boards[0]);
1560             }
1561         }
1562         if (initialMode == AnalyzeMode) {
1563           if (appData.noChessProgram) {
1564             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1565             return;
1566           }
1567           if (appData.icsActive) {
1568             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1569             return;
1570           }
1571           AnalyzeModeEvent();
1572         } else if (initialMode == AnalyzeFile) {
1573           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1574           ShowThinkingEvent();
1575           AnalyzeFileEvent();
1576           AnalysisPeriodicEvent(1);
1577         } else if (initialMode == MachinePlaysWhite) {
1578           if (appData.noChessProgram) {
1579             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1580                               0, 2);
1581             return;
1582           }
1583           if (appData.icsActive) {
1584             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1585                               0, 2);
1586             return;
1587           }
1588           MachineWhiteEvent();
1589         } else if (initialMode == MachinePlaysBlack) {
1590           if (appData.noChessProgram) {
1591             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1592                               0, 2);
1593             return;
1594           }
1595           if (appData.icsActive) {
1596             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1597                               0, 2);
1598             return;
1599           }
1600           MachineBlackEvent();
1601         } else if (initialMode == TwoMachinesPlay) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1604                               0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1609                               0, 2);
1610             return;
1611           }
1612           TwoMachinesEvent();
1613         } else if (initialMode == EditGame) {
1614           EditGameEvent();
1615         } else if (initialMode == EditPosition) {
1616           EditPositionEvent();
1617         } else if (initialMode == Training) {
1618           if (*appData.loadGameFile == NULLCHAR) {
1619             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1620             return;
1621           }
1622           TrainingEvent();
1623         }
1624     }
1625 }
1626
1627 /*
1628  * Establish will establish a contact to a remote host.port.
1629  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1630  *  used to talk to the host.
1631  * Returns 0 if okay, error code if not.
1632  */
1633 int
1634 establish()
1635 {
1636     char buf[MSG_SIZ];
1637
1638     if (*appData.icsCommPort != NULLCHAR) {
1639         /* Talk to the host through a serial comm port */
1640         return OpenCommPort(appData.icsCommPort, &icsPR);
1641
1642     } else if (*appData.gateway != NULLCHAR) {
1643         if (*appData.remoteShell == NULLCHAR) {
1644             /* Use the rcmd protocol to run telnet program on a gateway host */
1645             snprintf(buf, sizeof(buf), "%s %s %s",
1646                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1647             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1648
1649         } else {
1650             /* Use the rsh program to run telnet program on a gateway host */
1651             if (*appData.remoteUser == NULLCHAR) {
1652                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1653                         appData.gateway, appData.telnetProgram,
1654                         appData.icsHost, appData.icsPort);
1655             } else {
1656                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1657                         appData.remoteShell, appData.gateway,
1658                         appData.remoteUser, appData.telnetProgram,
1659                         appData.icsHost, appData.icsPort);
1660             }
1661             return StartChildProcess(buf, "", &icsPR);
1662
1663         }
1664     } else if (appData.useTelnet) {
1665         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1666
1667     } else {
1668         /* TCP socket interface differs somewhat between
1669            Unix and NT; handle details in the front end.
1670            */
1671         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1672     }
1673 }
1674
1675 void EscapeExpand(char *p, char *q)
1676 {       // [HGM] initstring: routine to shape up string arguments
1677         while(*p++ = *q++) if(p[-1] == '\\')
1678             switch(*q++) {
1679                 case 'n': p[-1] = '\n'; break;
1680                 case 'r': p[-1] = '\r'; break;
1681                 case 't': p[-1] = '\t'; break;
1682                 case '\\': p[-1] = '\\'; break;
1683                 case 0: *p = 0; return;
1684                 default: p[-1] = q[-1]; break;
1685             }
1686 }
1687
1688 void
1689 show_bytes(fp, buf, count)
1690      FILE *fp;
1691      char *buf;
1692      int count;
1693 {
1694     while (count--) {
1695         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1696             fprintf(fp, "\\%03o", *buf & 0xff);
1697         } else {
1698             putc(*buf, fp);
1699         }
1700         buf++;
1701     }
1702     fflush(fp);
1703 }
1704
1705 /* Returns an errno value */
1706 int
1707 OutputMaybeTelnet(pr, message, count, outError)
1708      ProcRef pr;
1709      char *message;
1710      int count;
1711      int *outError;
1712 {
1713     char buf[8192], *p, *q, *buflim;
1714     int left, newcount, outcount;
1715
1716     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1717         *appData.gateway != NULLCHAR) {
1718         if (appData.debugMode) {
1719             fprintf(debugFP, ">ICS: ");
1720             show_bytes(debugFP, message, count);
1721             fprintf(debugFP, "\n");
1722         }
1723         return OutputToProcess(pr, message, count, outError);
1724     }
1725
1726     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1727     p = message;
1728     q = buf;
1729     left = count;
1730     newcount = 0;
1731     while (left) {
1732         if (q >= buflim) {
1733             if (appData.debugMode) {
1734                 fprintf(debugFP, ">ICS: ");
1735                 show_bytes(debugFP, buf, newcount);
1736                 fprintf(debugFP, "\n");
1737             }
1738             outcount = OutputToProcess(pr, buf, newcount, outError);
1739             if (outcount < newcount) return -1; /* to be sure */
1740             q = buf;
1741             newcount = 0;
1742         }
1743         if (*p == '\n') {
1744             *q++ = '\r';
1745             newcount++;
1746         } else if (((unsigned char) *p) == TN_IAC) {
1747             *q++ = (char) TN_IAC;
1748             newcount ++;
1749         }
1750         *q++ = *p++;
1751         newcount++;
1752         left--;
1753     }
1754     if (appData.debugMode) {
1755         fprintf(debugFP, ">ICS: ");
1756         show_bytes(debugFP, buf, newcount);
1757         fprintf(debugFP, "\n");
1758     }
1759     outcount = OutputToProcess(pr, buf, newcount, outError);
1760     if (outcount < newcount) return -1; /* to be sure */
1761     return count;
1762 }
1763
1764 void
1765 read_from_player(isr, closure, message, count, error)
1766      InputSourceRef isr;
1767      VOIDSTAR closure;
1768      char *message;
1769      int count;
1770      int error;
1771 {
1772     int outError, outCount;
1773     static int gotEof = 0;
1774
1775     /* Pass data read from player on to ICS */
1776     if (count > 0) {
1777         gotEof = 0;
1778         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1779         if (outCount < count) {
1780             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1781         }
1782     } else if (count < 0) {
1783         RemoveInputSource(isr);
1784         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1785     } else if (gotEof++ > 0) {
1786         RemoveInputSource(isr);
1787         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1788     }
1789 }
1790
1791 void
1792 KeepAlive()
1793 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1794     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1795     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1796     SendToICS("date\n");
1797     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1798 }
1799
1800 /* added routine for printf style output to ics */
1801 void ics_printf(char *format, ...)
1802 {
1803     char buffer[MSG_SIZ];
1804     va_list args;
1805
1806     va_start(args, format);
1807     vsnprintf(buffer, sizeof(buffer), format, args);
1808     buffer[sizeof(buffer)-1] = '\0';
1809     SendToICS(buffer);
1810     va_end(args);
1811 }
1812
1813 void
1814 SendToICS(s)
1815      char *s;
1816 {
1817     int count, outCount, outError;
1818
1819     if (icsPR == NULL) return;
1820
1821     count = strlen(s);
1822     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1823     if (outCount < count) {
1824         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1825     }
1826 }
1827
1828 /* This is used for sending logon scripts to the ICS. Sending
1829    without a delay causes problems when using timestamp on ICC
1830    (at least on my machine). */
1831 void
1832 SendToICSDelayed(s,msdelay)
1833      char *s;
1834      long msdelay;
1835 {
1836     int count, outCount, outError;
1837
1838     if (icsPR == NULL) return;
1839
1840     count = strlen(s);
1841     if (appData.debugMode) {
1842         fprintf(debugFP, ">ICS: ");
1843         show_bytes(debugFP, s, count);
1844         fprintf(debugFP, "\n");
1845     }
1846     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1847                                       msdelay);
1848     if (outCount < count) {
1849         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1850     }
1851 }
1852
1853
1854 /* Remove all highlighting escape sequences in s
1855    Also deletes any suffix starting with '('
1856    */
1857 char *
1858 StripHighlightAndTitle(s)
1859      char *s;
1860 {
1861     static char retbuf[MSG_SIZ];
1862     char *p = retbuf;
1863
1864     while (*s != NULLCHAR) {
1865         while (*s == '\033') {
1866             while (*s != NULLCHAR && !isalpha(*s)) s++;
1867             if (*s != NULLCHAR) s++;
1868         }
1869         while (*s != NULLCHAR && *s != '\033') {
1870             if (*s == '(' || *s == '[') {
1871                 *p = NULLCHAR;
1872                 return retbuf;
1873             }
1874             *p++ = *s++;
1875         }
1876     }
1877     *p = NULLCHAR;
1878     return retbuf;
1879 }
1880
1881 /* Remove all highlighting escape sequences in s */
1882 char *
1883 StripHighlight(s)
1884      char *s;
1885 {
1886     static char retbuf[MSG_SIZ];
1887     char *p = retbuf;
1888
1889     while (*s != NULLCHAR) {
1890         while (*s == '\033') {
1891             while (*s != NULLCHAR && !isalpha(*s)) s++;
1892             if (*s != NULLCHAR) s++;
1893         }
1894         while (*s != NULLCHAR && *s != '\033') {
1895             *p++ = *s++;
1896         }
1897     }
1898     *p = NULLCHAR;
1899     return retbuf;
1900 }
1901
1902 char *variantNames[] = VARIANT_NAMES;
1903 char *
1904 VariantName(v)
1905      VariantClass v;
1906 {
1907     return variantNames[v];
1908 }
1909
1910
1911 /* Identify a variant from the strings the chess servers use or the
1912    PGN Variant tag names we use. */
1913 VariantClass
1914 StringToVariant(e)
1915      char *e;
1916 {
1917     char *p;
1918     int wnum = -1;
1919     VariantClass v = VariantNormal;
1920     int i, found = FALSE;
1921     char buf[MSG_SIZ];
1922     int len;
1923
1924     if (!e) return v;
1925
1926     /* [HGM] skip over optional board-size prefixes */
1927     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1928         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1929         while( *e++ != '_');
1930     }
1931
1932     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1933         v = VariantNormal;
1934         found = TRUE;
1935     } else
1936     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1937       if (StrCaseStr(e, variantNames[i])) {
1938         v = (VariantClass) i;
1939         found = TRUE;
1940         break;
1941       }
1942     }
1943
1944     if (!found) {
1945       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1946           || StrCaseStr(e, "wild/fr")
1947           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1948         v = VariantFischeRandom;
1949       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1950                  (i = 1, p = StrCaseStr(e, "w"))) {
1951         p += i;
1952         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1953         if (isdigit(*p)) {
1954           wnum = atoi(p);
1955         } else {
1956           wnum = -1;
1957         }
1958         switch (wnum) {
1959         case 0: /* FICS only, actually */
1960         case 1:
1961           /* Castling legal even if K starts on d-file */
1962           v = VariantWildCastle;
1963           break;
1964         case 2:
1965         case 3:
1966         case 4:
1967           /* Castling illegal even if K & R happen to start in
1968              normal positions. */
1969           v = VariantNoCastle;
1970           break;
1971         case 5:
1972         case 7:
1973         case 8:
1974         case 10:
1975         case 11:
1976         case 12:
1977         case 13:
1978         case 14:
1979         case 15:
1980         case 18:
1981         case 19:
1982           /* Castling legal iff K & R start in normal positions */
1983           v = VariantNormal;
1984           break;
1985         case 6:
1986         case 20:
1987         case 21:
1988           /* Special wilds for position setup; unclear what to do here */
1989           v = VariantLoadable;
1990           break;
1991         case 9:
1992           /* Bizarre ICC game */
1993           v = VariantTwoKings;
1994           break;
1995         case 16:
1996           v = VariantKriegspiel;
1997           break;
1998         case 17:
1999           v = VariantLosers;
2000           break;
2001         case 22:
2002           v = VariantFischeRandom;
2003           break;
2004         case 23:
2005           v = VariantCrazyhouse;
2006           break;
2007         case 24:
2008           v = VariantBughouse;
2009           break;
2010         case 25:
2011           v = Variant3Check;
2012           break;
2013         case 26:
2014           /* Not quite the same as FICS suicide! */
2015           v = VariantGiveaway;
2016           break;
2017         case 27:
2018           v = VariantAtomic;
2019           break;
2020         case 28:
2021           v = VariantShatranj;
2022           break;
2023
2024         /* Temporary names for future ICC types.  The name *will* change in
2025            the next xboard/WinBoard release after ICC defines it. */
2026         case 29:
2027           v = Variant29;
2028           break;
2029         case 30:
2030           v = Variant30;
2031           break;
2032         case 31:
2033           v = Variant31;
2034           break;
2035         case 32:
2036           v = Variant32;
2037           break;
2038         case 33:
2039           v = Variant33;
2040           break;
2041         case 34:
2042           v = Variant34;
2043           break;
2044         case 35:
2045           v = Variant35;
2046           break;
2047         case 36:
2048           v = Variant36;
2049           break;
2050         case 37:
2051           v = VariantShogi;
2052           break;
2053         case 38:
2054           v = VariantXiangqi;
2055           break;
2056         case 39:
2057           v = VariantCourier;
2058           break;
2059         case 40:
2060           v = VariantGothic;
2061           break;
2062         case 41:
2063           v = VariantCapablanca;
2064           break;
2065         case 42:
2066           v = VariantKnightmate;
2067           break;
2068         case 43:
2069           v = VariantFairy;
2070           break;
2071         case 44:
2072           v = VariantCylinder;
2073           break;
2074         case 45:
2075           v = VariantFalcon;
2076           break;
2077         case 46:
2078           v = VariantCapaRandom;
2079           break;
2080         case 47:
2081           v = VariantBerolina;
2082           break;
2083         case 48:
2084           v = VariantJanus;
2085           break;
2086         case 49:
2087           v = VariantSuper;
2088           break;
2089         case 50:
2090           v = VariantGreat;
2091           break;
2092         case -1:
2093           /* Found "wild" or "w" in the string but no number;
2094              must assume it's normal chess. */
2095           v = VariantNormal;
2096           break;
2097         default:
2098           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2099           if( (len > MSG_SIZ) && appData.debugMode )
2100             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2101
2102           DisplayError(buf, 0);
2103           v = VariantUnknown;
2104           break;
2105         }
2106       }
2107     }
2108     if (appData.debugMode) {
2109       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2110               e, wnum, VariantName(v));
2111     }
2112     return v;
2113 }
2114
2115 static int leftover_start = 0, leftover_len = 0;
2116 char star_match[STAR_MATCH_N][MSG_SIZ];
2117
2118 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2119    advance *index beyond it, and set leftover_start to the new value of
2120    *index; else return FALSE.  If pattern contains the character '*', it
2121    matches any sequence of characters not containing '\r', '\n', or the
2122    character following the '*' (if any), and the matched sequence(s) are
2123    copied into star_match.
2124    */
2125 int
2126 looking_at(buf, index, pattern)
2127      char *buf;
2128      int *index;
2129      char *pattern;
2130 {
2131     char *bufp = &buf[*index], *patternp = pattern;
2132     int star_count = 0;
2133     char *matchp = star_match[0];
2134
2135     for (;;) {
2136         if (*patternp == NULLCHAR) {
2137             *index = leftover_start = bufp - buf;
2138             *matchp = NULLCHAR;
2139             return TRUE;
2140         }
2141         if (*bufp == NULLCHAR) return FALSE;
2142         if (*patternp == '*') {
2143             if (*bufp == *(patternp + 1)) {
2144                 *matchp = NULLCHAR;
2145                 matchp = star_match[++star_count];
2146                 patternp += 2;
2147                 bufp++;
2148                 continue;
2149             } else if (*bufp == '\n' || *bufp == '\r') {
2150                 patternp++;
2151                 if (*patternp == NULLCHAR)
2152                   continue;
2153                 else
2154                   return FALSE;
2155             } else {
2156                 *matchp++ = *bufp++;
2157                 continue;
2158             }
2159         }
2160         if (*patternp != *bufp) return FALSE;
2161         patternp++;
2162         bufp++;
2163     }
2164 }
2165
2166 void
2167 SendToPlayer(data, length)
2168      char *data;
2169      int length;
2170 {
2171     int error, outCount;
2172     outCount = OutputToProcess(NoProc, data, length, &error);
2173     if (outCount < length) {
2174         DisplayFatalError(_("Error writing to display"), error, 1);
2175     }
2176 }
2177
2178 void
2179 PackHolding(packed, holding)
2180      char packed[];
2181      char *holding;
2182 {
2183     char *p = holding;
2184     char *q = packed;
2185     int runlength = 0;
2186     int curr = 9999;
2187     do {
2188         if (*p == curr) {
2189             runlength++;
2190         } else {
2191             switch (runlength) {
2192               case 0:
2193                 break;
2194               case 1:
2195                 *q++ = curr;
2196                 break;
2197               case 2:
2198                 *q++ = curr;
2199                 *q++ = curr;
2200                 break;
2201               default:
2202                 sprintf(q, "%d", runlength);
2203                 while (*q) q++;
2204                 *q++ = curr;
2205                 break;
2206             }
2207             runlength = 1;
2208             curr = *p;
2209         }
2210     } while (*p++);
2211     *q = NULLCHAR;
2212 }
2213
2214 /* Telnet protocol requests from the front end */
2215 void
2216 TelnetRequest(ddww, option)
2217      unsigned char ddww, option;
2218 {
2219     unsigned char msg[3];
2220     int outCount, outError;
2221
2222     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2223
2224     if (appData.debugMode) {
2225         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2226         switch (ddww) {
2227           case TN_DO:
2228             ddwwStr = "DO";
2229             break;
2230           case TN_DONT:
2231             ddwwStr = "DONT";
2232             break;
2233           case TN_WILL:
2234             ddwwStr = "WILL";
2235             break;
2236           case TN_WONT:
2237             ddwwStr = "WONT";
2238             break;
2239           default:
2240             ddwwStr = buf1;
2241             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2242             break;
2243         }
2244         switch (option) {
2245           case TN_ECHO:
2246             optionStr = "ECHO";
2247             break;
2248           default:
2249             optionStr = buf2;
2250             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2251             break;
2252         }
2253         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2254     }
2255     msg[0] = TN_IAC;
2256     msg[1] = ddww;
2257     msg[2] = option;
2258     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2259     if (outCount < 3) {
2260         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2261     }
2262 }
2263
2264 void
2265 DoEcho()
2266 {
2267     if (!appData.icsActive) return;
2268     TelnetRequest(TN_DO, TN_ECHO);
2269 }
2270
2271 void
2272 DontEcho()
2273 {
2274     if (!appData.icsActive) return;
2275     TelnetRequest(TN_DONT, TN_ECHO);
2276 }
2277
2278 void
2279 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2280 {
2281     /* put the holdings sent to us by the server on the board holdings area */
2282     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2283     char p;
2284     ChessSquare piece;
2285
2286     if(gameInfo.holdingsWidth < 2)  return;
2287     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2288         return; // prevent overwriting by pre-board holdings
2289
2290     if( (int)lowestPiece >= BlackPawn ) {
2291         holdingsColumn = 0;
2292         countsColumn = 1;
2293         holdingsStartRow = BOARD_HEIGHT-1;
2294         direction = -1;
2295     } else {
2296         holdingsColumn = BOARD_WIDTH-1;
2297         countsColumn = BOARD_WIDTH-2;
2298         holdingsStartRow = 0;
2299         direction = 1;
2300     }
2301
2302     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2303         board[i][holdingsColumn] = EmptySquare;
2304         board[i][countsColumn]   = (ChessSquare) 0;
2305     }
2306     while( (p=*holdings++) != NULLCHAR ) {
2307         piece = CharToPiece( ToUpper(p) );
2308         if(piece == EmptySquare) continue;
2309         /*j = (int) piece - (int) WhitePawn;*/
2310         j = PieceToNumber(piece);
2311         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2312         if(j < 0) continue;               /* should not happen */
2313         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2314         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2315         board[holdingsStartRow+j*direction][countsColumn]++;
2316     }
2317 }
2318
2319
2320 void
2321 VariantSwitch(Board board, VariantClass newVariant)
2322 {
2323    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2324    static Board oldBoard;
2325
2326    startedFromPositionFile = FALSE;
2327    if(gameInfo.variant == newVariant) return;
2328
2329    /* [HGM] This routine is called each time an assignment is made to
2330     * gameInfo.variant during a game, to make sure the board sizes
2331     * are set to match the new variant. If that means adding or deleting
2332     * holdings, we shift the playing board accordingly
2333     * This kludge is needed because in ICS observe mode, we get boards
2334     * of an ongoing game without knowing the variant, and learn about the
2335     * latter only later. This can be because of the move list we requested,
2336     * in which case the game history is refilled from the beginning anyway,
2337     * but also when receiving holdings of a crazyhouse game. In the latter
2338     * case we want to add those holdings to the already received position.
2339     */
2340
2341
2342    if (appData.debugMode) {
2343      fprintf(debugFP, "Switch board from %s to %s\n",
2344              VariantName(gameInfo.variant), VariantName(newVariant));
2345      setbuf(debugFP, NULL);
2346    }
2347    shuffleOpenings = 0;       /* [HGM] shuffle */
2348    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2349    switch(newVariant)
2350      {
2351      case VariantShogi:
2352        newWidth = 9;  newHeight = 9;
2353        gameInfo.holdingsSize = 7;
2354      case VariantBughouse:
2355      case VariantCrazyhouse:
2356        newHoldingsWidth = 2; break;
2357      case VariantGreat:
2358        newWidth = 10;
2359      case VariantSuper:
2360        newHoldingsWidth = 2;
2361        gameInfo.holdingsSize = 8;
2362        break;
2363      case VariantGothic:
2364      case VariantCapablanca:
2365      case VariantCapaRandom:
2366        newWidth = 10;
2367      default:
2368        newHoldingsWidth = gameInfo.holdingsSize = 0;
2369      };
2370
2371    if(newWidth  != gameInfo.boardWidth  ||
2372       newHeight != gameInfo.boardHeight ||
2373       newHoldingsWidth != gameInfo.holdingsWidth ) {
2374
2375      /* shift position to new playing area, if needed */
2376      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2377        for(i=0; i<BOARD_HEIGHT; i++)
2378          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2379            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2380              board[i][j];
2381        for(i=0; i<newHeight; i++) {
2382          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2383          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2384        }
2385      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2386        for(i=0; i<BOARD_HEIGHT; i++)
2387          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2388            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2389              board[i][j];
2390      }
2391      gameInfo.boardWidth  = newWidth;
2392      gameInfo.boardHeight = newHeight;
2393      gameInfo.holdingsWidth = newHoldingsWidth;
2394      gameInfo.variant = newVariant;
2395      InitDrawingSizes(-2, 0);
2396    } else gameInfo.variant = newVariant;
2397    CopyBoard(oldBoard, board);   // remember correctly formatted board
2398      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2399    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2400 }
2401
2402 static int loggedOn = FALSE;
2403
2404 /*-- Game start info cache: --*/
2405 int gs_gamenum;
2406 char gs_kind[MSG_SIZ];
2407 static char player1Name[128] = "";
2408 static char player2Name[128] = "";
2409 static char cont_seq[] = "\n\\   ";
2410 static int player1Rating = -1;
2411 static int player2Rating = -1;
2412 /*----------------------------*/
2413
2414 ColorClass curColor = ColorNormal;
2415 int suppressKibitz = 0;
2416
2417 // [HGM] seekgraph
2418 Boolean soughtPending = FALSE;
2419 Boolean seekGraphUp;
2420 #define MAX_SEEK_ADS 200
2421 #define SQUARE 0x80
2422 char *seekAdList[MAX_SEEK_ADS];
2423 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2424 float tcList[MAX_SEEK_ADS];
2425 char colorList[MAX_SEEK_ADS];
2426 int nrOfSeekAds = 0;
2427 int minRating = 1010, maxRating = 2800;
2428 int hMargin = 10, vMargin = 20, h, w;
2429 extern int squareSize, lineGap;
2430
2431 void
2432 PlotSeekAd(int i)
2433 {
2434         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2435         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2436         if(r < minRating+100 && r >=0 ) r = minRating+100;
2437         if(r > maxRating) r = maxRating;
2438         if(tc < 1.) tc = 1.;
2439         if(tc > 95.) tc = 95.;
2440         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2441         y = ((double)r - minRating)/(maxRating - minRating)
2442             * (h-vMargin-squareSize/8-1) + vMargin;
2443         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2444         if(strstr(seekAdList[i], " u ")) color = 1;
2445         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2446            !strstr(seekAdList[i], "bullet") &&
2447            !strstr(seekAdList[i], "blitz") &&
2448            !strstr(seekAdList[i], "standard") ) color = 2;
2449         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2450         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2451 }
2452
2453 void
2454 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2455 {
2456         char buf[MSG_SIZ], *ext = "";
2457         VariantClass v = StringToVariant(type);
2458         if(strstr(type, "wild")) {
2459             ext = type + 4; // append wild number
2460             if(v == VariantFischeRandom) type = "chess960"; else
2461             if(v == VariantLoadable) type = "setup"; else
2462             type = VariantName(v);
2463         }
2464         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2465         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2466             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2467             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2468             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2469             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2470             seekNrList[nrOfSeekAds] = nr;
2471             zList[nrOfSeekAds] = 0;
2472             seekAdList[nrOfSeekAds++] = StrSave(buf);
2473             if(plot) PlotSeekAd(nrOfSeekAds-1);
2474         }
2475 }
2476
2477 void
2478 EraseSeekDot(int i)
2479 {
2480     int x = xList[i], y = yList[i], d=squareSize/4, k;
2481     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2482     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2483     // now replot every dot that overlapped
2484     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2485         int xx = xList[k], yy = yList[k];
2486         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2487             DrawSeekDot(xx, yy, colorList[k]);
2488     }
2489 }
2490
2491 void
2492 RemoveSeekAd(int nr)
2493 {
2494         int i;
2495         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2496             EraseSeekDot(i);
2497             if(seekAdList[i]) free(seekAdList[i]);
2498             seekAdList[i] = seekAdList[--nrOfSeekAds];
2499             seekNrList[i] = seekNrList[nrOfSeekAds];
2500             ratingList[i] = ratingList[nrOfSeekAds];
2501             colorList[i]  = colorList[nrOfSeekAds];
2502             tcList[i] = tcList[nrOfSeekAds];
2503             xList[i]  = xList[nrOfSeekAds];
2504             yList[i]  = yList[nrOfSeekAds];
2505             zList[i]  = zList[nrOfSeekAds];
2506             seekAdList[nrOfSeekAds] = NULL;
2507             break;
2508         }
2509 }
2510
2511 Boolean
2512 MatchSoughtLine(char *line)
2513 {
2514     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2515     int nr, base, inc, u=0; char dummy;
2516
2517     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2518        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2519        (u=1) &&
2520        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2521         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2522         // match: compact and save the line
2523         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2524         return TRUE;
2525     }
2526     return FALSE;
2527 }
2528
2529 int
2530 DrawSeekGraph()
2531 {
2532     int i;
2533     if(!seekGraphUp) return FALSE;
2534     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2535     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2536
2537     DrawSeekBackground(0, 0, w, h);
2538     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2539     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2540     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2541         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2542         yy = h-1-yy;
2543         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2544         if(i%500 == 0) {
2545             char buf[MSG_SIZ];
2546             snprintf(buf, MSG_SIZ, "%d", i);
2547             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2548         }
2549     }
2550     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2551     for(i=1; i<100; i+=(i<10?1:5)) {
2552         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2553         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2554         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2555             char buf[MSG_SIZ];
2556             snprintf(buf, MSG_SIZ, "%d", i);
2557             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2558         }
2559     }
2560     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2561     return TRUE;
2562 }
2563
2564 int SeekGraphClick(ClickType click, int x, int y, int moving)
2565 {
2566     static int lastDown = 0, displayed = 0, lastSecond;
2567     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2568         if(click == Release || moving) return FALSE;
2569         nrOfSeekAds = 0;
2570         soughtPending = TRUE;
2571         SendToICS(ics_prefix);
2572         SendToICS("sought\n"); // should this be "sought all"?
2573     } else { // issue challenge based on clicked ad
2574         int dist = 10000; int i, closest = 0, second = 0;
2575         for(i=0; i<nrOfSeekAds; i++) {
2576             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2577             if(d < dist) { dist = d; closest = i; }
2578             second += (d - zList[i] < 120); // count in-range ads
2579             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2580         }
2581         if(dist < 120) {
2582             char buf[MSG_SIZ];
2583             second = (second > 1);
2584             if(displayed != closest || second != lastSecond) {
2585                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2586                 lastSecond = second; displayed = closest;
2587             }
2588             if(click == Press) {
2589                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2590                 lastDown = closest;
2591                 return TRUE;
2592             } // on press 'hit', only show info
2593             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2594             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2595             SendToICS(ics_prefix);
2596             SendToICS(buf);
2597             return TRUE; // let incoming board of started game pop down the graph
2598         } else if(click == Release) { // release 'miss' is ignored
2599             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2600             if(moving == 2) { // right up-click
2601                 nrOfSeekAds = 0; // refresh graph
2602                 soughtPending = TRUE;
2603                 SendToICS(ics_prefix);
2604                 SendToICS("sought\n"); // should this be "sought all"?
2605             }
2606             return TRUE;
2607         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2608         // press miss or release hit 'pop down' seek graph
2609         seekGraphUp = FALSE;
2610         DrawPosition(TRUE, NULL);
2611     }
2612     return TRUE;
2613 }
2614
2615 void
2616 read_from_ics(isr, closure, data, count, error)
2617      InputSourceRef isr;
2618      VOIDSTAR closure;
2619      char *data;
2620      int count;
2621      int error;
2622 {
2623 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2624 #define STARTED_NONE 0
2625 #define STARTED_MOVES 1
2626 #define STARTED_BOARD 2
2627 #define STARTED_OBSERVE 3
2628 #define STARTED_HOLDINGS 4
2629 #define STARTED_CHATTER 5
2630 #define STARTED_COMMENT 6
2631 #define STARTED_MOVES_NOHIDE 7
2632
2633     static int started = STARTED_NONE;
2634     static char parse[20000];
2635     static int parse_pos = 0;
2636     static char buf[BUF_SIZE + 1];
2637     static int firstTime = TRUE, intfSet = FALSE;
2638     static ColorClass prevColor = ColorNormal;
2639     static int savingComment = FALSE;
2640     static int cmatch = 0; // continuation sequence match
2641     char *bp;
2642     char str[MSG_SIZ];
2643     int i, oldi;
2644     int buf_len;
2645     int next_out;
2646     int tkind;
2647     int backup;    /* [DM] For zippy color lines */
2648     char *p;
2649     char talker[MSG_SIZ]; // [HGM] chat
2650     int channel;
2651
2652     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2653
2654     if (appData.debugMode) {
2655       if (!error) {
2656         fprintf(debugFP, "<ICS: ");
2657         show_bytes(debugFP, data, count);
2658         fprintf(debugFP, "\n");
2659       }
2660     }
2661
2662     if (appData.debugMode) { int f = forwardMostMove;
2663         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2664                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2665                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2666     }
2667     if (count > 0) {
2668         /* If last read ended with a partial line that we couldn't parse,
2669            prepend it to the new read and try again. */
2670         if (leftover_len > 0) {
2671             for (i=0; i<leftover_len; i++)
2672               buf[i] = buf[leftover_start + i];
2673         }
2674
2675     /* copy new characters into the buffer */
2676     bp = buf + leftover_len;
2677     buf_len=leftover_len;
2678     for (i=0; i<count; i++)
2679     {
2680         // ignore these
2681         if (data[i] == '\r')
2682             continue;
2683
2684         // join lines split by ICS?
2685         if (!appData.noJoin)
2686         {
2687             /*
2688                 Joining just consists of finding matches against the
2689                 continuation sequence, and discarding that sequence
2690                 if found instead of copying it.  So, until a match
2691                 fails, there's nothing to do since it might be the
2692                 complete sequence, and thus, something we don't want
2693                 copied.
2694             */
2695             if (data[i] == cont_seq[cmatch])
2696             {
2697                 cmatch++;
2698                 if (cmatch == strlen(cont_seq))
2699                 {
2700                     cmatch = 0; // complete match.  just reset the counter
2701
2702                     /*
2703                         it's possible for the ICS to not include the space
2704                         at the end of the last word, making our [correct]
2705                         join operation fuse two separate words.  the server
2706                         does this when the space occurs at the width setting.
2707                     */
2708                     if (!buf_len || buf[buf_len-1] != ' ')
2709                     {
2710                         *bp++ = ' ';
2711                         buf_len++;
2712                     }
2713                 }
2714                 continue;
2715             }
2716             else if (cmatch)
2717             {
2718                 /*
2719                     match failed, so we have to copy what matched before
2720                     falling through and copying this character.  In reality,
2721                     this will only ever be just the newline character, but
2722                     it doesn't hurt to be precise.
2723                 */
2724                 strncpy(bp, cont_seq, cmatch);
2725                 bp += cmatch;
2726                 buf_len += cmatch;
2727                 cmatch = 0;
2728             }
2729         }
2730
2731         // copy this char
2732         *bp++ = data[i];
2733         buf_len++;
2734     }
2735
2736         buf[buf_len] = NULLCHAR;
2737 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2738         next_out = 0;
2739         leftover_start = 0;
2740
2741         i = 0;
2742         while (i < buf_len) {
2743             /* Deal with part of the TELNET option negotiation
2744                protocol.  We refuse to do anything beyond the
2745                defaults, except that we allow the WILL ECHO option,
2746                which ICS uses to turn off password echoing when we are
2747                directly connected to it.  We reject this option
2748                if localLineEditing mode is on (always on in xboard)
2749                and we are talking to port 23, which might be a real
2750                telnet server that will try to keep WILL ECHO on permanently.
2751              */
2752             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2753                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2754                 unsigned char option;
2755                 oldi = i;
2756                 switch ((unsigned char) buf[++i]) {
2757                   case TN_WILL:
2758                     if (appData.debugMode)
2759                       fprintf(debugFP, "\n<WILL ");
2760                     switch (option = (unsigned char) buf[++i]) {
2761                       case TN_ECHO:
2762                         if (appData.debugMode)
2763                           fprintf(debugFP, "ECHO ");
2764                         /* Reply only if this is a change, according
2765                            to the protocol rules. */
2766                         if (remoteEchoOption) break;
2767                         if (appData.localLineEditing &&
2768                             atoi(appData.icsPort) == TN_PORT) {
2769                             TelnetRequest(TN_DONT, TN_ECHO);
2770                         } else {
2771                             EchoOff();
2772                             TelnetRequest(TN_DO, TN_ECHO);
2773                             remoteEchoOption = TRUE;
2774                         }
2775                         break;
2776                       default:
2777                         if (appData.debugMode)
2778                           fprintf(debugFP, "%d ", option);
2779                         /* Whatever this is, we don't want it. */
2780                         TelnetRequest(TN_DONT, option);
2781                         break;
2782                     }
2783                     break;
2784                   case TN_WONT:
2785                     if (appData.debugMode)
2786                       fprintf(debugFP, "\n<WONT ");
2787                     switch (option = (unsigned char) buf[++i]) {
2788                       case TN_ECHO:
2789                         if (appData.debugMode)
2790                           fprintf(debugFP, "ECHO ");
2791                         /* Reply only if this is a change, according
2792                            to the protocol rules. */
2793                         if (!remoteEchoOption) break;
2794                         EchoOn();
2795                         TelnetRequest(TN_DONT, TN_ECHO);
2796                         remoteEchoOption = FALSE;
2797                         break;
2798                       default:
2799                         if (appData.debugMode)
2800                           fprintf(debugFP, "%d ", (unsigned char) option);
2801                         /* Whatever this is, it must already be turned
2802                            off, because we never agree to turn on
2803                            anything non-default, so according to the
2804                            protocol rules, we don't reply. */
2805                         break;
2806                     }
2807                     break;
2808                   case TN_DO:
2809                     if (appData.debugMode)
2810                       fprintf(debugFP, "\n<DO ");
2811                     switch (option = (unsigned char) buf[++i]) {
2812                       default:
2813                         /* Whatever this is, we refuse to do it. */
2814                         if (appData.debugMode)
2815                           fprintf(debugFP, "%d ", option);
2816                         TelnetRequest(TN_WONT, option);
2817                         break;
2818                     }
2819                     break;
2820                   case TN_DONT:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<DONT ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       default:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "%d ", option);
2827                         /* Whatever this is, we are already not doing
2828                            it, because we never agree to do anything
2829                            non-default, so according to the protocol
2830                            rules, we don't reply. */
2831                         break;
2832                     }
2833                     break;
2834                   case TN_IAC:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<IAC ");
2837                     /* Doubled IAC; pass it through */
2838                     i--;
2839                     break;
2840                   default:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2843                     /* Drop all other telnet commands on the floor */
2844                     break;
2845                 }
2846                 if (oldi > next_out)
2847                   SendToPlayer(&buf[next_out], oldi - next_out);
2848                 if (++i > next_out)
2849                   next_out = i;
2850                 continue;
2851             }
2852
2853             /* OK, this at least will *usually* work */
2854             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2855                 loggedOn = TRUE;
2856             }
2857
2858             if (loggedOn && !intfSet) {
2859                 if (ics_type == ICS_ICC) {
2860                   snprintf(str, MSG_SIZ,
2861                           "/set-quietly interface %s\n/set-quietly style 12\n",
2862                           programVersion);
2863                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2865                 } else if (ics_type == ICS_CHESSNET) {
2866                   snprintf(str, MSG_SIZ, "/style 12\n");
2867                 } else {
2868                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2869                   strcat(str, programVersion);
2870                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2871                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2872                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2873 #ifdef WIN32
2874                   strcat(str, "$iset nohighlight 1\n");
2875 #endif
2876                   strcat(str, "$iset lock 1\n$style 12\n");
2877                 }
2878                 SendToICS(str);
2879                 NotifyFrontendLogin();
2880                 intfSet = TRUE;
2881             }
2882
2883             if (started == STARTED_COMMENT) {
2884                 /* Accumulate characters in comment */
2885                 parse[parse_pos++] = buf[i];
2886                 if (buf[i] == '\n') {
2887                     parse[parse_pos] = NULLCHAR;
2888                     if(chattingPartner>=0) {
2889                         char mess[MSG_SIZ];
2890                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2891                         OutputChatMessage(chattingPartner, mess);
2892                         chattingPartner = -1;
2893                         next_out = i+1; // [HGM] suppress printing in ICS window
2894                     } else
2895                     if(!suppressKibitz) // [HGM] kibitz
2896                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2897                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2898                         int nrDigit = 0, nrAlph = 0, j;
2899                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2900                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2901                         parse[parse_pos] = NULLCHAR;
2902                         // try to be smart: if it does not look like search info, it should go to
2903                         // ICS interaction window after all, not to engine-output window.
2904                         for(j=0; j<parse_pos; j++) { // count letters and digits
2905                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2906                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2907                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2908                         }
2909                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2910                             int depth=0; float score;
2911                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2912                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2913                                 pvInfoList[forwardMostMove-1].depth = depth;
2914                                 pvInfoList[forwardMostMove-1].score = 100*score;
2915                             }
2916                             OutputKibitz(suppressKibitz, parse);
2917                         } else {
2918                             char tmp[MSG_SIZ];
2919                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2920                             SendToPlayer(tmp, strlen(tmp));
2921                         }
2922                         next_out = i+1; // [HGM] suppress printing in ICS window
2923                     }
2924                     started = STARTED_NONE;
2925                 } else {
2926                     /* Don't match patterns against characters in comment */
2927                     i++;
2928                     continue;
2929                 }
2930             }
2931             if (started == STARTED_CHATTER) {
2932                 if (buf[i] != '\n') {
2933                     /* Don't match patterns against characters in chatter */
2934                     i++;
2935                     continue;
2936                 }
2937                 started = STARTED_NONE;
2938                 if(suppressKibitz) next_out = i+1;
2939             }
2940
2941             /* Kludge to deal with rcmd protocol */
2942             if (firstTime && looking_at(buf, &i, "\001*")) {
2943                 DisplayFatalError(&buf[1], 0, 1);
2944                 continue;
2945             } else {
2946                 firstTime = FALSE;
2947             }
2948
2949             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2950                 ics_type = ICS_ICC;
2951                 ics_prefix = "/";
2952                 if (appData.debugMode)
2953                   fprintf(debugFP, "ics_type %d\n", ics_type);
2954                 continue;
2955             }
2956             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2957                 ics_type = ICS_FICS;
2958                 ics_prefix = "$";
2959                 if (appData.debugMode)
2960                   fprintf(debugFP, "ics_type %d\n", ics_type);
2961                 continue;
2962             }
2963             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2964                 ics_type = ICS_CHESSNET;
2965                 ics_prefix = "/";
2966                 if (appData.debugMode)
2967                   fprintf(debugFP, "ics_type %d\n", ics_type);
2968                 continue;
2969             }
2970
2971             if (!loggedOn &&
2972                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2973                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2974                  looking_at(buf, &i, "will be \"*\""))) {
2975               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2976               continue;
2977             }
2978
2979             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2980               char buf[MSG_SIZ];
2981               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2982               DisplayIcsInteractionTitle(buf);
2983               have_set_title = TRUE;
2984             }
2985
2986             /* skip finger notes */
2987             if (started == STARTED_NONE &&
2988                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2989                  (buf[i] == '1' && buf[i+1] == '0')) &&
2990                 buf[i+2] == ':' && buf[i+3] == ' ') {
2991               started = STARTED_CHATTER;
2992               i += 3;
2993               continue;
2994             }
2995
2996             oldi = i;
2997             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2998             if(appData.seekGraph) {
2999                 if(soughtPending && MatchSoughtLine(buf+i)) {
3000                     i = strstr(buf+i, "rated") - buf;
3001                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3002                     next_out = leftover_start = i;
3003                     started = STARTED_CHATTER;
3004                     suppressKibitz = TRUE;
3005                     continue;
3006                 }
3007                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3008                         && looking_at(buf, &i, "* ads displayed")) {
3009                     soughtPending = FALSE;
3010                     seekGraphUp = TRUE;
3011                     DrawSeekGraph();
3012                     continue;
3013                 }
3014                 if(appData.autoRefresh) {
3015                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3016                         int s = (ics_type == ICS_ICC); // ICC format differs
3017                         if(seekGraphUp)
3018                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3019                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3020                         looking_at(buf, &i, "*% "); // eat prompt
3021                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3022                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3023                         next_out = i; // suppress
3024                         continue;
3025                     }
3026                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3027                         char *p = star_match[0];
3028                         while(*p) {
3029                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3030                             while(*p && *p++ != ' '); // next
3031                         }
3032                         looking_at(buf, &i, "*% "); // eat prompt
3033                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3034                         next_out = i;
3035                         continue;
3036                     }
3037                 }
3038             }
3039
3040             /* skip formula vars */
3041             if (started == STARTED_NONE &&
3042                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3043               started = STARTED_CHATTER;
3044               i += 3;
3045               continue;
3046             }
3047
3048             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3049             if (appData.autoKibitz && started == STARTED_NONE &&
3050                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3051                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3052                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3053                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3054                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3055                         suppressKibitz = TRUE;
3056                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3057                         next_out = i;
3058                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3059                                 && (gameMode == IcsPlayingWhite)) ||
3060                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3061                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3062                             started = STARTED_CHATTER; // own kibitz we simply discard
3063                         else {
3064                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3065                             parse_pos = 0; parse[0] = NULLCHAR;
3066                             savingComment = TRUE;
3067                             suppressKibitz = gameMode != IcsObserving ? 2 :
3068                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3069                         }
3070                         continue;
3071                 } else
3072                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3073                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3074                          && atoi(star_match[0])) {
3075                     // suppress the acknowledgements of our own autoKibitz
3076                     char *p;
3077                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3079                     SendToPlayer(star_match[0], strlen(star_match[0]));
3080                     if(looking_at(buf, &i, "*% ")) // eat prompt
3081                         suppressKibitz = FALSE;
3082                     next_out = i;
3083                     continue;
3084                 }
3085             } // [HGM] kibitz: end of patch
3086
3087             // [HGM] chat: intercept tells by users for which we have an open chat window
3088             channel = -1;
3089             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3090                                            looking_at(buf, &i, "* whispers:") ||
3091                                            looking_at(buf, &i, "* kibitzes:") ||
3092                                            looking_at(buf, &i, "* shouts:") ||
3093                                            looking_at(buf, &i, "* c-shouts:") ||
3094                                            looking_at(buf, &i, "--> * ") ||
3095                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3096                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3097                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3098                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3099                 int p;
3100                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3101                 chattingPartner = -1;
3102
3103                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3104                 for(p=0; p<MAX_CHAT; p++) {
3105                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3106                     talker[0] = '['; strcat(talker, "] ");
3107                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3108                     chattingPartner = p; break;
3109                     }
3110                 } else
3111                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3112                 for(p=0; p<MAX_CHAT; p++) {
3113                     if(!strcmp("kibitzes", chatPartner[p])) {
3114                         talker[0] = '['; strcat(talker, "] ");
3115                         chattingPartner = p; break;
3116                     }
3117                 } else
3118                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3119                 for(p=0; p<MAX_CHAT; p++) {
3120                     if(!strcmp("whispers", chatPartner[p])) {
3121                         talker[0] = '['; strcat(talker, "] ");
3122                         chattingPartner = p; break;
3123                     }
3124                 } else
3125                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3126                   if(buf[i-8] == '-' && buf[i-3] == 't')
3127                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3128                     if(!strcmp("c-shouts", chatPartner[p])) {
3129                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3130                         chattingPartner = p; break;
3131                     }
3132                   }
3133                   if(chattingPartner < 0)
3134                   for(p=0; p<MAX_CHAT; p++) {
3135                     if(!strcmp("shouts", chatPartner[p])) {
3136                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3137                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3138                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3139                         chattingPartner = p; break;
3140                     }
3141                   }
3142                 }
3143                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3144                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3145                     talker[0] = 0; Colorize(ColorTell, FALSE);
3146                     chattingPartner = p; break;
3147                 }
3148                 if(chattingPartner<0) i = oldi; else {
3149                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3150                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3151                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                     started = STARTED_COMMENT;
3153                     parse_pos = 0; parse[0] = NULLCHAR;
3154                     savingComment = 3 + chattingPartner; // counts as TRUE
3155                     suppressKibitz = TRUE;
3156                     continue;
3157                 }
3158             } // [HGM] chat: end of patch
3159
3160           backup = i;
3161             if (appData.zippyTalk || appData.zippyPlay) {
3162                 /* [DM] Backup address for color zippy lines */
3163 #if ZIPPY
3164                if (loggedOn == TRUE)
3165                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3166                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3167 #endif
3168             } // [DM] 'else { ' deleted
3169                 if (
3170                     /* Regular tells and says */
3171                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3172                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3173                     looking_at(buf, &i, "* says: ") ||
3174                     /* Don't color "message" or "messages" output */
3175                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3176                     looking_at(buf, &i, "*. * at *:*: ") ||
3177                     looking_at(buf, &i, "--* (*:*): ") ||
3178                     /* Message notifications (same color as tells) */
3179                     looking_at(buf, &i, "* has left a message ") ||
3180                     looking_at(buf, &i, "* just sent you a message:\n") ||
3181                     /* Whispers and kibitzes */
3182                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3183                     looking_at(buf, &i, "* kibitzes: ") ||
3184                     /* Channel tells */
3185                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3186
3187                   if (tkind == 1 && strchr(star_match[0], ':')) {
3188                       /* Avoid "tells you:" spoofs in channels */
3189                      tkind = 3;
3190                   }
3191                   if (star_match[0][0] == NULLCHAR ||
3192                       strchr(star_match[0], ' ') ||
3193                       (tkind == 3 && strchr(star_match[1], ' '))) {
3194                     /* Reject bogus matches */
3195                     i = oldi;
3196                   } else {
3197                     if (appData.colorize) {
3198                       if (oldi > next_out) {
3199                         SendToPlayer(&buf[next_out], oldi - next_out);
3200                         next_out = oldi;
3201                       }
3202                       switch (tkind) {
3203                       case 1:
3204                         Colorize(ColorTell, FALSE);
3205                         curColor = ColorTell;
3206                         break;
3207                       case 2:
3208                         Colorize(ColorKibitz, FALSE);
3209                         curColor = ColorKibitz;
3210                         break;
3211                       case 3:
3212                         p = strrchr(star_match[1], '(');
3213                         if (p == NULL) {
3214                           p = star_match[1];
3215                         } else {
3216                           p++;
3217                         }
3218                         if (atoi(p) == 1) {
3219                           Colorize(ColorChannel1, FALSE);
3220                           curColor = ColorChannel1;
3221                         } else {
3222                           Colorize(ColorChannel, FALSE);
3223                           curColor = ColorChannel;
3224                         }
3225                         break;
3226                       case 5:
3227                         curColor = ColorNormal;
3228                         break;
3229                       }
3230                     }
3231                     if (started == STARTED_NONE && appData.autoComment &&
3232                         (gameMode == IcsObserving ||
3233                          gameMode == IcsPlayingWhite ||
3234                          gameMode == IcsPlayingBlack)) {
3235                       parse_pos = i - oldi;
3236                       memcpy(parse, &buf[oldi], parse_pos);
3237                       parse[parse_pos] = NULLCHAR;
3238                       started = STARTED_COMMENT;
3239                       savingComment = TRUE;
3240                     } else {
3241                       started = STARTED_CHATTER;
3242                       savingComment = FALSE;
3243                     }
3244                     loggedOn = TRUE;
3245                     continue;
3246                   }
3247                 }
3248
3249                 if (looking_at(buf, &i, "* s-shouts: ") ||
3250                     looking_at(buf, &i, "* c-shouts: ")) {
3251                     if (appData.colorize) {
3252                         if (oldi > next_out) {
3253                             SendToPlayer(&buf[next_out], oldi - next_out);
3254                             next_out = oldi;
3255                         }
3256                         Colorize(ColorSShout, FALSE);
3257                         curColor = ColorSShout;
3258                     }
3259                     loggedOn = TRUE;
3260                     started = STARTED_CHATTER;
3261                     continue;
3262                 }
3263
3264                 if (looking_at(buf, &i, "--->")) {
3265                     loggedOn = TRUE;
3266                     continue;
3267                 }
3268
3269                 if (looking_at(buf, &i, "* shouts: ") ||
3270                     looking_at(buf, &i, "--> ")) {
3271                     if (appData.colorize) {
3272                         if (oldi > next_out) {
3273                             SendToPlayer(&buf[next_out], oldi - next_out);
3274                             next_out = oldi;
3275                         }
3276                         Colorize(ColorShout, FALSE);
3277                         curColor = ColorShout;
3278                     }
3279                     loggedOn = TRUE;
3280                     started = STARTED_CHATTER;
3281                     continue;
3282                 }
3283
3284                 if (looking_at( buf, &i, "Challenge:")) {
3285                     if (appData.colorize) {
3286                         if (oldi > next_out) {
3287                             SendToPlayer(&buf[next_out], oldi - next_out);
3288                             next_out = oldi;
3289                         }
3290                         Colorize(ColorChallenge, FALSE);
3291                         curColor = ColorChallenge;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                 }
3296
3297                 if (looking_at(buf, &i, "* offers you") ||
3298                     looking_at(buf, &i, "* offers to be") ||
3299                     looking_at(buf, &i, "* would like to") ||
3300                     looking_at(buf, &i, "* requests to") ||
3301                     looking_at(buf, &i, "Your opponent offers") ||
3302                     looking_at(buf, &i, "Your opponent requests")) {
3303
3304                     if (appData.colorize) {
3305                         if (oldi > next_out) {
3306                             SendToPlayer(&buf[next_out], oldi - next_out);
3307                             next_out = oldi;
3308                         }
3309                         Colorize(ColorRequest, FALSE);
3310                         curColor = ColorRequest;
3311                     }
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "* (*) seeking")) {
3316                     if (appData.colorize) {
3317                         if (oldi > next_out) {
3318                             SendToPlayer(&buf[next_out], oldi - next_out);
3319                             next_out = oldi;
3320                         }
3321                         Colorize(ColorSeek, FALSE);
3322                         curColor = ColorSeek;
3323                     }
3324                     continue;
3325             }
3326
3327           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3328
3329             if (looking_at(buf, &i, "\\   ")) {
3330                 if (prevColor != ColorNormal) {
3331                     if (oldi > next_out) {
3332                         SendToPlayer(&buf[next_out], oldi - next_out);
3333                         next_out = oldi;
3334                     }
3335                     Colorize(prevColor, TRUE);
3336                     curColor = prevColor;
3337                 }
3338                 if (savingComment) {
3339                     parse_pos = i - oldi;
3340                     memcpy(parse, &buf[oldi], parse_pos);
3341                     parse[parse_pos] = NULLCHAR;
3342                     started = STARTED_COMMENT;
3343                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3344                         chattingPartner = savingComment - 3; // kludge to remember the box
3345                 } else {
3346                     started = STARTED_CHATTER;
3347                 }
3348                 continue;
3349             }
3350
3351             if (looking_at(buf, &i, "Black Strength :") ||
3352                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3353                 looking_at(buf, &i, "<10>") ||
3354                 looking_at(buf, &i, "#@#")) {
3355                 /* Wrong board style */
3356                 loggedOn = TRUE;
3357                 SendToICS(ics_prefix);
3358                 SendToICS("set style 12\n");
3359                 SendToICS(ics_prefix);
3360                 SendToICS("refresh\n");
3361                 continue;
3362             }
3363
3364             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3365                 ICSInitScript();
3366                 have_sent_ICS_logon = 1;
3367                 continue;
3368             }
3369
3370             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3371                 (looking_at(buf, &i, "\n<12> ") ||
3372                  looking_at(buf, &i, "<12> "))) {
3373                 loggedOn = TRUE;
3374                 if (oldi > next_out) {
3375                     SendToPlayer(&buf[next_out], oldi - next_out);
3376                 }
3377                 next_out = i;
3378                 started = STARTED_BOARD;
3379                 parse_pos = 0;
3380                 continue;
3381             }
3382
3383             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3384                 looking_at(buf, &i, "<b1> ")) {
3385                 if (oldi > next_out) {
3386                     SendToPlayer(&buf[next_out], oldi - next_out);
3387                 }
3388                 next_out = i;
3389                 started = STARTED_HOLDINGS;
3390                 parse_pos = 0;
3391                 continue;
3392             }
3393
3394             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3395                 loggedOn = TRUE;
3396                 /* Header for a move list -- first line */
3397
3398                 switch (ics_getting_history) {
3399                   case H_FALSE:
3400                     switch (gameMode) {
3401                       case IcsIdle:
3402                       case BeginningOfGame:
3403                         /* User typed "moves" or "oldmoves" while we
3404                            were idle.  Pretend we asked for these
3405                            moves and soak them up so user can step
3406                            through them and/or save them.
3407                            */
3408                         Reset(FALSE, TRUE);
3409                         gameMode = IcsObserving;
3410                         ModeHighlight();
3411                         ics_gamenum = -1;
3412                         ics_getting_history = H_GOT_UNREQ_HEADER;
3413                         break;
3414                       case EditGame: /*?*/
3415                       case EditPosition: /*?*/
3416                         /* Should above feature work in these modes too? */
3417                         /* For now it doesn't */
3418                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3419                         break;
3420                       default:
3421                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3422                         break;
3423                     }
3424                     break;
3425                   case H_REQUESTED:
3426                     /* Is this the right one? */
3427                     if (gameInfo.white && gameInfo.black &&
3428                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3429                         strcmp(gameInfo.black, star_match[2]) == 0) {
3430                         /* All is well */
3431                         ics_getting_history = H_GOT_REQ_HEADER;
3432                     }
3433                     break;
3434                   case H_GOT_REQ_HEADER:
3435                   case H_GOT_UNREQ_HEADER:
3436                   case H_GOT_UNWANTED_HEADER:
3437                   case H_GETTING_MOVES:
3438                     /* Should not happen */
3439                     DisplayError(_("Error gathering move list: two headers"), 0);
3440                     ics_getting_history = H_FALSE;
3441                     break;
3442                 }
3443
3444                 /* Save player ratings into gameInfo if needed */
3445                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3446                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3447                     (gameInfo.whiteRating == -1 ||
3448                      gameInfo.blackRating == -1)) {
3449
3450                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3451                     gameInfo.blackRating = string_to_rating(star_match[3]);
3452                     if (appData.debugMode)
3453                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3454                               gameInfo.whiteRating, gameInfo.blackRating);
3455                 }
3456                 continue;
3457             }
3458
3459             if (looking_at(buf, &i,
3460               "* * match, initial time: * minute*, increment: * second")) {
3461                 /* Header for a move list -- second line */
3462                 /* Initial board will follow if this is a wild game */
3463                 if (gameInfo.event != NULL) free(gameInfo.event);
3464                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3465                 gameInfo.event = StrSave(str);
3466                 /* [HGM] we switched variant. Translate boards if needed. */
3467                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3468                 continue;
3469             }
3470
3471             if (looking_at(buf, &i, "Move  ")) {
3472                 /* Beginning of a move list */
3473                 switch (ics_getting_history) {
3474                   case H_FALSE:
3475                     /* Normally should not happen */
3476                     /* Maybe user hit reset while we were parsing */
3477                     break;
3478                   case H_REQUESTED:
3479                     /* Happens if we are ignoring a move list that is not
3480                      * the one we just requested.  Common if the user
3481                      * tries to observe two games without turning off
3482                      * getMoveList */
3483                     break;
3484                   case H_GETTING_MOVES:
3485                     /* Should not happen */
3486                     DisplayError(_("Error gathering move list: nested"), 0);
3487                     ics_getting_history = H_FALSE;
3488                     break;
3489                   case H_GOT_REQ_HEADER:
3490                     ics_getting_history = H_GETTING_MOVES;
3491                     started = STARTED_MOVES;
3492                     parse_pos = 0;
3493                     if (oldi > next_out) {
3494                         SendToPlayer(&buf[next_out], oldi - next_out);
3495                     }
3496                     break;
3497                   case H_GOT_UNREQ_HEADER:
3498                     ics_getting_history = H_GETTING_MOVES;
3499                     started = STARTED_MOVES_NOHIDE;
3500                     parse_pos = 0;
3501                     break;
3502                   case H_GOT_UNWANTED_HEADER:
3503                     ics_getting_history = H_FALSE;
3504                     break;
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i, "% ") ||
3510                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3511                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3512                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3513                     soughtPending = FALSE;
3514                     seekGraphUp = TRUE;
3515                     DrawSeekGraph();
3516                 }
3517                 if(suppressKibitz) next_out = i;
3518                 savingComment = FALSE;
3519                 suppressKibitz = 0;
3520                 switch (started) {
3521                   case STARTED_MOVES:
3522                   case STARTED_MOVES_NOHIDE:
3523                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3524                     parse[parse_pos + i - oldi] = NULLCHAR;
3525                     ParseGameHistory(parse);
3526 #if ZIPPY
3527                     if (appData.zippyPlay && first.initDone) {
3528                         FeedMovesToProgram(&first, forwardMostMove);
3529                         if (gameMode == IcsPlayingWhite) {
3530                             if (WhiteOnMove(forwardMostMove)) {
3531                                 if (first.sendTime) {
3532                                   if (first.useColors) {
3533                                     SendToProgram("black\n", &first);
3534                                   }
3535                                   SendTimeRemaining(&first, TRUE);
3536                                 }
3537                                 if (first.useColors) {
3538                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3539                                 }
3540                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3541                                 first.maybeThinking = TRUE;
3542                             } else {
3543                                 if (first.usePlayother) {
3544                                   if (first.sendTime) {
3545                                     SendTimeRemaining(&first, TRUE);
3546                                   }
3547                                   SendToProgram("playother\n", &first);
3548                                   firstMove = FALSE;
3549                                 } else {
3550                                   firstMove = TRUE;
3551                                 }
3552                             }
3553                         } else if (gameMode == IcsPlayingBlack) {
3554                             if (!WhiteOnMove(forwardMostMove)) {
3555                                 if (first.sendTime) {
3556                                   if (first.useColors) {
3557                                     SendToProgram("white\n", &first);
3558                                   }
3559                                   SendTimeRemaining(&first, FALSE);
3560                                 }
3561                                 if (first.useColors) {
3562                                   SendToProgram("black\n", &first);
3563                                 }
3564                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3565                                 first.maybeThinking = TRUE;
3566                             } else {
3567                                 if (first.usePlayother) {
3568                                   if (first.sendTime) {
3569                                     SendTimeRemaining(&first, FALSE);
3570                                   }
3571                                   SendToProgram("playother\n", &first);
3572                                   firstMove = FALSE;
3573                                 } else {
3574                                   firstMove = TRUE;
3575                                 }
3576                             }
3577                         }
3578                     }
3579 #endif
3580                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3581                         /* Moves came from oldmoves or moves command
3582                            while we weren't doing anything else.
3583                            */
3584                         currentMove = forwardMostMove;
3585                         ClearHighlights();/*!!could figure this out*/
3586                         flipView = appData.flipView;
3587                         DrawPosition(TRUE, boards[currentMove]);
3588                         DisplayBothClocks();
3589                         snprintf(str, MSG_SIZ, "%s vs. %s",
3590                                 gameInfo.white, gameInfo.black);
3591                         DisplayTitle(str);
3592                         gameMode = IcsIdle;
3593                     } else {
3594                         /* Moves were history of an active game */
3595                         if (gameInfo.resultDetails != NULL) {
3596                             free(gameInfo.resultDetails);
3597                             gameInfo.resultDetails = NULL;
3598                         }
3599                     }
3600                     HistorySet(parseList, backwardMostMove,
3601                                forwardMostMove, currentMove-1);
3602                     DisplayMove(currentMove - 1);
3603                     if (started == STARTED_MOVES) next_out = i;
3604                     started = STARTED_NONE;
3605                     ics_getting_history = H_FALSE;
3606                     break;
3607
3608                   case STARTED_OBSERVE:
3609                     started = STARTED_NONE;
3610                     SendToICS(ics_prefix);
3611                     SendToICS("refresh\n");
3612                     break;
3613
3614                   default:
3615                     break;
3616                 }
3617                 if(bookHit) { // [HGM] book: simulate book reply
3618                     static char bookMove[MSG_SIZ]; // a bit generous?
3619
3620                     programStats.nodes = programStats.depth = programStats.time =
3621                     programStats.score = programStats.got_only_move = 0;
3622                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3623
3624                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3625                     strcat(bookMove, bookHit);
3626                     HandleMachineMove(bookMove, &first);
3627                 }
3628                 continue;
3629             }
3630
3631             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3632                  started == STARTED_HOLDINGS ||
3633                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3634                 /* Accumulate characters in move list or board */
3635                 parse[parse_pos++] = buf[i];
3636             }
3637
3638             /* Start of game messages.  Mostly we detect start of game
3639                when the first board image arrives.  On some versions
3640                of the ICS, though, we need to do a "refresh" after starting
3641                to observe in order to get the current board right away. */
3642             if (looking_at(buf, &i, "Adding game * to observation list")) {
3643                 started = STARTED_OBSERVE;
3644                 continue;
3645             }
3646
3647             /* Handle auto-observe */
3648             if (appData.autoObserve &&
3649                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3650                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3651                 char *player;
3652                 /* Choose the player that was highlighted, if any. */
3653                 if (star_match[0][0] == '\033' ||
3654                     star_match[1][0] != '\033') {
3655                     player = star_match[0];
3656                 } else {
3657                     player = star_match[2];
3658                 }
3659                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3660                         ics_prefix, StripHighlightAndTitle(player));
3661                 SendToICS(str);
3662
3663                 /* Save ratings from notify string */
3664                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3665                 player1Rating = string_to_rating(star_match[1]);
3666                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3667                 player2Rating = string_to_rating(star_match[3]);
3668
3669                 if (appData.debugMode)
3670                   fprintf(debugFP,
3671                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3672                           player1Name, player1Rating,
3673                           player2Name, player2Rating);
3674
3675                 continue;
3676             }
3677
3678             /* Deal with automatic examine mode after a game,
3679                and with IcsObserving -> IcsExamining transition */
3680             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3681                 looking_at(buf, &i, "has made you an examiner of game *")) {
3682
3683                 int gamenum = atoi(star_match[0]);
3684                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3685                     gamenum == ics_gamenum) {
3686                     /* We were already playing or observing this game;
3687                        no need to refetch history */
3688                     gameMode = IcsExamining;
3689                     if (pausing) {
3690                         pauseExamForwardMostMove = forwardMostMove;
3691                     } else if (currentMove < forwardMostMove) {
3692                         ForwardInner(forwardMostMove);
3693                     }
3694                 } else {
3695                     /* I don't think this case really can happen */
3696                     SendToICS(ics_prefix);
3697                     SendToICS("refresh\n");
3698                 }
3699                 continue;
3700             }
3701
3702             /* Error messages */
3703 //          if (ics_user_moved) {
3704             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3705                 if (looking_at(buf, &i, "Illegal move") ||
3706                     looking_at(buf, &i, "Not a legal move") ||
3707                     looking_at(buf, &i, "Your king is in check") ||
3708                     looking_at(buf, &i, "It isn't your turn") ||
3709                     looking_at(buf, &i, "It is not your move")) {
3710                     /* Illegal move */
3711                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3712                         currentMove = forwardMostMove-1;
3713                         DisplayMove(currentMove - 1); /* before DMError */
3714                         DrawPosition(FALSE, boards[currentMove]);
3715                         SwitchClocks(forwardMostMove-1); // [HGM] race
3716                         DisplayBothClocks();
3717                     }
3718                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3719                     ics_user_moved = 0;
3720                     continue;
3721                 }
3722             }
3723
3724             if (looking_at(buf, &i, "still have time") ||
3725                 looking_at(buf, &i, "not out of time") ||
3726                 looking_at(buf, &i, "either player is out of time") ||
3727                 looking_at(buf, &i, "has timeseal; checking")) {
3728                 /* We must have called his flag a little too soon */
3729                 whiteFlag = blackFlag = FALSE;
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "added * seconds to") ||
3734                 looking_at(buf, &i, "seconds were added to")) {
3735                 /* Update the clocks */
3736                 SendToICS(ics_prefix);
3737                 SendToICS("refresh\n");
3738                 continue;
3739             }
3740
3741             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3742                 ics_clock_paused = TRUE;
3743                 StopClocks();
3744                 continue;
3745             }
3746
3747             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3748                 ics_clock_paused = FALSE;
3749                 StartClocks();
3750                 continue;
3751             }
3752
3753             /* Grab player ratings from the Creating: message.
3754                Note we have to check for the special case when
3755                the ICS inserts things like [white] or [black]. */
3756             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3757                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3758                 /* star_matches:
3759                    0    player 1 name (not necessarily white)
3760                    1    player 1 rating
3761                    2    empty, white, or black (IGNORED)
3762                    3    player 2 name (not necessarily black)
3763                    4    player 2 rating
3764
3765                    The names/ratings are sorted out when the game
3766                    actually starts (below).
3767                 */
3768                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3769                 player1Rating = string_to_rating(star_match[1]);
3770                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3771                 player2Rating = string_to_rating(star_match[4]);
3772
3773                 if (appData.debugMode)
3774                   fprintf(debugFP,
3775                           "Ratings from 'Creating:' %s %d, %s %d\n",
3776                           player1Name, player1Rating,
3777                           player2Name, player2Rating);
3778
3779                 continue;
3780             }
3781
3782             /* Improved generic start/end-of-game messages */
3783             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3784                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3785                 /* If tkind == 0: */
3786                 /* star_match[0] is the game number */
3787                 /*           [1] is the white player's name */
3788                 /*           [2] is the black player's name */
3789                 /* For end-of-game: */
3790                 /*           [3] is the reason for the game end */
3791                 /*           [4] is a PGN end game-token, preceded by " " */
3792                 /* For start-of-game: */
3793                 /*           [3] begins with "Creating" or "Continuing" */
3794                 /*           [4] is " *" or empty (don't care). */
3795                 int gamenum = atoi(star_match[0]);
3796                 char *whitename, *blackname, *why, *endtoken;
3797                 ChessMove endtype = EndOfFile;
3798
3799                 if (tkind == 0) {
3800                   whitename = star_match[1];
3801                   blackname = star_match[2];
3802                   why = star_match[3];
3803                   endtoken = star_match[4];
3804                 } else {
3805                   whitename = star_match[1];
3806                   blackname = star_match[3];
3807                   why = star_match[5];
3808                   endtoken = star_match[6];
3809                 }
3810
3811                 /* Game start messages */
3812                 if (strncmp(why, "Creating ", 9) == 0 ||
3813                     strncmp(why, "Continuing ", 11) == 0) {
3814                     gs_gamenum = gamenum;
3815                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3816                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3817 #if ZIPPY
3818                     if (appData.zippyPlay) {
3819                         ZippyGameStart(whitename, blackname);
3820                     }
3821 #endif /*ZIPPY*/
3822                     partnerBoardValid = FALSE; // [HGM] bughouse
3823                     continue;
3824                 }
3825
3826                 /* Game end messages */
3827                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3828                     ics_gamenum != gamenum) {
3829                     continue;
3830                 }
3831                 while (endtoken[0] == ' ') endtoken++;
3832                 switch (endtoken[0]) {
3833                   case '*':
3834                   default:
3835                     endtype = GameUnfinished;
3836                     break;
3837                   case '0':
3838                     endtype = BlackWins;
3839                     break;
3840                   case '1':
3841                     if (endtoken[1] == '/')
3842                       endtype = GameIsDrawn;
3843                     else
3844                       endtype = WhiteWins;
3845                     break;
3846                 }
3847                 GameEnds(endtype, why, GE_ICS);
3848 #if ZIPPY
3849                 if (appData.zippyPlay && first.initDone) {
3850                     ZippyGameEnd(endtype, why);
3851                     if (first.pr == NULL) {
3852                       /* Start the next process early so that we'll
3853                          be ready for the next challenge */
3854                       StartChessProgram(&first);
3855                     }
3856                     /* Send "new" early, in case this command takes
3857                        a long time to finish, so that we'll be ready
3858                        for the next challenge. */
3859                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3860                     Reset(TRUE, TRUE);
3861                 }
3862 #endif /*ZIPPY*/
3863                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3864                 continue;
3865             }
3866
3867             if (looking_at(buf, &i, "Removing game * from observation") ||
3868                 looking_at(buf, &i, "no longer observing game *") ||
3869                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3870                 if (gameMode == IcsObserving &&
3871                     atoi(star_match[0]) == ics_gamenum)
3872                   {
3873                       /* icsEngineAnalyze */
3874                       if (appData.icsEngineAnalyze) {
3875                             ExitAnalyzeMode();
3876                             ModeHighlight();
3877                       }
3878                       StopClocks();
3879                       gameMode = IcsIdle;
3880                       ics_gamenum = -1;
3881                       ics_user_moved = FALSE;
3882                   }
3883                 continue;
3884             }
3885
3886             if (looking_at(buf, &i, "no longer examining game *")) {
3887                 if (gameMode == IcsExamining &&
3888                     atoi(star_match[0]) == ics_gamenum)
3889                   {
3890                       gameMode = IcsIdle;
3891                       ics_gamenum = -1;
3892                       ics_user_moved = FALSE;
3893                   }
3894                 continue;
3895             }
3896
3897             /* Advance leftover_start past any newlines we find,
3898                so only partial lines can get reparsed */
3899             if (looking_at(buf, &i, "\n")) {
3900                 prevColor = curColor;
3901                 if (curColor != ColorNormal) {
3902                     if (oldi > next_out) {
3903                         SendToPlayer(&buf[next_out], oldi - next_out);
3904                         next_out = oldi;
3905                     }
3906                     Colorize(ColorNormal, FALSE);
3907                     curColor = ColorNormal;
3908                 }
3909                 if (started == STARTED_BOARD) {
3910                     started = STARTED_NONE;
3911                     parse[parse_pos] = NULLCHAR;
3912                     ParseBoard12(parse);
3913                     ics_user_moved = 0;
3914
3915                     /* Send premove here */
3916                     if (appData.premove) {
3917                       char str[MSG_SIZ];
3918                       if (currentMove == 0 &&
3919                           gameMode == IcsPlayingWhite &&
3920                           appData.premoveWhite) {
3921                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3922                         if (appData.debugMode)
3923                           fprintf(debugFP, "Sending premove:\n");
3924                         SendToICS(str);
3925                       } else if (currentMove == 1 &&
3926                                  gameMode == IcsPlayingBlack &&
3927                                  appData.premoveBlack) {
3928                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3929                         if (appData.debugMode)
3930                           fprintf(debugFP, "Sending premove:\n");
3931                         SendToICS(str);
3932                       } else if (gotPremove) {
3933                         gotPremove = 0;
3934                         ClearPremoveHighlights();
3935                         if (appData.debugMode)
3936                           fprintf(debugFP, "Sending premove:\n");
3937                           UserMoveEvent(premoveFromX, premoveFromY,
3938                                         premoveToX, premoveToY,
3939                                         premovePromoChar);
3940                       }
3941                     }
3942
3943                     /* Usually suppress following prompt */
3944                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3945                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3946                         if (looking_at(buf, &i, "*% ")) {
3947                             savingComment = FALSE;
3948                             suppressKibitz = 0;
3949                         }
3950                     }
3951                     next_out = i;
3952                 } else if (started == STARTED_HOLDINGS) {
3953                     int gamenum;
3954                     char new_piece[MSG_SIZ];
3955                     started = STARTED_NONE;
3956                     parse[parse_pos] = NULLCHAR;
3957                     if (appData.debugMode)
3958                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3959                                                         parse, currentMove);
3960                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3961                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3962                         if (gameInfo.variant == VariantNormal) {
3963                           /* [HGM] We seem to switch variant during a game!
3964                            * Presumably no holdings were displayed, so we have
3965                            * to move the position two files to the right to
3966                            * create room for them!
3967                            */
3968                           VariantClass newVariant;
3969                           switch(gameInfo.boardWidth) { // base guess on board width
3970                                 case 9:  newVariant = VariantShogi; break;
3971                                 case 10: newVariant = VariantGreat; break;
3972                                 default: newVariant = VariantCrazyhouse; break;
3973                           }
3974                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3975                           /* Get a move list just to see the header, which
3976                              will tell us whether this is really bug or zh */
3977                           if (ics_getting_history == H_FALSE) {
3978                             ics_getting_history = H_REQUESTED;
3979                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3980                             SendToICS(str);
3981                           }
3982                         }
3983                         new_piece[0] = NULLCHAR;
3984                         sscanf(parse, "game %d white [%s black [%s <- %s",
3985                                &gamenum, white_holding, black_holding,
3986                                new_piece);
3987                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3988                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3989                         /* [HGM] copy holdings to board holdings area */
3990                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3991                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3992                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3993 #if ZIPPY
3994                         if (appData.zippyPlay && first.initDone) {
3995                             ZippyHoldings(white_holding, black_holding,
3996                                           new_piece);
3997                         }
3998 #endif /*ZIPPY*/
3999                         if (tinyLayout || smallLayout) {
4000                             char wh[16], bh[16];
4001                             PackHolding(wh, white_holding);
4002                             PackHolding(bh, black_holding);
4003                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4004                                     gameInfo.white, gameInfo.black);
4005                         } else {
4006                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4007                                     gameInfo.white, white_holding,
4008                                     gameInfo.black, black_holding);
4009                         }
4010                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4011                         DrawPosition(FALSE, boards[currentMove]);
4012                         DisplayTitle(str);
4013                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4014                         sscanf(parse, "game %d white [%s black [%s <- %s",
4015                                &gamenum, white_holding, black_holding,
4016                                new_piece);
4017                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4018                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4019                         /* [HGM] copy holdings to partner-board holdings area */
4020                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4021                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4022                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4023                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4024                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4025                       }
4026                     }
4027                     /* Suppress following prompt */
4028                     if (looking_at(buf, &i, "*% ")) {
4029                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4030                         savingComment = FALSE;
4031                         suppressKibitz = 0;
4032                     }
4033                     next_out = i;
4034                 }
4035                 continue;
4036             }
4037
4038             i++;                /* skip unparsed character and loop back */
4039         }
4040
4041         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4042 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4043 //          SendToPlayer(&buf[next_out], i - next_out);
4044             started != STARTED_HOLDINGS && leftover_start > next_out) {
4045             SendToPlayer(&buf[next_out], leftover_start - next_out);
4046             next_out = i;
4047         }
4048
4049         leftover_len = buf_len - leftover_start;
4050         /* if buffer ends with something we couldn't parse,
4051            reparse it after appending the next read */
4052
4053     } else if (count == 0) {
4054         RemoveInputSource(isr);
4055         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4056     } else {
4057         DisplayFatalError(_("Error reading from ICS"), error, 1);
4058     }
4059 }
4060
4061
4062 /* Board style 12 looks like this:
4063
4064    <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
4065
4066  * The "<12> " is stripped before it gets to this routine.  The two
4067  * trailing 0's (flip state and clock ticking) are later addition, and
4068  * some chess servers may not have them, or may have only the first.
4069  * Additional trailing fields may be added in the future.
4070  */
4071
4072 #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"
4073
4074 #define RELATION_OBSERVING_PLAYED    0
4075 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4076 #define RELATION_PLAYING_MYMOVE      1
4077 #define RELATION_PLAYING_NOTMYMOVE  -1
4078 #define RELATION_EXAMINING           2
4079 #define RELATION_ISOLATED_BOARD     -3
4080 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4081
4082 void
4083 ParseBoard12(string)
4084      char *string;
4085 {
4086     GameMode newGameMode;
4087     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4088     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4089     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4090     char to_play, board_chars[200];
4091     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4092     char black[32], white[32];
4093     Board board;
4094     int prevMove = currentMove;
4095     int ticking = 2;
4096     ChessMove moveType;
4097     int fromX, fromY, toX, toY;
4098     char promoChar;
4099     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4100     char *bookHit = NULL; // [HGM] book
4101     Boolean weird = FALSE, reqFlag = FALSE;
4102
4103     fromX = fromY = toX = toY = -1;
4104
4105     newGame = FALSE;
4106
4107     if (appData.debugMode)
4108       fprintf(debugFP, _("Parsing board: %s\n"), string);
4109
4110     move_str[0] = NULLCHAR;
4111     elapsed_time[0] = NULLCHAR;
4112     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4113         int  i = 0, j;
4114         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4115             if(string[i] == ' ') { ranks++; files = 0; }
4116             else files++;
4117             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4118             i++;
4119         }
4120         for(j = 0; j <i; j++) board_chars[j] = string[j];
4121         board_chars[i] = '\0';
4122         string += i + 1;
4123     }
4124     n = sscanf(string, PATTERN, &to_play, &double_push,
4125                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4126                &gamenum, white, black, &relation, &basetime, &increment,
4127                &white_stren, &black_stren, &white_time, &black_time,
4128                &moveNum, str, elapsed_time, move_str, &ics_flip,
4129                &ticking);
4130
4131     if (n < 21) {
4132         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4133         DisplayError(str, 0);
4134         return;
4135     }
4136
4137     /* Convert the move number to internal form */
4138     moveNum = (moveNum - 1) * 2;
4139     if (to_play == 'B') moveNum++;
4140     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4141       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4142                         0, 1);
4143       return;
4144     }
4145
4146     switch (relation) {
4147       case RELATION_OBSERVING_PLAYED:
4148       case RELATION_OBSERVING_STATIC:
4149         if (gamenum == -1) {
4150             /* Old ICC buglet */
4151             relation = RELATION_OBSERVING_STATIC;
4152         }
4153         newGameMode = IcsObserving;
4154         break;
4155       case RELATION_PLAYING_MYMOVE:
4156       case RELATION_PLAYING_NOTMYMOVE:
4157         newGameMode =
4158           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4159             IcsPlayingWhite : IcsPlayingBlack;
4160         break;
4161       case RELATION_EXAMINING:
4162         newGameMode = IcsExamining;
4163         break;
4164       case RELATION_ISOLATED_BOARD:
4165       default:
4166         /* Just display this board.  If user was doing something else,
4167            we will forget about it until the next board comes. */
4168         newGameMode = IcsIdle;
4169         break;
4170       case RELATION_STARTING_POSITION:
4171         newGameMode = gameMode;
4172         break;
4173     }
4174
4175     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4176          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4177       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4178       char *toSqr;
4179       for (k = 0; k < ranks; k++) {
4180         for (j = 0; j < files; j++)
4181           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4182         if(gameInfo.holdingsWidth > 1) {
4183              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4184              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4185         }
4186       }
4187       CopyBoard(partnerBoard, board);
4188       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4189         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4190         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4192       if(toSqr = strchr(str, '-')) {
4193         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4194         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4195       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4196       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4197       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4198       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4199       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4200       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4201                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4202       DisplayMessage(partnerStatus, "");
4203         partnerBoardValid = TRUE;
4204       return;
4205     }
4206
4207     /* Modify behavior for initial board display on move listing
4208        of wild games.
4209        */
4210     switch (ics_getting_history) {
4211       case H_FALSE:
4212       case H_REQUESTED:
4213         break;
4214       case H_GOT_REQ_HEADER:
4215       case H_GOT_UNREQ_HEADER:
4216         /* This is the initial position of the current game */
4217         gamenum = ics_gamenum;
4218         moveNum = 0;            /* old ICS bug workaround */
4219         if (to_play == 'B') {
4220           startedFromSetupPosition = TRUE;
4221           blackPlaysFirst = TRUE;
4222           moveNum = 1;
4223           if (forwardMostMove == 0) forwardMostMove = 1;
4224           if (backwardMostMove == 0) backwardMostMove = 1;
4225           if (currentMove == 0) currentMove = 1;
4226         }
4227         newGameMode = gameMode;
4228         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4229         break;
4230       case H_GOT_UNWANTED_HEADER:
4231         /* This is an initial board that we don't want */
4232         return;
4233       case H_GETTING_MOVES:
4234         /* Should not happen */
4235         DisplayError(_("Error gathering move list: extra board"), 0);
4236         ics_getting_history = H_FALSE;
4237         return;
4238     }
4239
4240    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4241                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4242      /* [HGM] We seem to have switched variant unexpectedly
4243       * Try to guess new variant from board size
4244       */
4245           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4246           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4247           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4248           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4249           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4250           if(!weird) newVariant = VariantNormal;
4251           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4252           /* Get a move list just to see the header, which
4253              will tell us whether this is really bug or zh */
4254           if (ics_getting_history == H_FALSE) {
4255             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4256             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4257             SendToICS(str);
4258           }
4259     }
4260
4261     /* Take action if this is the first board of a new game, or of a
4262        different game than is currently being displayed.  */
4263     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4264         relation == RELATION_ISOLATED_BOARD) {
4265
4266         /* Forget the old game and get the history (if any) of the new one */
4267         if (gameMode != BeginningOfGame) {
4268           Reset(TRUE, TRUE);
4269         }
4270         newGame = TRUE;
4271         if (appData.autoRaiseBoard) BoardToTop();
4272         prevMove = -3;
4273         if (gamenum == -1) {
4274             newGameMode = IcsIdle;
4275         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4276                    appData.getMoveList && !reqFlag) {
4277             /* Need to get game history */
4278             ics_getting_history = H_REQUESTED;
4279             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280             SendToICS(str);
4281         }
4282
4283         /* Initially flip the board to have black on the bottom if playing
4284            black or if the ICS flip flag is set, but let the user change
4285            it with the Flip View button. */
4286         flipView = appData.autoFlipView ?
4287           (newGameMode == IcsPlayingBlack) || ics_flip :
4288           appData.flipView;
4289
4290         /* Done with values from previous mode; copy in new ones */
4291         gameMode = newGameMode;
4292         ModeHighlight();
4293         ics_gamenum = gamenum;
4294         if (gamenum == gs_gamenum) {
4295             int klen = strlen(gs_kind);
4296             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4297             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4298             gameInfo.event = StrSave(str);
4299         } else {
4300             gameInfo.event = StrSave("ICS game");
4301         }
4302         gameInfo.site = StrSave(appData.icsHost);
4303         gameInfo.date = PGNDate();
4304         gameInfo.round = StrSave("-");
4305         gameInfo.white = StrSave(white);
4306         gameInfo.black = StrSave(black);
4307         timeControl = basetime * 60 * 1000;
4308         timeControl_2 = 0;
4309         timeIncrement = increment * 1000;
4310         movesPerSession = 0;
4311         gameInfo.timeControl = TimeControlTagValue();
4312         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4313   if (appData.debugMode) {
4314     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4315     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4316     setbuf(debugFP, NULL);
4317   }
4318
4319         gameInfo.outOfBook = NULL;
4320
4321         /* Do we have the ratings? */
4322         if (strcmp(player1Name, white) == 0 &&
4323             strcmp(player2Name, black) == 0) {
4324             if (appData.debugMode)
4325               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4326                       player1Rating, player2Rating);
4327             gameInfo.whiteRating = player1Rating;
4328             gameInfo.blackRating = player2Rating;
4329         } else if (strcmp(player2Name, white) == 0 &&
4330                    strcmp(player1Name, black) == 0) {
4331             if (appData.debugMode)
4332               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4333                       player2Rating, player1Rating);
4334             gameInfo.whiteRating = player2Rating;
4335             gameInfo.blackRating = player1Rating;
4336         }
4337         player1Name[0] = player2Name[0] = NULLCHAR;
4338
4339         /* Silence shouts if requested */
4340         if (appData.quietPlay &&
4341             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4342             SendToICS(ics_prefix);
4343             SendToICS("set shout 0\n");
4344         }
4345     }
4346
4347     /* Deal with midgame name changes */
4348     if (!newGame) {
4349         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4350             if (gameInfo.white) free(gameInfo.white);
4351             gameInfo.white = StrSave(white);
4352         }
4353         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4354             if (gameInfo.black) free(gameInfo.black);
4355             gameInfo.black = StrSave(black);
4356         }
4357     }
4358
4359     /* Throw away game result if anything actually changes in examine mode */
4360     if (gameMode == IcsExamining && !newGame) {
4361         gameInfo.result = GameUnfinished;
4362         if (gameInfo.resultDetails != NULL) {
4363             free(gameInfo.resultDetails);
4364             gameInfo.resultDetails = NULL;
4365         }
4366     }
4367
4368     /* In pausing && IcsExamining mode, we ignore boards coming
4369        in if they are in a different variation than we are. */
4370     if (pauseExamInvalid) return;
4371     if (pausing && gameMode == IcsExamining) {
4372         if (moveNum <= pauseExamForwardMostMove) {
4373             pauseExamInvalid = TRUE;
4374             forwardMostMove = pauseExamForwardMostMove;
4375             return;
4376         }
4377     }
4378
4379   if (appData.debugMode) {
4380     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4381   }
4382     /* Parse the board */
4383     for (k = 0; k < ranks; k++) {
4384       for (j = 0; j < files; j++)
4385         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4386       if(gameInfo.holdingsWidth > 1) {
4387            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4388            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4389       }
4390     }
4391     CopyBoard(boards[moveNum], board);
4392     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4393     if (moveNum == 0) {
4394         startedFromSetupPosition =
4395           !CompareBoards(board, initialPosition);
4396         if(startedFromSetupPosition)
4397             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4398     }
4399
4400     /* [HGM] Set castling rights. Take the outermost Rooks,
4401        to make it also work for FRC opening positions. Note that board12
4402        is really defective for later FRC positions, as it has no way to
4403        indicate which Rook can castle if they are on the same side of King.
4404        For the initial position we grant rights to the outermost Rooks,
4405        and remember thos rights, and we then copy them on positions
4406        later in an FRC game. This means WB might not recognize castlings with
4407        Rooks that have moved back to their original position as illegal,
4408        but in ICS mode that is not its job anyway.
4409     */
4410     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4411     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4412
4413         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4414             if(board[0][i] == WhiteRook) j = i;
4415         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4416         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4417             if(board[0][i] == WhiteRook) j = i;
4418         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4419         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4420             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4421         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4422         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4423             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4424         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4425
4426         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4427         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4428             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4429         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4430             if(board[BOARD_HEIGHT-1][k] == bKing)
4431                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4432         if(gameInfo.variant == VariantTwoKings) {
4433             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4434             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4435             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4436         }
4437     } else { int r;
4438         r = boards[moveNum][CASTLING][0] = initialRights[0];
4439         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4440         r = boards[moveNum][CASTLING][1] = initialRights[1];
4441         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4442         r = boards[moveNum][CASTLING][3] = initialRights[3];
4443         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4444         r = boards[moveNum][CASTLING][4] = initialRights[4];
4445         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4446         /* wildcastle kludge: always assume King has rights */
4447         r = boards[moveNum][CASTLING][2] = initialRights[2];
4448         r = boards[moveNum][CASTLING][5] = initialRights[5];
4449     }
4450     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4451     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4452
4453
4454     if (ics_getting_history == H_GOT_REQ_HEADER ||
4455         ics_getting_history == H_GOT_UNREQ_HEADER) {
4456         /* This was an initial position from a move list, not
4457            the current position */
4458         return;
4459     }
4460
4461     /* Update currentMove and known move number limits */
4462     newMove = newGame || moveNum > forwardMostMove;
4463
4464     if (newGame) {
4465         forwardMostMove = backwardMostMove = currentMove = moveNum;
4466         if (gameMode == IcsExamining && moveNum == 0) {
4467           /* Workaround for ICS limitation: we are not told the wild
4468              type when starting to examine a game.  But if we ask for
4469              the move list, the move list header will tell us */
4470             ics_getting_history = H_REQUESTED;
4471             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4472             SendToICS(str);
4473         }
4474     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4475                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4476 #if ZIPPY
4477         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4478         /* [HGM] applied this also to an engine that is silently watching        */
4479         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4480             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4481             gameInfo.variant == currentlyInitializedVariant) {
4482           takeback = forwardMostMove - moveNum;
4483           for (i = 0; i < takeback; i++) {
4484             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4485             SendToProgram("undo\n", &first);
4486           }
4487         }
4488 #endif
4489
4490         forwardMostMove = moveNum;
4491         if (!pausing || currentMove > forwardMostMove)
4492           currentMove = forwardMostMove;
4493     } else {
4494         /* New part of history that is not contiguous with old part */
4495         if (pausing && gameMode == IcsExamining) {
4496             pauseExamInvalid = TRUE;
4497             forwardMostMove = pauseExamForwardMostMove;
4498             return;
4499         }
4500         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4501 #if ZIPPY
4502             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4503                 // [HGM] when we will receive the move list we now request, it will be
4504                 // fed to the engine from the first move on. So if the engine is not
4505                 // in the initial position now, bring it there.
4506                 InitChessProgram(&first, 0);
4507             }
4508 #endif
4509             ics_getting_history = H_REQUESTED;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512         }
4513         forwardMostMove = backwardMostMove = currentMove = moveNum;
4514     }
4515
4516     /* Update the clocks */
4517     if (strchr(elapsed_time, '.')) {
4518       /* Time is in ms */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4521     } else {
4522       /* Time is in seconds */
4523       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4524       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4525     }
4526
4527
4528 #if ZIPPY
4529     if (appData.zippyPlay && newGame &&
4530         gameMode != IcsObserving && gameMode != IcsIdle &&
4531         gameMode != IcsExamining)
4532       ZippyFirstBoard(moveNum, basetime, increment);
4533 #endif
4534
4535     /* Put the move on the move list, first converting
4536        to canonical algebraic form. */
4537     if (moveNum > 0) {
4538   if (appData.debugMode) {
4539     if (appData.debugMode) { int f = forwardMostMove;
4540         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4541                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4542                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4543     }
4544     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4545     fprintf(debugFP, "moveNum = %d\n", moveNum);
4546     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4547     setbuf(debugFP, NULL);
4548   }
4549         if (moveNum <= backwardMostMove) {
4550             /* We don't know what the board looked like before
4551                this move.  Punt. */
4552           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4553             strcat(parseList[moveNum - 1], " ");
4554             strcat(parseList[moveNum - 1], elapsed_time);
4555             moveList[moveNum - 1][0] = NULLCHAR;
4556         } else if (strcmp(move_str, "none") == 0) {
4557             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4558             /* Again, we don't know what the board looked like;
4559                this is really the start of the game. */
4560             parseList[moveNum - 1][0] = NULLCHAR;
4561             moveList[moveNum - 1][0] = NULLCHAR;
4562             backwardMostMove = moveNum;
4563             startedFromSetupPosition = TRUE;
4564             fromX = fromY = toX = toY = -1;
4565         } else {
4566           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4567           //                 So we parse the long-algebraic move string in stead of the SAN move
4568           int valid; char buf[MSG_SIZ], *prom;
4569
4570           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4571                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4572           // str looks something like "Q/a1-a2"; kill the slash
4573           if(str[1] == '/')
4574             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4575           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4576           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4577                 strcat(buf, prom); // long move lacks promo specification!
4578           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4579                 if(appData.debugMode)
4580                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4581                 safeStrCpy(move_str, buf, MSG_SIZ);
4582           }
4583           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4584                                 &fromX, &fromY, &toX, &toY, &promoChar)
4585                || ParseOneMove(buf, moveNum - 1, &moveType,
4586                                 &fromX, &fromY, &toX, &toY, &promoChar);
4587           // end of long SAN patch
4588           if (valid) {
4589             (void) CoordsToAlgebraic(boards[moveNum - 1],
4590                                      PosFlags(moveNum - 1),
4591                                      fromY, fromX, toY, toX, promoChar,
4592                                      parseList[moveNum-1]);
4593             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4594               case MT_NONE:
4595               case MT_STALEMATE:
4596               default:
4597                 break;
4598               case MT_CHECK:
4599                 if(gameInfo.variant != VariantShogi)
4600                     strcat(parseList[moveNum - 1], "+");
4601                 break;
4602               case MT_CHECKMATE:
4603               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4604                 strcat(parseList[moveNum - 1], "#");
4605                 break;
4606             }
4607             strcat(parseList[moveNum - 1], " ");
4608             strcat(parseList[moveNum - 1], elapsed_time);
4609             /* currentMoveString is set as a side-effect of ParseOneMove */
4610             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4611             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4612             strcat(moveList[moveNum - 1], "\n");
4613
4614             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4615                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4616               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4617                 ChessSquare old, new = boards[moveNum][k][j];
4618                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4619                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4620                   if(old == new) continue;
4621                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4622                   else if(new == WhiteWazir || new == BlackWazir) {
4623                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4624                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4625                       else boards[moveNum][k][j] = old; // preserve type of Gold
4626                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4627                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4628               }
4629           } else {
4630             /* Move from ICS was illegal!?  Punt. */
4631             if (appData.debugMode) {
4632               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4633               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4634             }
4635             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             moveList[moveNum - 1][0] = NULLCHAR;
4639             fromX = fromY = toX = toY = -1;
4640           }
4641         }
4642   if (appData.debugMode) {
4643     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4644     setbuf(debugFP, NULL);
4645   }
4646
4647 #if ZIPPY
4648         /* Send move to chess program (BEFORE animating it). */
4649         if (appData.zippyPlay && !newGame && newMove &&
4650            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4651
4652             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4653                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4654                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4655                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4656                             move_str);
4657                     DisplayError(str, 0);
4658                 } else {
4659                     if (first.sendTime) {
4660                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4661                     }
4662                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4663                     if (firstMove && !bookHit) {
4664                         firstMove = FALSE;
4665                         if (first.useColors) {
4666                           SendToProgram(gameMode == IcsPlayingWhite ?
4667                                         "white\ngo\n" :
4668                                         "black\ngo\n", &first);
4669                         } else {
4670                           SendToProgram("go\n", &first);
4671                         }
4672                         first.maybeThinking = TRUE;
4673                     }
4674                 }
4675             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4676               if (moveList[moveNum - 1][0] == NULLCHAR) {
4677                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4678                 DisplayError(str, 0);
4679               } else {
4680                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4681                 SendMoveToProgram(moveNum - 1, &first);
4682               }
4683             }
4684         }
4685 #endif
4686     }
4687
4688     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4689         /* If move comes from a remote source, animate it.  If it
4690            isn't remote, it will have already been animated. */
4691         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4692             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4693         }
4694         if (!pausing && appData.highlightLastMove) {
4695             SetHighlights(fromX, fromY, toX, toY);
4696         }
4697     }
4698
4699     /* Start the clocks */
4700     whiteFlag = blackFlag = FALSE;
4701     appData.clockMode = !(basetime == 0 && increment == 0);
4702     if (ticking == 0) {
4703       ics_clock_paused = TRUE;
4704       StopClocks();
4705     } else if (ticking == 1) {
4706       ics_clock_paused = FALSE;
4707     }
4708     if (gameMode == IcsIdle ||
4709         relation == RELATION_OBSERVING_STATIC ||
4710         relation == RELATION_EXAMINING ||
4711         ics_clock_paused)
4712       DisplayBothClocks();
4713     else
4714       StartClocks();
4715
4716     /* Display opponents and material strengths */
4717     if (gameInfo.variant != VariantBughouse &&
4718         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4719         if (tinyLayout || smallLayout) {
4720             if(gameInfo.variant == VariantNormal)
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment);
4724             else
4725               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4726                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4727                     basetime, increment, (int) gameInfo.variant);
4728         } else {
4729             if(gameInfo.variant == VariantNormal)
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment);
4733             else
4734               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4735                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4736                     basetime, increment, VariantName(gameInfo.variant));
4737         }
4738         DisplayTitle(str);
4739   if (appData.debugMode) {
4740     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4741   }
4742     }
4743
4744
4745     /* Display the board */
4746     if (!pausing && !appData.noGUI) {
4747
4748       if (appData.premove)
4749           if (!gotPremove ||
4750              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4751              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4752               ClearPremoveHighlights();
4753
4754       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4755         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4756       DrawPosition(j, boards[currentMove]);
4757
4758       DisplayMove(moveNum - 1);
4759       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4760             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4761               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4762         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4763       }
4764     }
4765
4766     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4767 #if ZIPPY
4768     if(bookHit) { // [HGM] book: simulate book reply
4769         static char bookMove[MSG_SIZ]; // a bit generous?
4770
4771         programStats.nodes = programStats.depth = programStats.time =
4772         programStats.score = programStats.got_only_move = 0;
4773         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4774
4775         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4776         strcat(bookMove, bookHit);
4777         HandleMachineMove(bookMove, &first);
4778     }
4779 #endif
4780 }
4781
4782 void
4783 GetMoveListEvent()
4784 {
4785     char buf[MSG_SIZ];
4786     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4787         ics_getting_history = H_REQUESTED;
4788         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4789         SendToICS(buf);
4790     }
4791 }
4792
4793 void
4794 AnalysisPeriodicEvent(force)
4795      int force;
4796 {
4797     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4798          && !force) || !appData.periodicUpdates)
4799       return;
4800
4801     /* Send . command to Crafty to collect stats */
4802     SendToProgram(".\n", &first);
4803
4804     /* Don't send another until we get a response (this makes
4805        us stop sending to old Crafty's which don't understand
4806        the "." command (sending illegal cmds resets node count & time,
4807        which looks bad)) */
4808     programStats.ok_to_send = 0;
4809 }
4810
4811 void ics_update_width(new_width)
4812         int new_width;
4813 {
4814         ics_printf("set width %d\n", new_width);
4815 }
4816
4817 void
4818 SendMoveToProgram(moveNum, cps)
4819      int moveNum;
4820      ChessProgramState *cps;
4821 {
4822     char buf[MSG_SIZ];
4823
4824     if (cps->useUsermove) {
4825       SendToProgram("usermove ", cps);
4826     }
4827     if (cps->useSAN) {
4828       char *space;
4829       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4830         int len = space - parseList[moveNum];
4831         memcpy(buf, parseList[moveNum], len);
4832         buf[len++] = '\n';
4833         buf[len] = NULLCHAR;
4834       } else {
4835         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4836       }
4837       SendToProgram(buf, cps);
4838     } else {
4839       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4840         AlphaRank(moveList[moveNum], 4);
4841         SendToProgram(moveList[moveNum], cps);
4842         AlphaRank(moveList[moveNum], 4); // and back
4843       } else
4844       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4845        * the engine. It would be nice to have a better way to identify castle
4846        * moves here. */
4847       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4848                                                                          && cps->useOOCastle) {
4849         int fromX = moveList[moveNum][0] - AAA;
4850         int fromY = moveList[moveNum][1] - ONE;
4851         int toX = moveList[moveNum][2] - AAA;
4852         int toY = moveList[moveNum][3] - ONE;
4853         if((boards[moveNum][fromY][fromX] == WhiteKing
4854             && boards[moveNum][toY][toX] == WhiteRook)
4855            || (boards[moveNum][fromY][fromX] == BlackKing
4856                && boards[moveNum][toY][toX] == BlackRook)) {
4857           if(toX > fromX) SendToProgram("O-O\n", cps);
4858           else SendToProgram("O-O-O\n", cps);
4859         }
4860         else SendToProgram(moveList[moveNum], cps);
4861       }
4862       else SendToProgram(moveList[moveNum], cps);
4863       /* End of additions by Tord */
4864     }
4865
4866     /* [HGM] setting up the opening has brought engine in force mode! */
4867     /*       Send 'go' if we are in a mode where machine should play. */
4868     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4869         (gameMode == TwoMachinesPlay   ||
4870 #if ZIPPY
4871          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4872 #endif
4873          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4874         SendToProgram("go\n", cps);
4875   if (appData.debugMode) {
4876     fprintf(debugFP, "(extra)\n");
4877   }
4878     }
4879     setboardSpoiledMachineBlack = 0;
4880 }
4881
4882 void
4883 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4884      ChessMove moveType;
4885      int fromX, fromY, toX, toY;
4886      char promoChar;
4887 {
4888     char user_move[MSG_SIZ];
4889
4890     switch (moveType) {
4891       default:
4892         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4893                 (int)moveType, fromX, fromY, toX, toY);
4894         DisplayError(user_move + strlen("say "), 0);
4895         break;
4896       case WhiteKingSideCastle:
4897       case BlackKingSideCastle:
4898       case WhiteQueenSideCastleWild:
4899       case BlackQueenSideCastleWild:
4900       /* PUSH Fabien */
4901       case WhiteHSideCastleFR:
4902       case BlackHSideCastleFR:
4903       /* POP Fabien */
4904         snprintf(user_move, MSG_SIZ, "o-o\n");
4905         break;
4906       case WhiteQueenSideCastle:
4907       case BlackQueenSideCastle:
4908       case WhiteKingSideCastleWild:
4909       case BlackKingSideCastleWild:
4910       /* PUSH Fabien */
4911       case WhiteASideCastleFR:
4912       case BlackASideCastleFR:
4913       /* POP Fabien */
4914         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4915         break;
4916       case WhiteNonPromotion:
4917       case BlackNonPromotion:
4918         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4919         break;
4920       case WhitePromotion:
4921       case BlackPromotion:
4922         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4923           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteFerz));
4926         else if(gameInfo.variant == VariantGreat)
4927           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 PieceToChar(WhiteMan));
4930         else
4931           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4932                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4933                 promoChar);
4934         break;
4935       case WhiteDrop:
4936       case BlackDrop:
4937       drop:
4938         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4939                  ToUpper(PieceToChar((ChessSquare) fromX)),
4940                  AAA + toX, ONE + toY);
4941         break;
4942       case IllegalMove:  /* could be a variant we don't quite understand */
4943         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4944       case NormalMove:
4945       case WhiteCapturesEnPassant:
4946       case BlackCapturesEnPassant:
4947         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4948                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4949         break;
4950     }
4951     SendToICS(user_move);
4952     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4953         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4954 }
4955
4956 void
4957 UploadGameEvent()
4958 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4959     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4960     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4961     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4962         DisplayError("You cannot do this while you are playing or observing", 0);
4963         return;
4964     }
4965     if(gameMode != IcsExamining) { // is this ever not the case?
4966         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4967
4968         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4969           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4970         } else { // on FICS we must first go to general examine mode
4971           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4972         }
4973         if(gameInfo.variant != VariantNormal) {
4974             // try figure out wild number, as xboard names are not always valid on ICS
4975             for(i=1; i<=36; i++) {
4976               snprintf(buf, MSG_SIZ, "wild/%d", i);
4977                 if(StringToVariant(buf) == gameInfo.variant) break;
4978             }
4979             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4980             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4981             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4982         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4983         SendToICS(ics_prefix);
4984         SendToICS(buf);
4985         if(startedFromSetupPosition || backwardMostMove != 0) {
4986           fen = PositionToFEN(backwardMostMove, NULL);
4987           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4988             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4989             SendToICS(buf);
4990           } else { // FICS: everything has to set by separate bsetup commands
4991             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4992             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4993             SendToICS(buf);
4994             if(!WhiteOnMove(backwardMostMove)) {
4995                 SendToICS("bsetup tomove black\n");
4996             }
4997             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4998             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4999             SendToICS(buf);
5000             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5001             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5002             SendToICS(buf);
5003             i = boards[backwardMostMove][EP_STATUS];
5004             if(i >= 0) { // set e.p.
5005               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5006                 SendToICS(buf);
5007             }
5008             bsetup++;
5009           }
5010         }
5011       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5012             SendToICS("bsetup done\n"); // switch to normal examining.
5013     }
5014     for(i = backwardMostMove; i<last; i++) {
5015         char buf[20];
5016         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5017         SendToICS(buf);
5018     }
5019     SendToICS(ics_prefix);
5020     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5021 }
5022
5023 void
5024 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5025      int rf, ff, rt, ft;
5026      char promoChar;
5027      char move[7];
5028 {
5029     if (rf == DROP_RANK) {
5030       sprintf(move, "%c@%c%c\n",
5031                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5032     } else {
5033         if (promoChar == 'x' || promoChar == NULLCHAR) {
5034           sprintf(move, "%c%c%c%c\n",
5035                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5036         } else {
5037             sprintf(move, "%c%c%c%c%c\n",
5038                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5039         }
5040     }
5041 }
5042
5043 void
5044 ProcessICSInitScript(f)
5045      FILE *f;
5046 {
5047     char buf[MSG_SIZ];
5048
5049     while (fgets(buf, MSG_SIZ, f)) {
5050         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5051     }
5052
5053     fclose(f);
5054 }
5055
5056
5057 static int lastX, lastY, selectFlag, dragging;
5058
5059 void
5060 Sweep(int step)
5061 {
5062     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5063     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5064     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5065     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5066     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5067     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5068     do {
5069         promoSweep -= step;
5070         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5071         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5072         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5073         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5074         if(!step) step = 1;
5075     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5076             appData.testLegality && (promoSweep == king ||
5077             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5078     ChangeDragPiece(promoSweep);
5079 }
5080
5081 int PromoScroll(int x, int y)
5082 {
5083   int step = 0;
5084
5085   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5086   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5087   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5088   if(!step) return FALSE;
5089   lastX = x; lastY = y;
5090   if((promoSweep < BlackPawn) == flipView) step = -step;
5091   if(step > 0) selectFlag = 1;
5092   if(!selectFlag) Sweep(step);
5093   return FALSE;
5094 }
5095
5096 void
5097 NextPiece(int step)
5098 {
5099     ChessSquare piece = boards[currentMove][toY][toX];
5100     do {
5101         pieceSweep -= step;
5102         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5103         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5104         if(!step) step = -1;
5105     } while(PieceToChar(pieceSweep) == '.');
5106     boards[currentMove][toY][toX] = pieceSweep;
5107     DrawPosition(FALSE, boards[currentMove]);
5108     boards[currentMove][toY][toX] = piece;
5109 }
5110 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5111 void
5112 AlphaRank(char *move, int n)
5113 {
5114 //    char *p = move, c; int x, y;
5115
5116     if (appData.debugMode) {
5117         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5118     }
5119
5120     if(move[1]=='*' &&
5121        move[2]>='0' && move[2]<='9' &&
5122        move[3]>='a' && move[3]<='x'    ) {
5123         move[1] = '@';
5124         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5125         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5126     } else
5127     if(move[0]>='0' && move[0]<='9' &&
5128        move[1]>='a' && move[1]<='x' &&
5129        move[2]>='0' && move[2]<='9' &&
5130        move[3]>='a' && move[3]<='x'    ) {
5131         /* input move, Shogi -> normal */
5132         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5133         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5136     } else
5137     if(move[1]=='@' &&
5138        move[3]>='0' && move[3]<='9' &&
5139        move[2]>='a' && move[2]<='x'    ) {
5140         move[1] = '*';
5141         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5142         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5143     } else
5144     if(
5145        move[0]>='a' && move[0]<='x' &&
5146        move[3]>='0' && move[3]<='9' &&
5147        move[2]>='a' && move[2]<='x'    ) {
5148          /* output move, normal -> Shogi */
5149         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5150         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5153         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5154     }
5155     if (appData.debugMode) {
5156         fprintf(debugFP, "   out = '%s'\n", move);
5157     }
5158 }
5159
5160 char yy_textstr[8000];
5161
5162 /* Parser for moves from gnuchess, ICS, or user typein box */
5163 Boolean
5164 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5165      char *move;
5166      int moveNum;
5167      ChessMove *moveType;
5168      int *fromX, *fromY, *toX, *toY;
5169      char *promoChar;
5170 {
5171     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5172
5173     switch (*moveType) {
5174       case WhitePromotion:
5175       case BlackPromotion:
5176       case WhiteNonPromotion:
5177       case BlackNonPromotion:
5178       case NormalMove:
5179       case WhiteCapturesEnPassant:
5180       case BlackCapturesEnPassant:
5181       case WhiteKingSideCastle:
5182       case WhiteQueenSideCastle:
5183       case BlackKingSideCastle:
5184       case BlackQueenSideCastle:
5185       case WhiteKingSideCastleWild:
5186       case WhiteQueenSideCastleWild:
5187       case BlackKingSideCastleWild:
5188       case BlackQueenSideCastleWild:
5189       /* Code added by Tord: */
5190       case WhiteHSideCastleFR:
5191       case WhiteASideCastleFR:
5192       case BlackHSideCastleFR:
5193       case BlackASideCastleFR:
5194       /* End of code added by Tord */
5195       case IllegalMove:         /* bug or odd chess variant */
5196         *fromX = currentMoveString[0] - AAA;
5197         *fromY = currentMoveString[1] - ONE;
5198         *toX = currentMoveString[2] - AAA;
5199         *toY = currentMoveString[3] - ONE;
5200         *promoChar = currentMoveString[4];
5201         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5202             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5205     }
5206             *fromX = *fromY = *toX = *toY = 0;
5207             return FALSE;
5208         }
5209         if (appData.testLegality) {
5210           return (*moveType != IllegalMove);
5211         } else {
5212           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5213                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5214         }
5215
5216       case WhiteDrop:
5217       case BlackDrop:
5218         *fromX = *moveType == WhiteDrop ?
5219           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5220           (int) CharToPiece(ToLower(currentMoveString[0]));
5221         *fromY = DROP_RANK;
5222         *toX = currentMoveString[2] - AAA;
5223         *toY = currentMoveString[3] - ONE;
5224         *promoChar = NULLCHAR;
5225         return TRUE;
5226
5227       case AmbiguousMove:
5228       case ImpossibleMove:
5229       case EndOfFile:
5230       case ElapsedTime:
5231       case Comment:
5232       case PGNTag:
5233       case NAG:
5234       case WhiteWins:
5235       case BlackWins:
5236       case GameIsDrawn:
5237       default:
5238     if (appData.debugMode) {
5239         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5240     }
5241         /* bug? */
5242         *fromX = *fromY = *toX = *toY = 0;
5243         *promoChar = NULLCHAR;
5244         return FALSE;
5245     }
5246 }
5247
5248 Boolean pushed = FALSE;
5249
5250 void
5251 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5252 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5253   int fromX, fromY, toX, toY; char promoChar;
5254   ChessMove moveType;
5255   Boolean valid;
5256   int nr = 0;
5257
5258   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5259     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5260     pushed = TRUE;
5261   }
5262   endPV = forwardMostMove;
5263   do {
5264     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5265     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5266     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5267 if(appData.debugMode){
5268 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);
5269 }
5270     if(!valid && nr == 0 &&
5271        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5272         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5273         // Hande case where played move is different from leading PV move
5274         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5275         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5276         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5277         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5278           endPV += 2; // if position different, keep this
5279           moveList[endPV-1][0] = fromX + AAA;
5280           moveList[endPV-1][1] = fromY + ONE;
5281           moveList[endPV-1][2] = toX + AAA;
5282           moveList[endPV-1][3] = toY + ONE;
5283           parseList[endPV-1][0] = NULLCHAR;
5284           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5285         }
5286       }
5287     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5288     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5289     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5290     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5291         valid++; // allow comments in PV
5292         continue;
5293     }
5294     nr++;
5295     if(endPV+1 > framePtr) break; // no space, truncate
5296     if(!valid) break;
5297     endPV++;
5298     CopyBoard(boards[endPV], boards[endPV-1]);
5299     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5300     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5301     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5302     CoordsToAlgebraic(boards[endPV - 1],
5303                              PosFlags(endPV - 1),
5304                              fromY, fromX, toY, toX, promoChar,
5305                              parseList[endPV - 1]);
5306   } while(valid);
5307   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5308   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5309   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5310                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5311   DrawPosition(TRUE, boards[currentMove]);
5312 }
5313
5314 int
5315 MultiPV(ChessProgramState *cps)
5316 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5317         int i;
5318         for(i=0; i<cps->nrOptions; i++)
5319             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5320                 return i;
5321         return -1;
5322 }
5323
5324 Boolean
5325 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5326 {
5327         int startPV, multi, lineStart, origIndex = index;
5328         char *p, buf2[MSG_SIZ];
5329
5330         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5331         lastX = x; lastY = y;
5332         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5333         lineStart = startPV = index;
5334         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5335         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5336         index = startPV;
5337         do{ while(buf[index] && buf[index] != '\n') index++;
5338         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5339         buf[index] = 0;
5340         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5341                 int n = first.option[multi].value;
5342                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5343                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5344                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5345                 first.option[multi].value = n;
5346                 *start = *end = 0;
5347                 return FALSE;
5348         }
5349         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5350         *start = startPV; *end = index-1;
5351         return TRUE;
5352 }
5353
5354 Boolean
5355 LoadPV(int x, int y)
5356 { // called on right mouse click to load PV
5357   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5358   lastX = x; lastY = y;
5359   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5360   return TRUE;
5361 }
5362
5363 void
5364 UnLoadPV()
5365 {
5366   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5367   if(endPV < 0) return;
5368   endPV = -1;
5369   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5370         Boolean saveAnimate = appData.animate;
5371         if(pushed) {
5372             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5373                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5374             } else storedGames--; // abandon shelved tail of original game
5375         }
5376         pushed = FALSE;
5377         forwardMostMove = currentMove;
5378         currentMove = oldFMM;
5379         appData.animate = FALSE;
5380         ToNrEvent(forwardMostMove);
5381         appData.animate = saveAnimate;
5382   }
5383   currentMove = forwardMostMove;
5384   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5385   ClearPremoveHighlights();
5386   DrawPosition(TRUE, boards[currentMove]);
5387 }
5388
5389 void
5390 MovePV(int x, int y, int h)
5391 { // step through PV based on mouse coordinates (called on mouse move)
5392   int margin = h>>3, step = 0;
5393
5394   // we must somehow check if right button is still down (might be released off board!)
5395   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5396   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5397   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5398   if(!step) return;
5399   lastX = x; lastY = y;
5400
5401   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5402   if(endPV < 0) return;
5403   if(y < margin) step = 1; else
5404   if(y > h - margin) step = -1;
5405   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5406   currentMove += step;
5407   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5408   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5409                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5410   DrawPosition(FALSE, boards[currentMove]);
5411 }
5412
5413
5414 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5415 // All positions will have equal probability, but the current method will not provide a unique
5416 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5417 #define DARK 1
5418 #define LITE 2
5419 #define ANY 3
5420
5421 int squaresLeft[4];
5422 int piecesLeft[(int)BlackPawn];
5423 int seed, nrOfShuffles;
5424
5425 void GetPositionNumber()
5426 {       // sets global variable seed
5427         int i;
5428
5429         seed = appData.defaultFrcPosition;
5430         if(seed < 0) { // randomize based on time for negative FRC position numbers
5431                 for(i=0; i<50; i++) seed += random();
5432                 seed = random() ^ random() >> 8 ^ random() << 8;
5433                 if(seed<0) seed = -seed;
5434         }
5435 }
5436
5437 int put(Board board, int pieceType, int rank, int n, int shade)
5438 // put the piece on the (n-1)-th empty squares of the given shade
5439 {
5440         int i;
5441
5442         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5443                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5444                         board[rank][i] = (ChessSquare) pieceType;
5445                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5446                         squaresLeft[ANY]--;
5447                         piecesLeft[pieceType]--;
5448                         return i;
5449                 }
5450         }
5451         return -1;
5452 }
5453
5454
5455 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5456 // calculate where the next piece goes, (any empty square), and put it there
5457 {
5458         int i;
5459
5460         i = seed % squaresLeft[shade];
5461         nrOfShuffles *= squaresLeft[shade];
5462         seed /= squaresLeft[shade];
5463         put(board, pieceType, rank, i, shade);
5464 }
5465
5466 void AddTwoPieces(Board board, int pieceType, int rank)
5467 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5468 {
5469         int i, n=squaresLeft[ANY], j=n-1, k;
5470
5471         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5472         i = seed % k;  // pick one
5473         nrOfShuffles *= k;
5474         seed /= k;
5475         while(i >= j) i -= j--;
5476         j = n - 1 - j; i += j;
5477         put(board, pieceType, rank, j, ANY);
5478         put(board, pieceType, rank, i, ANY);
5479 }
5480
5481 void SetUpShuffle(Board board, int number)
5482 {
5483         int i, p, first=1;
5484
5485         GetPositionNumber(); nrOfShuffles = 1;
5486
5487         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5488         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5489         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5490
5491         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5492
5493         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5494             p = (int) board[0][i];
5495             if(p < (int) BlackPawn) piecesLeft[p] ++;
5496             board[0][i] = EmptySquare;
5497         }
5498
5499         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5500             // shuffles restricted to allow normal castling put KRR first
5501             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5502                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5503             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5504                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5505             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5506                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5507             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5508                 put(board, WhiteRook, 0, 0, ANY);
5509             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5510         }
5511
5512         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5513             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5514             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5515                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5516                 while(piecesLeft[p] >= 2) {
5517                     AddOnePiece(board, p, 0, LITE);
5518                     AddOnePiece(board, p, 0, DARK);
5519                 }
5520                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5521             }
5522
5523         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5524             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5525             // but we leave King and Rooks for last, to possibly obey FRC restriction
5526             if(p == (int)WhiteRook) continue;
5527             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5528             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5529         }
5530
5531         // now everything is placed, except perhaps King (Unicorn) and Rooks
5532
5533         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5534             // Last King gets castling rights
5535             while(piecesLeft[(int)WhiteUnicorn]) {
5536                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5537                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5538             }
5539
5540             while(piecesLeft[(int)WhiteKing]) {
5541                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5542                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5543             }
5544
5545
5546         } else {
5547             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5548             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5549         }
5550
5551         // Only Rooks can be left; simply place them all
5552         while(piecesLeft[(int)WhiteRook]) {
5553                 i = put(board, WhiteRook, 0, 0, ANY);
5554                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5555                         if(first) {
5556                                 first=0;
5557                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5558                         }
5559                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5560                 }
5561         }
5562         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5563             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5564         }
5565
5566         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5567 }
5568
5569 int SetCharTable( char *table, const char * map )
5570 /* [HGM] moved here from winboard.c because of its general usefulness */
5571 /*       Basically a safe strcpy that uses the last character as King */
5572 {
5573     int result = FALSE; int NrPieces;
5574
5575     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5576                     && NrPieces >= 12 && !(NrPieces&1)) {
5577         int i; /* [HGM] Accept even length from 12 to 34 */
5578
5579         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5580         for( i=0; i<NrPieces/2-1; i++ ) {
5581             table[i] = map[i];
5582             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5583         }
5584         table[(int) WhiteKing]  = map[NrPieces/2-1];
5585         table[(int) BlackKing]  = map[NrPieces-1];
5586
5587         result = TRUE;
5588     }
5589
5590     return result;
5591 }
5592
5593 void Prelude(Board board)
5594 {       // [HGM] superchess: random selection of exo-pieces
5595         int i, j, k; ChessSquare p;
5596         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5597
5598         GetPositionNumber(); // use FRC position number
5599
5600         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5601             SetCharTable(pieceToChar, appData.pieceToCharTable);
5602             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5603                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5604         }
5605
5606         j = seed%4;                 seed /= 4;
5607         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5608         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5609         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5610         j = seed%3 + (seed%3 >= j); seed /= 3;
5611         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5612         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5613         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5614         j = seed%3;                 seed /= 3;
5615         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5616         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5617         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5618         j = seed%2 + (seed%2 >= j); seed /= 2;
5619         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5620         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5621         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5622         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5623         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5624         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5625         put(board, exoPieces[0],    0, 0, ANY);
5626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5627 }
5628
5629 void
5630 InitPosition(redraw)
5631      int redraw;
5632 {
5633     ChessSquare (* pieces)[BOARD_FILES];
5634     int i, j, pawnRow, overrule,
5635     oldx = gameInfo.boardWidth,
5636     oldy = gameInfo.boardHeight,
5637     oldh = gameInfo.holdingsWidth;
5638     static int oldv;
5639
5640     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5641
5642     /* [AS] Initialize pv info list [HGM] and game status */
5643     {
5644         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5645             pvInfoList[i].depth = 0;
5646             boards[i][EP_STATUS] = EP_NONE;
5647             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5648         }
5649
5650         initialRulePlies = 0; /* 50-move counter start */
5651
5652         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5653         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5654     }
5655
5656
5657     /* [HGM] logic here is completely changed. In stead of full positions */
5658     /* the initialized data only consist of the two backranks. The switch */
5659     /* selects which one we will use, which is than copied to the Board   */
5660     /* initialPosition, which for the rest is initialized by Pawns and    */
5661     /* empty squares. This initial position is then copied to boards[0],  */
5662     /* possibly after shuffling, so that it remains available.            */
5663
5664     gameInfo.holdingsWidth = 0; /* default board sizes */
5665     gameInfo.boardWidth    = 8;
5666     gameInfo.boardHeight   = 8;
5667     gameInfo.holdingsSize  = 0;
5668     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5669     for(i=0; i<BOARD_FILES-2; i++)
5670       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5671     initialPosition[EP_STATUS] = EP_NONE;
5672     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5673     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5674          SetCharTable(pieceNickName, appData.pieceNickNames);
5675     else SetCharTable(pieceNickName, "............");
5676     pieces = FIDEArray;
5677
5678     switch (gameInfo.variant) {
5679     case VariantFischeRandom:
5680       shuffleOpenings = TRUE;
5681     default:
5682       break;
5683     case VariantShatranj:
5684       pieces = ShatranjArray;
5685       nrCastlingRights = 0;
5686       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5687       break;
5688     case VariantMakruk:
5689       pieces = makrukArray;
5690       nrCastlingRights = 0;
5691       startedFromSetupPosition = TRUE;
5692       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5693       break;
5694     case VariantTwoKings:
5695       pieces = twoKingsArray;
5696       break;
5697     case VariantCapaRandom:
5698       shuffleOpenings = TRUE;
5699     case VariantCapablanca:
5700       pieces = CapablancaArray;
5701       gameInfo.boardWidth = 10;
5702       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5703       break;
5704     case VariantGothic:
5705       pieces = GothicArray;
5706       gameInfo.boardWidth = 10;
5707       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5708       break;
5709     case VariantSChess:
5710       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5711       gameInfo.holdingsSize = 7;
5712       break;
5713     case VariantJanus:
5714       pieces = JanusArray;
5715       gameInfo.boardWidth = 10;
5716       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5717       nrCastlingRights = 6;
5718         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5719         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5720         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5721         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5722         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5723         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5724       break;
5725     case VariantFalcon:
5726       pieces = FalconArray;
5727       gameInfo.boardWidth = 10;
5728       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5729       break;
5730     case VariantXiangqi:
5731       pieces = XiangqiArray;
5732       gameInfo.boardWidth  = 9;
5733       gameInfo.boardHeight = 10;
5734       nrCastlingRights = 0;
5735       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5736       break;
5737     case VariantShogi:
5738       pieces = ShogiArray;
5739       gameInfo.boardWidth  = 9;
5740       gameInfo.boardHeight = 9;
5741       gameInfo.holdingsSize = 7;
5742       nrCastlingRights = 0;
5743       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5744       break;
5745     case VariantCourier:
5746       pieces = CourierArray;
5747       gameInfo.boardWidth  = 12;
5748       nrCastlingRights = 0;
5749       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5750       break;
5751     case VariantKnightmate:
5752       pieces = KnightmateArray;
5753       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5754       break;
5755     case VariantSpartan:
5756       pieces = SpartanArray;
5757       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5758       break;
5759     case VariantFairy:
5760       pieces = fairyArray;
5761       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5762       break;
5763     case VariantGreat:
5764       pieces = GreatArray;
5765       gameInfo.boardWidth = 10;
5766       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5767       gameInfo.holdingsSize = 8;
5768       break;
5769     case VariantSuper:
5770       pieces = FIDEArray;
5771       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5772       gameInfo.holdingsSize = 8;
5773       startedFromSetupPosition = TRUE;
5774       break;
5775     case VariantCrazyhouse:
5776     case VariantBughouse:
5777       pieces = FIDEArray;
5778       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5779       gameInfo.holdingsSize = 5;
5780       break;
5781     case VariantWildCastle:
5782       pieces = FIDEArray;
5783       /* !!?shuffle with kings guaranteed to be on d or e file */
5784       shuffleOpenings = 1;
5785       break;
5786     case VariantNoCastle:
5787       pieces = FIDEArray;
5788       nrCastlingRights = 0;
5789       /* !!?unconstrained back-rank shuffle */
5790       shuffleOpenings = 1;
5791       break;
5792     }
5793
5794     overrule = 0;
5795     if(appData.NrFiles >= 0) {
5796         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5797         gameInfo.boardWidth = appData.NrFiles;
5798     }
5799     if(appData.NrRanks >= 0) {
5800         gameInfo.boardHeight = appData.NrRanks;
5801     }
5802     if(appData.holdingsSize >= 0) {
5803         i = appData.holdingsSize;
5804         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5805         gameInfo.holdingsSize = i;
5806     }
5807     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5808     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5809         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5810
5811     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5812     if(pawnRow < 1) pawnRow = 1;
5813     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5814
5815     /* User pieceToChar list overrules defaults */
5816     if(appData.pieceToCharTable != NULL)
5817         SetCharTable(pieceToChar, appData.pieceToCharTable);
5818
5819     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5820
5821         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5822             s = (ChessSquare) 0; /* account holding counts in guard band */
5823         for( i=0; i<BOARD_HEIGHT; i++ )
5824             initialPosition[i][j] = s;
5825
5826         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5827         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5828         initialPosition[pawnRow][j] = WhitePawn;
5829         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5830         if(gameInfo.variant == VariantXiangqi) {
5831             if(j&1) {
5832                 initialPosition[pawnRow][j] =
5833                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5834                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5835                    initialPosition[2][j] = WhiteCannon;
5836                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5837                 }
5838             }
5839         }
5840         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5841     }
5842     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5843
5844             j=BOARD_LEFT+1;
5845             initialPosition[1][j] = WhiteBishop;
5846             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5847             j=BOARD_RGHT-2;
5848             initialPosition[1][j] = WhiteRook;
5849             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5850     }
5851
5852     if( nrCastlingRights == -1) {
5853         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5854         /*       This sets default castling rights from none to normal corners   */
5855         /* Variants with other castling rights must set them themselves above    */
5856         nrCastlingRights = 6;
5857
5858         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5859         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5860         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5861         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5862         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5863         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5864      }
5865
5866      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5867      if(gameInfo.variant == VariantGreat) { // promotion commoners
5868         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5869         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5870         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5871         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5872      }
5873      if( gameInfo.variant == VariantSChess ) {
5874       initialPosition[1][0] = BlackMarshall;
5875       initialPosition[2][0] = BlackAngel;
5876       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5877       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5878       initialPosition[1][1] = initialPosition[2][1] = 
5879       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5880      }
5881   if (appData.debugMode) {
5882     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5883   }
5884     if(shuffleOpenings) {
5885         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5886         startedFromSetupPosition = TRUE;
5887     }
5888     if(startedFromPositionFile) {
5889       /* [HGM] loadPos: use PositionFile for every new game */
5890       CopyBoard(initialPosition, filePosition);
5891       for(i=0; i<nrCastlingRights; i++)
5892           initialRights[i] = filePosition[CASTLING][i];
5893       startedFromSetupPosition = TRUE;
5894     }
5895
5896     CopyBoard(boards[0], initialPosition);
5897
5898     if(oldx != gameInfo.boardWidth ||
5899        oldy != gameInfo.boardHeight ||
5900        oldv != gameInfo.variant ||
5901        oldh != gameInfo.holdingsWidth
5902                                          )
5903             InitDrawingSizes(-2 ,0);
5904
5905     oldv = gameInfo.variant;
5906     if (redraw)
5907       DrawPosition(TRUE, boards[currentMove]);
5908 }
5909
5910 void
5911 SendBoard(cps, moveNum)
5912      ChessProgramState *cps;
5913      int moveNum;
5914 {
5915     char message[MSG_SIZ];
5916
5917     if (cps->useSetboard) {
5918       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5919       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5920       SendToProgram(message, cps);
5921       free(fen);
5922
5923     } else {
5924       ChessSquare *bp;
5925       int i, j;
5926       /* Kludge to set black to move, avoiding the troublesome and now
5927        * deprecated "black" command.
5928        */
5929       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5930         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5931
5932       SendToProgram("edit\n", cps);
5933       SendToProgram("#\n", cps);
5934       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5935         bp = &boards[moveNum][i][BOARD_LEFT];
5936         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5937           if ((int) *bp < (int) BlackPawn) {
5938             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5939                     AAA + j, ONE + i);
5940             if(message[0] == '+' || message[0] == '~') {
5941               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5942                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5943                         AAA + j, ONE + i);
5944             }
5945             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5946                 message[1] = BOARD_RGHT   - 1 - j + '1';
5947                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5948             }
5949             SendToProgram(message, cps);
5950           }
5951         }
5952       }
5953
5954       SendToProgram("c\n", cps);
5955       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5956         bp = &boards[moveNum][i][BOARD_LEFT];
5957         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5958           if (((int) *bp != (int) EmptySquare)
5959               && ((int) *bp >= (int) BlackPawn)) {
5960             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5961                     AAA + j, ONE + i);
5962             if(message[0] == '+' || message[0] == '~') {
5963               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5964                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5965                         AAA + j, ONE + i);
5966             }
5967             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5968                 message[1] = BOARD_RGHT   - 1 - j + '1';
5969                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5970             }
5971             SendToProgram(message, cps);
5972           }
5973         }
5974       }
5975
5976       SendToProgram(".\n", cps);
5977     }
5978     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5979 }
5980
5981 ChessSquare
5982 DefaultPromoChoice(int white)
5983 {
5984     ChessSquare result;
5985     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5986         result = WhiteFerz; // no choice
5987     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5988         result= WhiteKing; // in Suicide Q is the last thing we want
5989     else if(gameInfo.variant == VariantSpartan)
5990         result = white ? WhiteQueen : WhiteAngel;
5991     else result = WhiteQueen;
5992     if(!white) result = WHITE_TO_BLACK result;
5993     return result;
5994 }
5995
5996 static int autoQueen; // [HGM] oneclick
5997
5998 int
5999 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6000 {
6001     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6002     /* [HGM] add Shogi promotions */
6003     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6004     ChessSquare piece;
6005     ChessMove moveType;
6006     Boolean premove;
6007
6008     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6009     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6010
6011     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6012       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6013         return FALSE;
6014
6015     piece = boards[currentMove][fromY][fromX];
6016     if(gameInfo.variant == VariantShogi) {
6017         promotionZoneSize = BOARD_HEIGHT/3;
6018         highestPromotingPiece = (int)WhiteFerz;
6019     } else if(gameInfo.variant == VariantMakruk) {
6020         promotionZoneSize = 3;
6021     }
6022
6023     // Treat Lance as Pawn when it is not representing Amazon
6024     if(gameInfo.variant != VariantSuper) {
6025         if(piece == WhiteLance) piece = WhitePawn; else
6026         if(piece == BlackLance) piece = BlackPawn;
6027     }
6028
6029     // next weed out all moves that do not touch the promotion zone at all
6030     if((int)piece >= BlackPawn) {
6031         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6032              return FALSE;
6033         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6034     } else {
6035         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6036            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6037     }
6038
6039     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6040
6041     // weed out mandatory Shogi promotions
6042     if(gameInfo.variant == VariantShogi) {
6043         if(piece >= BlackPawn) {
6044             if(toY == 0 && piece == BlackPawn ||
6045                toY == 0 && piece == BlackQueen ||
6046                toY <= 1 && piece == BlackKnight) {
6047                 *promoChoice = '+';
6048                 return FALSE;
6049             }
6050         } else {
6051             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6052                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6053                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6054                 *promoChoice = '+';
6055                 return FALSE;
6056             }
6057         }
6058     }
6059
6060     // weed out obviously illegal Pawn moves
6061     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6062         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6063         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6064         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6065         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6066         // note we are not allowed to test for valid (non-)capture, due to premove
6067     }
6068
6069     // we either have a choice what to promote to, or (in Shogi) whether to promote
6070     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6071         *promoChoice = PieceToChar(BlackFerz);  // no choice
6072         return FALSE;
6073     }
6074     // no sense asking what we must promote to if it is going to explode...
6075     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6076         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6077         return FALSE;
6078     }
6079     // give caller the default choice even if we will not make it
6080     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6081     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6082     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6083                            && gameInfo.variant != VariantShogi
6084                            && gameInfo.variant != VariantSuper) return FALSE;
6085     if(autoQueen) return FALSE; // predetermined
6086
6087     // suppress promotion popup on illegal moves that are not premoves
6088     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6089               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6090     if(appData.testLegality && !premove) {
6091         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6092                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6093         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6094             return FALSE;
6095     }
6096
6097     return TRUE;
6098 }
6099
6100 int
6101 InPalace(row, column)
6102      int row, column;
6103 {   /* [HGM] for Xiangqi */
6104     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6105          column < (BOARD_WIDTH + 4)/2 &&
6106          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6107     return FALSE;
6108 }
6109
6110 int
6111 PieceForSquare (x, y)
6112      int x;
6113      int y;
6114 {
6115   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6116      return -1;
6117   else
6118      return boards[currentMove][y][x];
6119 }
6120
6121 int
6122 OKToStartUserMove(x, y)
6123      int x, y;
6124 {
6125     ChessSquare from_piece;
6126     int white_piece;
6127
6128     if (matchMode) return FALSE;
6129     if (gameMode == EditPosition) return TRUE;
6130
6131     if (x >= 0 && y >= 0)
6132       from_piece = boards[currentMove][y][x];
6133     else
6134       from_piece = EmptySquare;
6135
6136     if (from_piece == EmptySquare) return FALSE;
6137
6138     white_piece = (int)from_piece >= (int)WhitePawn &&
6139       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6140
6141     switch (gameMode) {
6142       case PlayFromGameFile:
6143       case AnalyzeFile:
6144       case TwoMachinesPlay:
6145       case EndOfGame:
6146         return FALSE;
6147
6148       case IcsObserving:
6149       case IcsIdle:
6150         return FALSE;
6151
6152       case MachinePlaysWhite:
6153       case IcsPlayingBlack:
6154         if (appData.zippyPlay) return FALSE;
6155         if (white_piece) {
6156             DisplayMoveError(_("You are playing Black"));
6157             return FALSE;
6158         }
6159         break;
6160
6161       case MachinePlaysBlack:
6162       case IcsPlayingWhite:
6163         if (appData.zippyPlay) return FALSE;
6164         if (!white_piece) {
6165             DisplayMoveError(_("You are playing White"));
6166             return FALSE;
6167         }
6168         break;
6169
6170       case EditGame:
6171         if (!white_piece && WhiteOnMove(currentMove)) {
6172             DisplayMoveError(_("It is White's turn"));
6173             return FALSE;
6174         }
6175         if (white_piece && !WhiteOnMove(currentMove)) {
6176             DisplayMoveError(_("It is Black's turn"));
6177             return FALSE;
6178         }
6179         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6180             /* Editing correspondence game history */
6181             /* Could disallow this or prompt for confirmation */
6182             cmailOldMove = -1;
6183         }
6184         break;
6185
6186       case BeginningOfGame:
6187         if (appData.icsActive) return FALSE;
6188         if (!appData.noChessProgram) {
6189             if (!white_piece) {
6190                 DisplayMoveError(_("You are playing White"));
6191                 return FALSE;
6192             }
6193         }
6194         break;
6195
6196       case Training:
6197         if (!white_piece && WhiteOnMove(currentMove)) {
6198             DisplayMoveError(_("It is White's turn"));
6199             return FALSE;
6200         }
6201         if (white_piece && !WhiteOnMove(currentMove)) {
6202             DisplayMoveError(_("It is Black's turn"));
6203             return FALSE;
6204         }
6205         break;
6206
6207       default:
6208       case IcsExamining:
6209         break;
6210     }
6211     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6212         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6213         && gameMode != AnalyzeFile && gameMode != Training) {
6214         DisplayMoveError(_("Displayed position is not current"));
6215         return FALSE;
6216     }
6217     return TRUE;
6218 }
6219
6220 Boolean
6221 OnlyMove(int *x, int *y, Boolean captures) {
6222     DisambiguateClosure cl;
6223     if (appData.zippyPlay) return FALSE;
6224     switch(gameMode) {
6225       case MachinePlaysBlack:
6226       case IcsPlayingWhite:
6227       case BeginningOfGame:
6228         if(!WhiteOnMove(currentMove)) return FALSE;
6229         break;
6230       case MachinePlaysWhite:
6231       case IcsPlayingBlack:
6232         if(WhiteOnMove(currentMove)) return FALSE;
6233         break;
6234       case EditGame:
6235         break;
6236       default:
6237         return FALSE;
6238     }
6239     cl.pieceIn = EmptySquare;
6240     cl.rfIn = *y;
6241     cl.ffIn = *x;
6242     cl.rtIn = -1;
6243     cl.ftIn = -1;
6244     cl.promoCharIn = NULLCHAR;
6245     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6246     if( cl.kind == NormalMove ||
6247         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6248         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6249         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6250       fromX = cl.ff;
6251       fromY = cl.rf;
6252       *x = cl.ft;
6253       *y = cl.rt;
6254       return TRUE;
6255     }
6256     if(cl.kind != ImpossibleMove) return FALSE;
6257     cl.pieceIn = EmptySquare;
6258     cl.rfIn = -1;
6259     cl.ffIn = -1;
6260     cl.rtIn = *y;
6261     cl.ftIn = *x;
6262     cl.promoCharIn = NULLCHAR;
6263     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6264     if( cl.kind == NormalMove ||
6265         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6266         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6267         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6268       fromX = cl.ff;
6269       fromY = cl.rf;
6270       *x = cl.ft;
6271       *y = cl.rt;
6272       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6273       return TRUE;
6274     }
6275     return FALSE;
6276 }
6277
6278 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6279 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6280 int lastLoadGameUseList = FALSE;
6281 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6282 ChessMove lastLoadGameStart = EndOfFile;
6283
6284 void
6285 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6286      int fromX, fromY, toX, toY;
6287      int promoChar;
6288 {
6289     ChessMove moveType;
6290     ChessSquare pdown, pup;
6291
6292     /* Check if the user is playing in turn.  This is complicated because we
6293        let the user "pick up" a piece before it is his turn.  So the piece he
6294        tried to pick up may have been captured by the time he puts it down!
6295        Therefore we use the color the user is supposed to be playing in this
6296        test, not the color of the piece that is currently on the starting
6297        square---except in EditGame mode, where the user is playing both
6298        sides; fortunately there the capture race can't happen.  (It can
6299        now happen in IcsExamining mode, but that's just too bad.  The user
6300        will get a somewhat confusing message in that case.)
6301        */
6302
6303     switch (gameMode) {
6304       case PlayFromGameFile:
6305       case AnalyzeFile:
6306       case TwoMachinesPlay:
6307       case EndOfGame:
6308       case IcsObserving:
6309       case IcsIdle:
6310         /* We switched into a game mode where moves are not accepted,
6311            perhaps while the mouse button was down. */
6312         return;
6313
6314       case MachinePlaysWhite:
6315         /* User is moving for Black */
6316         if (WhiteOnMove(currentMove)) {
6317             DisplayMoveError(_("It is White's turn"));
6318             return;
6319         }
6320         break;
6321
6322       case MachinePlaysBlack:
6323         /* User is moving for White */
6324         if (!WhiteOnMove(currentMove)) {
6325             DisplayMoveError(_("It is Black's turn"));
6326             return;
6327         }
6328         break;
6329
6330       case EditGame:
6331       case IcsExamining:
6332       case BeginningOfGame:
6333       case AnalyzeMode:
6334       case Training:
6335         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6336         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6337             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6338             /* User is moving for Black */
6339             if (WhiteOnMove(currentMove)) {
6340                 DisplayMoveError(_("It is White's turn"));
6341                 return;
6342             }
6343         } else {
6344             /* User is moving for White */
6345             if (!WhiteOnMove(currentMove)) {
6346                 DisplayMoveError(_("It is Black's turn"));
6347                 return;
6348             }
6349         }
6350         break;
6351
6352       case IcsPlayingBlack:
6353         /* User is moving for Black */
6354         if (WhiteOnMove(currentMove)) {
6355             if (!appData.premove) {
6356                 DisplayMoveError(_("It is White's turn"));
6357             } else if (toX >= 0 && toY >= 0) {
6358                 premoveToX = toX;
6359                 premoveToY = toY;
6360                 premoveFromX = fromX;
6361                 premoveFromY = fromY;
6362                 premovePromoChar = promoChar;
6363                 gotPremove = 1;
6364                 if (appData.debugMode)
6365                     fprintf(debugFP, "Got premove: fromX %d,"
6366                             "fromY %d, toX %d, toY %d\n",
6367                             fromX, fromY, toX, toY);
6368             }
6369             return;
6370         }
6371         break;
6372
6373       case IcsPlayingWhite:
6374         /* User is moving for White */
6375         if (!WhiteOnMove(currentMove)) {
6376             if (!appData.premove) {
6377                 DisplayMoveError(_("It is Black's turn"));
6378             } else if (toX >= 0 && toY >= 0) {
6379                 premoveToX = toX;
6380                 premoveToY = toY;
6381                 premoveFromX = fromX;
6382                 premoveFromY = fromY;
6383                 premovePromoChar = promoChar;
6384                 gotPremove = 1;
6385                 if (appData.debugMode)
6386                     fprintf(debugFP, "Got premove: fromX %d,"
6387                             "fromY %d, toX %d, toY %d\n",
6388                             fromX, fromY, toX, toY);
6389             }
6390             return;
6391         }
6392         break;
6393
6394       default:
6395         break;
6396
6397       case EditPosition:
6398         /* EditPosition, empty square, or different color piece;
6399            click-click move is possible */
6400         if (toX == -2 || toY == -2) {
6401             boards[0][fromY][fromX] = EmptySquare;
6402             DrawPosition(FALSE, boards[currentMove]);
6403             return;
6404         } else if (toX >= 0 && toY >= 0) {
6405             boards[0][toY][toX] = boards[0][fromY][fromX];
6406             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6407                 if(boards[0][fromY][0] != EmptySquare) {
6408                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6409                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6410                 }
6411             } else
6412             if(fromX == BOARD_RGHT+1) {
6413                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6414                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6415                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6416                 }
6417             } else
6418             boards[0][fromY][fromX] = EmptySquare;
6419             DrawPosition(FALSE, boards[currentMove]);
6420             return;
6421         }
6422         return;
6423     }
6424
6425     if(toX < 0 || toY < 0) return;
6426     pdown = boards[currentMove][fromY][fromX];
6427     pup = boards[currentMove][toY][toX];
6428
6429     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6430     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6431          if( pup != EmptySquare ) return;
6432          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6433            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6434                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6435            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6436            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6437            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6438            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6439          fromY = DROP_RANK;
6440     }
6441
6442     /* [HGM] always test for legality, to get promotion info */
6443     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6444                                          fromY, fromX, toY, toX, promoChar);
6445     /* [HGM] but possibly ignore an IllegalMove result */
6446     if (appData.testLegality) {
6447         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6448             DisplayMoveError(_("Illegal move"));
6449             return;
6450         }
6451     }
6452
6453     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6454 }
6455
6456 /* Common tail of UserMoveEvent and DropMenuEvent */
6457 int
6458 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6459      ChessMove moveType;
6460      int fromX, fromY, toX, toY;
6461      /*char*/int promoChar;
6462 {
6463     char *bookHit = 0;
6464
6465     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6466         // [HGM] superchess: suppress promotions to non-available piece
6467         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6468         if(WhiteOnMove(currentMove)) {
6469             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6470         } else {
6471             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6472         }
6473     }
6474
6475     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6476        move type in caller when we know the move is a legal promotion */
6477     if(moveType == NormalMove && promoChar)
6478         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6479
6480     /* [HGM] <popupFix> The following if has been moved here from
6481        UserMoveEvent(). Because it seemed to belong here (why not allow
6482        piece drops in training games?), and because it can only be
6483        performed after it is known to what we promote. */
6484     if (gameMode == Training) {
6485       /* compare the move played on the board to the next move in the
6486        * game. If they match, display the move and the opponent's response.
6487        * If they don't match, display an error message.
6488        */
6489       int saveAnimate;
6490       Board testBoard;
6491       CopyBoard(testBoard, boards[currentMove]);
6492       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6493
6494       if (CompareBoards(testBoard, boards[currentMove+1])) {
6495         ForwardInner(currentMove+1);
6496
6497         /* Autoplay the opponent's response.
6498          * if appData.animate was TRUE when Training mode was entered,
6499          * the response will be animated.
6500          */
6501         saveAnimate = appData.animate;
6502         appData.animate = animateTraining;
6503         ForwardInner(currentMove+1);
6504         appData.animate = saveAnimate;
6505
6506         /* check for the end of the game */
6507         if (currentMove >= forwardMostMove) {
6508           gameMode = PlayFromGameFile;
6509           ModeHighlight();
6510           SetTrainingModeOff();
6511           DisplayInformation(_("End of game"));
6512         }
6513       } else {
6514         DisplayError(_("Incorrect move"), 0);
6515       }
6516       return 1;
6517     }
6518
6519   /* Ok, now we know that the move is good, so we can kill
6520      the previous line in Analysis Mode */
6521   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6522                                 && currentMove < forwardMostMove) {
6523     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6524     else forwardMostMove = currentMove;
6525   }
6526
6527   /* If we need the chess program but it's dead, restart it */
6528   ResurrectChessProgram();
6529
6530   /* A user move restarts a paused game*/
6531   if (pausing)
6532     PauseEvent();
6533
6534   thinkOutput[0] = NULLCHAR;
6535
6536   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6537
6538   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6539     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540     return 1;
6541   }
6542
6543   if (gameMode == BeginningOfGame) {
6544     if (appData.noChessProgram) {
6545       gameMode = EditGame;
6546       SetGameInfo();
6547     } else {
6548       char buf[MSG_SIZ];
6549       gameMode = MachinePlaysBlack;
6550       StartClocks();
6551       SetGameInfo();
6552       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6553       DisplayTitle(buf);
6554       if (first.sendName) {
6555         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6556         SendToProgram(buf, &first);
6557       }
6558       StartClocks();
6559     }
6560     ModeHighlight();
6561   }
6562
6563   /* Relay move to ICS or chess engine */
6564   if (appData.icsActive) {
6565     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6566         gameMode == IcsExamining) {
6567       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6568         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6569         SendToICS("draw ");
6570         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6571       }
6572       // also send plain move, in case ICS does not understand atomic claims
6573       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6574       ics_user_moved = 1;
6575     }
6576   } else {
6577     if (first.sendTime && (gameMode == BeginningOfGame ||
6578                            gameMode == MachinePlaysWhite ||
6579                            gameMode == MachinePlaysBlack)) {
6580       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6581     }
6582     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6583          // [HGM] book: if program might be playing, let it use book
6584         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6585         first.maybeThinking = TRUE;
6586     } else SendMoveToProgram(forwardMostMove-1, &first);
6587     if (currentMove == cmailOldMove + 1) {
6588       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6589     }
6590   }
6591
6592   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6593
6594   switch (gameMode) {
6595   case EditGame:
6596     if(appData.testLegality)
6597     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6598     case MT_NONE:
6599     case MT_CHECK:
6600       break;
6601     case MT_CHECKMATE:
6602     case MT_STAINMATE:
6603       if (WhiteOnMove(currentMove)) {
6604         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6605       } else {
6606         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6607       }
6608       break;
6609     case MT_STALEMATE:
6610       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6611       break;
6612     }
6613     break;
6614
6615   case MachinePlaysBlack:
6616   case MachinePlaysWhite:
6617     /* disable certain menu options while machine is thinking */
6618     SetMachineThinkingEnables();
6619     break;
6620
6621   default:
6622     break;
6623   }
6624
6625   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6626   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6627
6628   if(bookHit) { // [HGM] book: simulate book reply
6629         static char bookMove[MSG_SIZ]; // a bit generous?
6630
6631         programStats.nodes = programStats.depth = programStats.time =
6632         programStats.score = programStats.got_only_move = 0;
6633         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6634
6635         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6636         strcat(bookMove, bookHit);
6637         HandleMachineMove(bookMove, &first);
6638   }
6639   return 1;
6640 }
6641
6642 void
6643 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6644      Board board;
6645      int flags;
6646      ChessMove kind;
6647      int rf, ff, rt, ft;
6648      VOIDSTAR closure;
6649 {
6650     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6651     Markers *m = (Markers *) closure;
6652     if(rf == fromY && ff == fromX)
6653         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6654                          || kind == WhiteCapturesEnPassant
6655                          || kind == BlackCapturesEnPassant);
6656     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6657 }
6658
6659 void
6660 MarkTargetSquares(int clear)
6661 {
6662   int x, y;
6663   if(!appData.markers || !appData.highlightDragging ||
6664      !appData.testLegality || gameMode == EditPosition) return;
6665   if(clear) {
6666     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6667   } else {
6668     int capt = 0;
6669     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6670     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6671       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6672       if(capt)
6673       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6674     }
6675   }
6676   DrawPosition(TRUE, NULL);
6677 }
6678
6679 int
6680 Explode(Board board, int fromX, int fromY, int toX, int toY)
6681 {
6682     if(gameInfo.variant == VariantAtomic &&
6683        (board[toY][toX] != EmptySquare ||                     // capture?
6684         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6685                          board[fromY][fromX] == BlackPawn   )
6686       )) {
6687         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6688         return TRUE;
6689     }
6690     return FALSE;
6691 }
6692
6693 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6694
6695 int CanPromote(ChessSquare piece, int y)
6696 {
6697         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6698         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6699         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6700            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6701            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6702                                                   gameInfo.variant == VariantMakruk) return FALSE;
6703         return (piece == BlackPawn && y == 1 ||
6704                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6705                 piece == BlackLance && y == 1 ||
6706                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6707 }
6708
6709 void LeftClick(ClickType clickType, int xPix, int yPix)
6710 {
6711     int x, y;
6712     Boolean saveAnimate;
6713     static int second = 0, promotionChoice = 0, clearFlag = 0;
6714     char promoChoice = NULLCHAR;
6715     ChessSquare piece;
6716
6717     if(appData.seekGraph && appData.icsActive && loggedOn &&
6718         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6719         SeekGraphClick(clickType, xPix, yPix, 0);
6720         return;
6721     }
6722
6723     if (clickType == Press) ErrorPopDown();
6724     MarkTargetSquares(1);
6725
6726     x = EventToSquare(xPix, BOARD_WIDTH);
6727     y = EventToSquare(yPix, BOARD_HEIGHT);
6728     if (!flipView && y >= 0) {
6729         y = BOARD_HEIGHT - 1 - y;
6730     }
6731     if (flipView && x >= 0) {
6732         x = BOARD_WIDTH - 1 - x;
6733     }
6734
6735     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6736         defaultPromoChoice = promoSweep;
6737         promoSweep = EmptySquare;   // terminate sweep
6738         promoDefaultAltered = TRUE;
6739         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6740     }
6741
6742     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6743         if(clickType == Release) return; // ignore upclick of click-click destination
6744         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6745         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6746         if(gameInfo.holdingsWidth &&
6747                 (WhiteOnMove(currentMove)
6748                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6749                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6750             // click in right holdings, for determining promotion piece
6751             ChessSquare p = boards[currentMove][y][x];
6752             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6753             if(p != EmptySquare) {
6754                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6755                 fromX = fromY = -1;
6756                 return;
6757             }
6758         }
6759         DrawPosition(FALSE, boards[currentMove]);
6760         return;
6761     }
6762
6763     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6764     if(clickType == Press
6765             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6766               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6767               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6768         return;
6769
6770     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6771         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6772
6773     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6774         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6775                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6776         defaultPromoChoice = DefaultPromoChoice(side);
6777     }
6778
6779     autoQueen = appData.alwaysPromoteToQueen;
6780
6781     if (fromX == -1) {
6782       int originalY = y;
6783       gatingPiece = EmptySquare;
6784       if (clickType != Press) {
6785         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6786             DragPieceEnd(xPix, yPix); dragging = 0;
6787             DrawPosition(FALSE, NULL);
6788         }
6789         return;
6790       }
6791       fromX = x; fromY = y;
6792       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6793          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6794          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6795             /* First square */
6796             if (OKToStartUserMove(fromX, fromY)) {
6797                 second = 0;
6798                 MarkTargetSquares(0);
6799                 DragPieceBegin(xPix, yPix); dragging = 1;
6800                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6801                     promoSweep = defaultPromoChoice;
6802                     selectFlag = 0; lastX = xPix; lastY = yPix;
6803                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6804                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6805                 }
6806                 if (appData.highlightDragging) {
6807                     SetHighlights(fromX, fromY, -1, -1);
6808                 }
6809             } else fromX = fromY = -1;
6810             return;
6811         }
6812     }
6813
6814     /* fromX != -1 */
6815     if (clickType == Press && gameMode != EditPosition) {
6816         ChessSquare fromP;
6817         ChessSquare toP;
6818         int frc;
6819
6820         // ignore off-board to clicks
6821         if(y < 0 || x < 0) return;
6822
6823         /* Check if clicking again on the same color piece */
6824         fromP = boards[currentMove][fromY][fromX];
6825         toP = boards[currentMove][y][x];
6826         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6827         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6828              WhitePawn <= toP && toP <= WhiteKing &&
6829              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6830              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6831             (BlackPawn <= fromP && fromP <= BlackKing &&
6832              BlackPawn <= toP && toP <= BlackKing &&
6833              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6834              !(fromP == BlackKing && toP == BlackRook && frc))) {
6835             /* Clicked again on same color piece -- changed his mind */
6836             second = (x == fromX && y == fromY);
6837             promoDefaultAltered = FALSE;
6838            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6839             if (appData.highlightDragging) {
6840                 SetHighlights(x, y, -1, -1);
6841             } else {
6842                 ClearHighlights();
6843             }
6844             if (OKToStartUserMove(x, y)) {
6845                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6846                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6847                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6848                  gatingPiece = boards[currentMove][fromY][fromX];
6849                 else gatingPiece = EmptySquare;
6850                 fromX = x;
6851                 fromY = y; dragging = 1;
6852                 MarkTargetSquares(0);
6853                 DragPieceBegin(xPix, yPix);
6854                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6855                     promoSweep = defaultPromoChoice;
6856                     selectFlag = 0; lastX = xPix; lastY = yPix;
6857                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6858                 }
6859             }
6860            }
6861            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6862            second = FALSE; 
6863         }
6864         // ignore clicks on holdings
6865         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6866     }
6867
6868     if (clickType == Release && x == fromX && y == fromY) {
6869         DragPieceEnd(xPix, yPix); dragging = 0;
6870         if(clearFlag) {
6871             // a deferred attempt to click-click move an empty square on top of a piece
6872             boards[currentMove][y][x] = EmptySquare;
6873             ClearHighlights();
6874             DrawPosition(FALSE, boards[currentMove]);
6875             fromX = fromY = -1; clearFlag = 0;
6876             return;
6877         }
6878         if (appData.animateDragging) {
6879             /* Undo animation damage if any */
6880             DrawPosition(FALSE, NULL);
6881         }
6882         if (second) {
6883             /* Second up/down in same square; just abort move */
6884             second = 0;
6885             fromX = fromY = -1;
6886             gatingPiece = EmptySquare;
6887             ClearHighlights();
6888             gotPremove = 0;
6889             ClearPremoveHighlights();
6890         } else {
6891             /* First upclick in same square; start click-click mode */
6892             SetHighlights(x, y, -1, -1);
6893         }
6894         return;
6895     }
6896
6897     clearFlag = 0;
6898
6899     /* we now have a different from- and (possibly off-board) to-square */
6900     /* Completed move */
6901     toX = x;
6902     toY = y;
6903     saveAnimate = appData.animate;
6904     if (clickType == Press) {
6905         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6906             // must be Edit Position mode with empty-square selected
6907             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6908             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6909             return;
6910         }
6911         /* Finish clickclick move */
6912         if (appData.animate || appData.highlightLastMove) {
6913             SetHighlights(fromX, fromY, toX, toY);
6914         } else {
6915             ClearHighlights();
6916         }
6917     } else {
6918         /* Finish drag move */
6919         if (appData.highlightLastMove) {
6920             SetHighlights(fromX, fromY, toX, toY);
6921         } else {
6922             ClearHighlights();
6923         }
6924         DragPieceEnd(xPix, yPix); dragging = 0;
6925         /* Don't animate move and drag both */
6926         appData.animate = FALSE;
6927     }
6928
6929     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6930     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6931         ChessSquare piece = boards[currentMove][fromY][fromX];
6932         if(gameMode == EditPosition && piece != EmptySquare &&
6933            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6934             int n;
6935
6936             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6937                 n = PieceToNumber(piece - (int)BlackPawn);
6938                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6939                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6940                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6941             } else
6942             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6943                 n = PieceToNumber(piece);
6944                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6945                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6946                 boards[currentMove][n][BOARD_WIDTH-2]++;
6947             }
6948             boards[currentMove][fromY][fromX] = EmptySquare;
6949         }
6950         ClearHighlights();
6951         fromX = fromY = -1;
6952         DrawPosition(TRUE, boards[currentMove]);
6953         return;
6954     }
6955
6956     // off-board moves should not be highlighted
6957     if(x < 0 || y < 0) ClearHighlights();
6958
6959     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6960
6961     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6962         SetHighlights(fromX, fromY, toX, toY);
6963         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6964             // [HGM] super: promotion to captured piece selected from holdings
6965             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6966             promotionChoice = TRUE;
6967             // kludge follows to temporarily execute move on display, without promoting yet
6968             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6969             boards[currentMove][toY][toX] = p;
6970             DrawPosition(FALSE, boards[currentMove]);
6971             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6972             boards[currentMove][toY][toX] = q;
6973             DisplayMessage("Click in holdings to choose piece", "");
6974             return;
6975         }
6976         PromotionPopUp();
6977     } else {
6978         int oldMove = currentMove;
6979         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6980         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6981         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6982         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6983            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6984             DrawPosition(TRUE, boards[currentMove]);
6985         fromX = fromY = -1;
6986     }
6987     appData.animate = saveAnimate;
6988     if (appData.animate || appData.animateDragging) {
6989         /* Undo animation damage if needed */
6990         DrawPosition(FALSE, NULL);
6991     }
6992 }
6993
6994 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6995 {   // front-end-free part taken out of PieceMenuPopup
6996     int whichMenu; int xSqr, ySqr;
6997
6998     if(seekGraphUp) { // [HGM] seekgraph
6999         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7000         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7001         return -2;
7002     }
7003
7004     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7005          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7006         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7007         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7008         if(action == Press)   {
7009             originalFlip = flipView;
7010             flipView = !flipView; // temporarily flip board to see game from partners perspective
7011             DrawPosition(TRUE, partnerBoard);
7012             DisplayMessage(partnerStatus, "");
7013             partnerUp = TRUE;
7014         } else if(action == Release) {
7015             flipView = originalFlip;
7016             DrawPosition(TRUE, boards[currentMove]);
7017             partnerUp = FALSE;
7018         }
7019         return -2;
7020     }
7021
7022     xSqr = EventToSquare(x, BOARD_WIDTH);
7023     ySqr = EventToSquare(y, BOARD_HEIGHT);
7024     if (action == Release) {
7025         if(pieceSweep != EmptySquare) {
7026             EditPositionMenuEvent(pieceSweep, toX, toY);
7027             pieceSweep = EmptySquare;
7028         } else UnLoadPV(); // [HGM] pv
7029     }
7030     if (action != Press) return -2; // return code to be ignored
7031     switch (gameMode) {
7032       case IcsExamining:
7033         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7034       case EditPosition:
7035         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7036         if (xSqr < 0 || ySqr < 0) return -1;
7037         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7038         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7039         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7040         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7041         NextPiece(0);
7042         return -2;\r
7043       case IcsObserving:
7044         if(!appData.icsEngineAnalyze) return -1;
7045       case IcsPlayingWhite:
7046       case IcsPlayingBlack:
7047         if(!appData.zippyPlay) goto noZip;
7048       case AnalyzeMode:
7049       case AnalyzeFile:
7050       case MachinePlaysWhite:
7051       case MachinePlaysBlack:
7052       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7053         if (!appData.dropMenu) {
7054           LoadPV(x, y);
7055           return 2; // flag front-end to grab mouse events
7056         }
7057         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7058            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7059       case EditGame:
7060       noZip:
7061         if (xSqr < 0 || ySqr < 0) return -1;
7062         if (!appData.dropMenu || appData.testLegality &&
7063             gameInfo.variant != VariantBughouse &&
7064             gameInfo.variant != VariantCrazyhouse) return -1;
7065         whichMenu = 1; // drop menu
7066         break;
7067       default:
7068         return -1;
7069     }
7070
7071     if (((*fromX = xSqr) < 0) ||
7072         ((*fromY = ySqr) < 0)) {
7073         *fromX = *fromY = -1;
7074         return -1;
7075     }
7076     if (flipView)
7077       *fromX = BOARD_WIDTH - 1 - *fromX;
7078     else
7079       *fromY = BOARD_HEIGHT - 1 - *fromY;
7080
7081     return whichMenu;
7082 }
7083
7084 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7085 {
7086 //    char * hint = lastHint;
7087     FrontEndProgramStats stats;
7088
7089     stats.which = cps == &first ? 0 : 1;
7090     stats.depth = cpstats->depth;
7091     stats.nodes = cpstats->nodes;
7092     stats.score = cpstats->score;
7093     stats.time = cpstats->time;
7094     stats.pv = cpstats->movelist;
7095     stats.hint = lastHint;
7096     stats.an_move_index = 0;
7097     stats.an_move_count = 0;
7098
7099     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7100         stats.hint = cpstats->move_name;
7101         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7102         stats.an_move_count = cpstats->nr_moves;
7103     }
7104
7105     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
7106
7107     SetProgramStats( &stats );
7108 }
7109
7110 #define MAXPLAYERS 500
7111
7112 char *
7113 TourneyStandings(int display)
7114 {
7115     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7116     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7117     char result, *p, *names[MAXPLAYERS];
7118
7119     names[0] = p = strdup(appData.participants);
7120     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7121
7122     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7123
7124     while(result = appData.results[nr]) {
7125         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7126         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7127         wScore = bScore = 0;
7128         switch(result) {
7129           case '+': wScore = 2; break;
7130           case '-': bScore = 2; break;
7131           case '=': wScore = bScore = 1; break;
7132           case ' ':
7133           case '*': return strdup("busy"); // tourney not finished
7134         }
7135         score[w] += wScore;
7136         score[b] += bScore;
7137         games[w]++;
7138         games[b]++;
7139         nr++;
7140     }
7141     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7142     for(w=0; w<nPlayers; w++) {
7143         bScore = -1;
7144         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7145         ranking[w] = b; points[w] = bScore; score[b] = -2;
7146     }
7147     p = malloc(nPlayers*34+1);
7148     for(w=0; w<nPlayers && w<display; w++)
7149         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7150     free(names[0]);
7151     return p;
7152 }
7153
7154 void
7155 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7156 {       // count all piece types
7157         int p, f, r;
7158         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7159         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7160         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7161                 p = board[r][f];
7162                 pCnt[p]++;
7163                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7164                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7165                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7166                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7167                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7168                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7169         }
7170 }
7171
7172 int
7173 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7174 {
7175         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7176         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7177
7178         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7179         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7180         if(myPawns == 2 && nMine == 3) // KPP
7181             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7182         if(myPawns == 1 && nMine == 2) // KP
7183             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7184         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7185             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7186         if(myPawns) return FALSE;
7187         if(pCnt[WhiteRook+side])
7188             return pCnt[BlackRook-side] ||
7189                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7190                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7191                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7192         if(pCnt[WhiteCannon+side]) {
7193             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7194             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7195         }
7196         if(pCnt[WhiteKnight+side])
7197             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7198         return FALSE;
7199 }
7200
7201 int
7202 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7203 {
7204         VariantClass v = gameInfo.variant;
7205
7206         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7207         if(v == VariantShatranj) return TRUE; // always winnable through baring
7208         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7209         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7210
7211         if(v == VariantXiangqi) {
7212                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7213
7214                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7215                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7216                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7217                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7218                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7219                 if(stale) // we have at least one last-rank P plus perhaps C
7220                     return majors // KPKX
7221                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7222                 else // KCA*E*
7223                     return pCnt[WhiteFerz+side] // KCAK
7224                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7225                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7226                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7227
7228         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7229                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7230
7231                 if(nMine == 1) return FALSE; // bare King
7232                 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
7233                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7234                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7235                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7236                 if(pCnt[WhiteKnight+side])
7237                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7238                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7239                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7240                 if(nBishops)
7241                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7242                 if(pCnt[WhiteAlfil+side])
7243                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7244                 if(pCnt[WhiteWazir+side])
7245                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7246         }
7247
7248         return TRUE;
7249 }
7250
7251 int
7252 Adjudicate(ChessProgramState *cps)
7253 {       // [HGM] some adjudications useful with buggy engines
7254         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7255         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7256         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7257         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7258         int k, count = 0; static int bare = 1;
7259         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7260         Boolean canAdjudicate = !appData.icsActive;
7261
7262         // most tests only when we understand the game, i.e. legality-checking on
7263             if( appData.testLegality )
7264             {   /* [HGM] Some more adjudications for obstinate engines */
7265                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7266                 static int moveCount = 6;
7267                 ChessMove result;
7268                 char *reason = NULL;
7269
7270                 /* Count what is on board. */
7271                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7272
7273                 /* Some material-based adjudications that have to be made before stalemate test */
7274                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7275                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7276                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7277                      if(canAdjudicate && appData.checkMates) {
7278                          if(engineOpponent)
7279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7280                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7281                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7282                          return 1;
7283                      }
7284                 }
7285
7286                 /* Bare King in Shatranj (loses) or Losers (wins) */
7287                 if( nrW == 1 || nrB == 1) {
7288                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7289                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7290                      if(canAdjudicate && appData.checkMates) {
7291                          if(engineOpponent)
7292                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7293                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7294                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7295                          return 1;
7296                      }
7297                   } else
7298                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7299                   {    /* bare King */
7300                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7301                         if(canAdjudicate && appData.checkMates) {
7302                             /* but only adjudicate if adjudication enabled */
7303                             if(engineOpponent)
7304                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7305                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7306                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7307                             return 1;
7308                         }
7309                   }
7310                 } else bare = 1;
7311
7312
7313             // don't wait for engine to announce game end if we can judge ourselves
7314             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7315               case MT_CHECK:
7316                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7317                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7318                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7319                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7320                             checkCnt++;
7321                         if(checkCnt >= 2) {
7322                             reason = "Xboard adjudication: 3rd check";
7323                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7324                             break;
7325                         }
7326                     }
7327                 }
7328               case MT_NONE:
7329               default:
7330                 break;
7331               case MT_STALEMATE:
7332               case MT_STAINMATE:
7333                 reason = "Xboard adjudication: Stalemate";
7334                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7335                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7336                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7337                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7338                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7339                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7340                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7341                                                                         EP_CHECKMATE : EP_WINS);
7342                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7343                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7344                 }
7345                 break;
7346               case MT_CHECKMATE:
7347                 reason = "Xboard adjudication: Checkmate";
7348                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7349                 break;
7350             }
7351
7352                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7353                     case EP_STALEMATE:
7354                         result = GameIsDrawn; break;
7355                     case EP_CHECKMATE:
7356                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7357                     case EP_WINS:
7358                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7359                     default:
7360                         result = EndOfFile;
7361                 }
7362                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7363                     if(engineOpponent)
7364                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7365                     GameEnds( result, reason, GE_XBOARD );
7366                     return 1;
7367                 }
7368
7369                 /* Next absolutely insufficient mating material. */
7370                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7371                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7372                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7373
7374                      /* always flag draws, for judging claims */
7375                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7376
7377                      if(canAdjudicate && appData.materialDraws) {
7378                          /* but only adjudicate them if adjudication enabled */
7379                          if(engineOpponent) {
7380                            SendToProgram("force\n", engineOpponent); // suppress reply
7381                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7382                          }
7383                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7384                          return 1;
7385                      }
7386                 }
7387
7388                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7389                 if(gameInfo.variant == VariantXiangqi ?
7390                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7391                  : nrW + nrB == 4 &&
7392                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7393                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7394                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7395                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7396                    ) ) {
7397                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7398                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7399                           if(engineOpponent) {
7400                             SendToProgram("force\n", engineOpponent); // suppress reply
7401                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7402                           }
7403                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7404                           return 1;
7405                      }
7406                 } else moveCount = 6;
7407             }
7408         if (appData.debugMode) { int i;
7409             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7410                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7411                     appData.drawRepeats);
7412             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7413               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7414
7415         }
7416
7417         // Repetition draws and 50-move rule can be applied independently of legality testing
7418
7419                 /* Check for rep-draws */
7420                 count = 0;
7421                 for(k = forwardMostMove-2;
7422                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7423                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7424                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7425                     k-=2)
7426                 {   int rights=0;
7427                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7428                         /* compare castling rights */
7429                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7430                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7431                                 rights++; /* King lost rights, while rook still had them */
7432                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7433                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7434                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7435                                    rights++; /* but at least one rook lost them */
7436                         }
7437                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7438                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7439                                 rights++;
7440                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7441                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7442                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7443                                    rights++;
7444                         }
7445                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7446                             && appData.drawRepeats > 1) {
7447                              /* adjudicate after user-specified nr of repeats */
7448                              int result = GameIsDrawn;
7449                              char *details = "XBoard adjudication: repetition draw";
7450                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7451                                 // [HGM] xiangqi: check for forbidden perpetuals
7452                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7453                                 for(m=forwardMostMove; m>k; m-=2) {
7454                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7455                                         ourPerpetual = 0; // the current mover did not always check
7456                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7457                                         hisPerpetual = 0; // the opponent did not always check
7458                                 }
7459                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7460                                                                         ourPerpetual, hisPerpetual);
7461                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7462                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7463                                     details = "Xboard adjudication: perpetual checking";
7464                                 } else
7465                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7466                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7467                                 } else
7468                                 // Now check for perpetual chases
7469                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7470                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7471                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7472                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7473                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7474                                         details = "Xboard adjudication: perpetual chasing";
7475                                     } else
7476                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7477                                         break; // Abort repetition-checking loop.
7478                                 }
7479                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7480                              }
7481                              if(engineOpponent) {
7482                                SendToProgram("force\n", engineOpponent); // suppress reply
7483                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7484                              }
7485                              GameEnds( result, details, GE_XBOARD );
7486                              return 1;
7487                         }
7488                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7489                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7490                     }
7491                 }
7492
7493                 /* Now we test for 50-move draws. Determine ply count */
7494                 count = forwardMostMove;
7495                 /* look for last irreversble move */
7496                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7497                     count--;
7498                 /* if we hit starting position, add initial plies */
7499                 if( count == backwardMostMove )
7500                     count -= initialRulePlies;
7501                 count = forwardMostMove - count;
7502                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7503                         // adjust reversible move counter for checks in Xiangqi
7504                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7505                         if(i < backwardMostMove) i = backwardMostMove;
7506                         while(i <= forwardMostMove) {
7507                                 lastCheck = inCheck; // check evasion does not count
7508                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7509                                 if(inCheck || lastCheck) count--; // check does not count
7510                                 i++;
7511                         }
7512                 }
7513                 if( count >= 100)
7514                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7515                          /* this is used to judge if draw claims are legal */
7516                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7517                          if(engineOpponent) {
7518                            SendToProgram("force\n", engineOpponent); // suppress reply
7519                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7520                          }
7521                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7522                          return 1;
7523                 }
7524
7525                 /* if draw offer is pending, treat it as a draw claim
7526                  * when draw condition present, to allow engines a way to
7527                  * claim draws before making their move to avoid a race
7528                  * condition occurring after their move
7529                  */
7530                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7531                          char *p = NULL;
7532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7533                              p = "Draw claim: 50-move rule";
7534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7535                              p = "Draw claim: 3-fold repetition";
7536                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7537                              p = "Draw claim: insufficient mating material";
7538                          if( p != NULL && canAdjudicate) {
7539                              if(engineOpponent) {
7540                                SendToProgram("force\n", engineOpponent); // suppress reply
7541                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7542                              }
7543                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7544                              return 1;
7545                          }
7546                 }
7547
7548                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7549                     if(engineOpponent) {
7550                       SendToProgram("force\n", engineOpponent); // suppress reply
7551                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7552                     }
7553                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7554                     return 1;
7555                 }
7556         return 0;
7557 }
7558
7559 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7560 {   // [HGM] book: this routine intercepts moves to simulate book replies
7561     char *bookHit = NULL;
7562
7563     //first determine if the incoming move brings opponent into his book
7564     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7565         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7566     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7567     if(bookHit != NULL && !cps->bookSuspend) {
7568         // make sure opponent is not going to reply after receiving move to book position
7569         SendToProgram("force\n", cps);
7570         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7571     }
7572     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7573     // now arrange restart after book miss
7574     if(bookHit) {
7575         // after a book hit we never send 'go', and the code after the call to this routine
7576         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7577         char buf[MSG_SIZ], *move = bookHit;
7578         if(cps->useSAN) {
7579             int fromX, fromY, toX, toY;
7580             char promoChar;
7581             ChessMove moveType;
7582             move = buf + 30;
7583             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7584                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7585                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7586                                     PosFlags(forwardMostMove),
7587                                     fromY, fromX, toY, toX, promoChar, move);
7588             } else {
7589                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7590                 bookHit = NULL;
7591             }
7592         }
7593         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7594         SendToProgram(buf, cps);
7595         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7596     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7597         SendToProgram("go\n", cps);
7598         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7599     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7600         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7601             SendToProgram("go\n", cps);
7602         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7603     }
7604     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7605 }
7606
7607 char *savedMessage;
7608 ChessProgramState *savedState;
7609 void DeferredBookMove(void)
7610 {
7611         if(savedState->lastPing != savedState->lastPong)
7612                     ScheduleDelayedEvent(DeferredBookMove, 10);
7613         else
7614         HandleMachineMove(savedMessage, savedState);
7615 }
7616
7617 void
7618 HandleMachineMove(message, cps)
7619      char *message;
7620      ChessProgramState *cps;
7621 {
7622     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7623     char realname[MSG_SIZ];
7624     int fromX, fromY, toX, toY;
7625     ChessMove moveType;
7626     char promoChar;
7627     char *p;
7628     int machineWhite;
7629     char *bookHit;
7630
7631     cps->userError = 0;
7632
7633 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7634     /*
7635      * Kludge to ignore BEL characters
7636      */
7637     while (*message == '\007') message++;
7638
7639     /*
7640      * [HGM] engine debug message: ignore lines starting with '#' character
7641      */
7642     if(cps->debug && *message == '#') return;
7643
7644     /*
7645      * Look for book output
7646      */
7647     if (cps == &first && bookRequested) {
7648         if (message[0] == '\t' || message[0] == ' ') {
7649             /* Part of the book output is here; append it */
7650             strcat(bookOutput, message);
7651             strcat(bookOutput, "  \n");
7652             return;
7653         } else if (bookOutput[0] != NULLCHAR) {
7654             /* All of book output has arrived; display it */
7655             char *p = bookOutput;
7656             while (*p != NULLCHAR) {
7657                 if (*p == '\t') *p = ' ';
7658                 p++;
7659             }
7660             DisplayInformation(bookOutput);
7661             bookRequested = FALSE;
7662             /* Fall through to parse the current output */
7663         }
7664     }
7665
7666     /*
7667      * Look for machine move.
7668      */
7669     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7670         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7671     {
7672         /* This method is only useful on engines that support ping */
7673         if (cps->lastPing != cps->lastPong) {
7674           if (gameMode == BeginningOfGame) {
7675             /* Extra move from before last new; ignore */
7676             if (appData.debugMode) {
7677                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7678             }
7679           } else {
7680             if (appData.debugMode) {
7681                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7682                         cps->which, gameMode);
7683             }
7684
7685             SendToProgram("undo\n", cps);
7686           }
7687           return;
7688         }
7689
7690         switch (gameMode) {
7691           case BeginningOfGame:
7692             /* Extra move from before last reset; ignore */
7693             if (appData.debugMode) {
7694                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7695             }
7696             return;
7697
7698           case EndOfGame:
7699           case IcsIdle:
7700           default:
7701             /* Extra move after we tried to stop.  The mode test is
7702                not a reliable way of detecting this problem, but it's
7703                the best we can do on engines that don't support ping.
7704             */
7705             if (appData.debugMode) {
7706                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7707                         cps->which, gameMode);
7708             }
7709             SendToProgram("undo\n", cps);
7710             return;
7711
7712           case MachinePlaysWhite:
7713           case IcsPlayingWhite:
7714             machineWhite = TRUE;
7715             break;
7716
7717           case MachinePlaysBlack:
7718           case IcsPlayingBlack:
7719             machineWhite = FALSE;
7720             break;
7721
7722           case TwoMachinesPlay:
7723             machineWhite = (cps->twoMachinesColor[0] == 'w');
7724             break;
7725         }
7726         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7727             if (appData.debugMode) {
7728                 fprintf(debugFP,
7729                         "Ignoring move out of turn by %s, gameMode %d"
7730                         ", forwardMost %d\n",
7731                         cps->which, gameMode, forwardMostMove);
7732             }
7733             return;
7734         }
7735
7736     if (appData.debugMode) { int f = forwardMostMove;
7737         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7738                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7739                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7740     }
7741         if(cps->alphaRank) AlphaRank(machineMove, 4);
7742         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7743                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7744             /* Machine move could not be parsed; ignore it. */
7745           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7746                     machineMove, _(cps->which));
7747             DisplayError(buf1, 0);
7748             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7749                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7750             if (gameMode == TwoMachinesPlay) {
7751               GameEnds(machineWhite ? BlackWins : WhiteWins,
7752                        buf1, GE_XBOARD);
7753             }
7754             return;
7755         }
7756
7757         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7758         /* So we have to redo legality test with true e.p. status here,  */
7759         /* to make sure an illegal e.p. capture does not slip through,   */
7760         /* to cause a forfeit on a justified illegal-move complaint      */
7761         /* of the opponent.                                              */
7762         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7763            ChessMove moveType;
7764            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7765                              fromY, fromX, toY, toX, promoChar);
7766             if (appData.debugMode) {
7767                 int i;
7768                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7769                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7770                 fprintf(debugFP, "castling rights\n");
7771             }
7772             if(moveType == IllegalMove) {
7773               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7774                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7775                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7776                            buf1, GE_XBOARD);
7777                 return;
7778            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7779            /* [HGM] Kludge to handle engines that send FRC-style castling
7780               when they shouldn't (like TSCP-Gothic) */
7781            switch(moveType) {
7782              case WhiteASideCastleFR:
7783              case BlackASideCastleFR:
7784                toX+=2;
7785                currentMoveString[2]++;
7786                break;
7787              case WhiteHSideCastleFR:
7788              case BlackHSideCastleFR:
7789                toX--;
7790                currentMoveString[2]--;
7791                break;
7792              default: ; // nothing to do, but suppresses warning of pedantic compilers
7793            }
7794         }
7795         hintRequested = FALSE;
7796         lastHint[0] = NULLCHAR;
7797         bookRequested = FALSE;
7798         /* Program may be pondering now */
7799         cps->maybeThinking = TRUE;
7800         if (cps->sendTime == 2) cps->sendTime = 1;
7801         if (cps->offeredDraw) cps->offeredDraw--;
7802
7803         /* [AS] Save move info*/
7804         pvInfoList[ forwardMostMove ].score = programStats.score;
7805         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7806         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7807
7808         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7809
7810         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7811         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7812             int count = 0;
7813
7814             while( count < adjudicateLossPlies ) {
7815                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7816
7817                 if( count & 1 ) {
7818                     score = -score; /* Flip score for winning side */
7819                 }
7820
7821                 if( score > adjudicateLossThreshold ) {
7822                     break;
7823                 }
7824
7825                 count++;
7826             }
7827
7828             if( count >= adjudicateLossPlies ) {
7829                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7830
7831                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7832                     "Xboard adjudication",
7833                     GE_XBOARD );
7834
7835                 return;
7836             }
7837         }
7838
7839         if(Adjudicate(cps)) {
7840             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7841             return; // [HGM] adjudicate: for all automatic game ends
7842         }
7843
7844 #if ZIPPY
7845         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7846             first.initDone) {
7847           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7848                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7849                 SendToICS("draw ");
7850                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7851           }
7852           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7853           ics_user_moved = 1;
7854           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7855                 char buf[3*MSG_SIZ];
7856
7857                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7858                         programStats.score / 100.,
7859                         programStats.depth,
7860                         programStats.time / 100.,
7861                         (unsigned int)programStats.nodes,
7862                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7863                         programStats.movelist);
7864                 SendToICS(buf);
7865 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7866           }
7867         }
7868 #endif
7869
7870         /* [AS] Clear stats for next move */
7871         ClearProgramStats();
7872         thinkOutput[0] = NULLCHAR;
7873         hiddenThinkOutputState = 0;
7874
7875         bookHit = NULL;
7876         if (gameMode == TwoMachinesPlay) {
7877             /* [HGM] relaying draw offers moved to after reception of move */
7878             /* and interpreting offer as claim if it brings draw condition */
7879             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7880                 SendToProgram("draw\n", cps->other);
7881             }
7882             if (cps->other->sendTime) {
7883                 SendTimeRemaining(cps->other,
7884                                   cps->other->twoMachinesColor[0] == 'w');
7885             }
7886             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7887             if (firstMove && !bookHit) {
7888                 firstMove = FALSE;
7889                 if (cps->other->useColors) {
7890                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7891                 }
7892                 SendToProgram("go\n", cps->other);
7893             }
7894             cps->other->maybeThinking = TRUE;
7895         }
7896
7897         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7898
7899         if (!pausing && appData.ringBellAfterMoves) {
7900             RingBell();
7901         }
7902
7903         /*
7904          * Reenable menu items that were disabled while
7905          * machine was thinking
7906          */
7907         if (gameMode != TwoMachinesPlay)
7908             SetUserThinkingEnables();
7909
7910         // [HGM] book: after book hit opponent has received move and is now in force mode
7911         // force the book reply into it, and then fake that it outputted this move by jumping
7912         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7913         if(bookHit) {
7914                 static char bookMove[MSG_SIZ]; // a bit generous?
7915
7916                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7917                 strcat(bookMove, bookHit);
7918                 message = bookMove;
7919                 cps = cps->other;
7920                 programStats.nodes = programStats.depth = programStats.time =
7921                 programStats.score = programStats.got_only_move = 0;
7922                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7923
7924                 if(cps->lastPing != cps->lastPong) {
7925                     savedMessage = message; // args for deferred call
7926                     savedState = cps;
7927                     ScheduleDelayedEvent(DeferredBookMove, 10);
7928                     return;
7929                 }
7930                 goto FakeBookMove;
7931         }
7932
7933         return;
7934     }
7935
7936     /* Set special modes for chess engines.  Later something general
7937      *  could be added here; for now there is just one kludge feature,
7938      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7939      *  when "xboard" is given as an interactive command.
7940      */
7941     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7942         cps->useSigint = FALSE;
7943         cps->useSigterm = FALSE;
7944     }
7945     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7946       ParseFeatures(message+8, cps);
7947       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7948     }
7949
7950     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7951       int dummy, s=6; char buf[MSG_SIZ];
7952       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7953       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7954       ParseFEN(boards[0], &dummy, message+s);
7955       DrawPosition(TRUE, boards[0]);
7956       startedFromSetupPosition = TRUE;
7957       return;
7958     }
7959     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7960      * want this, I was asked to put it in, and obliged.
7961      */
7962     if (!strncmp(message, "setboard ", 9)) {
7963         Board initial_position;
7964
7965         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7966
7967         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7968             DisplayError(_("Bad FEN received from engine"), 0);
7969             return ;
7970         } else {
7971            Reset(TRUE, FALSE);
7972            CopyBoard(boards[0], initial_position);
7973            initialRulePlies = FENrulePlies;
7974            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7975            else gameMode = MachinePlaysBlack;
7976            DrawPosition(FALSE, boards[currentMove]);
7977         }
7978         return;
7979     }
7980
7981     /*
7982      * Look for communication commands
7983      */
7984     if (!strncmp(message, "telluser ", 9)) {
7985         if(message[9] == '\\' && message[10] == '\\')
7986             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7987         DisplayNote(message + 9);
7988         return;
7989     }
7990     if (!strncmp(message, "tellusererror ", 14)) {
7991         cps->userError = 1;
7992         if(message[14] == '\\' && message[15] == '\\')
7993             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7994         DisplayError(message + 14, 0);
7995         return;
7996     }
7997     if (!strncmp(message, "tellopponent ", 13)) {
7998       if (appData.icsActive) {
7999         if (loggedOn) {
8000           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8001           SendToICS(buf1);
8002         }
8003       } else {
8004         DisplayNote(message + 13);
8005       }
8006       return;
8007     }
8008     if (!strncmp(message, "tellothers ", 11)) {
8009       if (appData.icsActive) {
8010         if (loggedOn) {
8011           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8012           SendToICS(buf1);
8013         }
8014       }
8015       return;
8016     }
8017     if (!strncmp(message, "tellall ", 8)) {
8018       if (appData.icsActive) {
8019         if (loggedOn) {
8020           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8021           SendToICS(buf1);
8022         }
8023       } else {
8024         DisplayNote(message + 8);
8025       }
8026       return;
8027     }
8028     if (strncmp(message, "warning", 7) == 0) {
8029         /* Undocumented feature, use tellusererror in new code */
8030         DisplayError(message, 0);
8031         return;
8032     }
8033     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8034         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8035         strcat(realname, " query");
8036         AskQuestion(realname, buf2, buf1, cps->pr);
8037         return;
8038     }
8039     /* Commands from the engine directly to ICS.  We don't allow these to be
8040      *  sent until we are logged on. Crafty kibitzes have been known to
8041      *  interfere with the login process.
8042      */
8043     if (loggedOn) {
8044         if (!strncmp(message, "tellics ", 8)) {
8045             SendToICS(message + 8);
8046             SendToICS("\n");
8047             return;
8048         }
8049         if (!strncmp(message, "tellicsnoalias ", 15)) {
8050             SendToICS(ics_prefix);
8051             SendToICS(message + 15);
8052             SendToICS("\n");
8053             return;
8054         }
8055         /* The following are for backward compatibility only */
8056         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8057             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8058             SendToICS(ics_prefix);
8059             SendToICS(message);
8060             SendToICS("\n");
8061             return;
8062         }
8063     }
8064     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8065         return;
8066     }
8067     /*
8068      * If the move is illegal, cancel it and redraw the board.
8069      * Also deal with other error cases.  Matching is rather loose
8070      * here to accommodate engines written before the spec.
8071      */
8072     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8073         strncmp(message, "Error", 5) == 0) {
8074         if (StrStr(message, "name") ||
8075             StrStr(message, "rating") || StrStr(message, "?") ||
8076             StrStr(message, "result") || StrStr(message, "board") ||
8077             StrStr(message, "bk") || StrStr(message, "computer") ||
8078             StrStr(message, "variant") || StrStr(message, "hint") ||
8079             StrStr(message, "random") || StrStr(message, "depth") ||
8080             StrStr(message, "accepted")) {
8081             return;
8082         }
8083         if (StrStr(message, "protover")) {
8084           /* Program is responding to input, so it's apparently done
8085              initializing, and this error message indicates it is
8086              protocol version 1.  So we don't need to wait any longer
8087              for it to initialize and send feature commands. */
8088           FeatureDone(cps, 1);
8089           cps->protocolVersion = 1;
8090           return;
8091         }
8092         cps->maybeThinking = FALSE;
8093
8094         if (StrStr(message, "draw")) {
8095             /* Program doesn't have "draw" command */
8096             cps->sendDrawOffers = 0;
8097             return;
8098         }
8099         if (cps->sendTime != 1 &&
8100             (StrStr(message, "time") || StrStr(message, "otim"))) {
8101           /* Program apparently doesn't have "time" or "otim" command */
8102           cps->sendTime = 0;
8103           return;
8104         }
8105         if (StrStr(message, "analyze")) {
8106             cps->analysisSupport = FALSE;
8107             cps->analyzing = FALSE;
8108             Reset(FALSE, TRUE);
8109             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8110             DisplayError(buf2, 0);
8111             return;
8112         }
8113         if (StrStr(message, "(no matching move)st")) {
8114           /* Special kludge for GNU Chess 4 only */
8115           cps->stKludge = TRUE;
8116           SendTimeControl(cps, movesPerSession, timeControl,
8117                           timeIncrement, appData.searchDepth,
8118                           searchTime);
8119           return;
8120         }
8121         if (StrStr(message, "(no matching move)sd")) {
8122           /* Special kludge for GNU Chess 4 only */
8123           cps->sdKludge = TRUE;
8124           SendTimeControl(cps, movesPerSession, timeControl,
8125                           timeIncrement, appData.searchDepth,
8126                           searchTime);
8127           return;
8128         }
8129         if (!StrStr(message, "llegal")) {
8130             return;
8131         }
8132         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8133             gameMode == IcsIdle) return;
8134         if (forwardMostMove <= backwardMostMove) return;
8135         if (pausing) PauseEvent();
8136       if(appData.forceIllegal) {
8137             // [HGM] illegal: machine refused move; force position after move into it
8138           SendToProgram("force\n", cps);
8139           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8140                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8141                 // when black is to move, while there might be nothing on a2 or black
8142                 // might already have the move. So send the board as if white has the move.
8143                 // But first we must change the stm of the engine, as it refused the last move
8144                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8145                 if(WhiteOnMove(forwardMostMove)) {
8146                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8147                     SendBoard(cps, forwardMostMove); // kludgeless board
8148                 } else {
8149                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8150                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8151                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8152                 }
8153           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8154             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8155                  gameMode == TwoMachinesPlay)
8156               SendToProgram("go\n", cps);
8157             return;
8158       } else
8159         if (gameMode == PlayFromGameFile) {
8160             /* Stop reading this game file */
8161             gameMode = EditGame;
8162             ModeHighlight();
8163         }
8164         /* [HGM] illegal-move claim should forfeit game when Xboard */
8165         /* only passes fully legal moves                            */
8166         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8167             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8168                                 "False illegal-move claim", GE_XBOARD );
8169             return; // do not take back move we tested as valid
8170         }
8171         currentMove = forwardMostMove-1;
8172         DisplayMove(currentMove-1); /* before DisplayMoveError */
8173         SwitchClocks(forwardMostMove-1); // [HGM] race
8174         DisplayBothClocks();
8175         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8176                 parseList[currentMove], _(cps->which));
8177         DisplayMoveError(buf1);
8178         DrawPosition(FALSE, boards[currentMove]);
8179         return;
8180     }
8181     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8182         /* Program has a broken "time" command that
8183            outputs a string not ending in newline.
8184            Don't use it. */
8185         cps->sendTime = 0;
8186     }
8187
8188     /*
8189      * If chess program startup fails, exit with an error message.
8190      * Attempts to recover here are futile.
8191      */
8192     if ((StrStr(message, "unknown host") != NULL)
8193         || (StrStr(message, "No remote directory") != NULL)
8194         || (StrStr(message, "not found") != NULL)
8195         || (StrStr(message, "No such file") != NULL)
8196         || (StrStr(message, "can't alloc") != NULL)
8197         || (StrStr(message, "Permission denied") != NULL)) {
8198
8199         cps->maybeThinking = FALSE;
8200         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8201                 _(cps->which), cps->program, cps->host, message);
8202         RemoveInputSource(cps->isr);
8203         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8204             if(cps == &first) appData.noChessProgram = TRUE;
8205             DisplayError(buf1, 0);
8206         }
8207         return;
8208     }
8209
8210     /*
8211      * Look for hint output
8212      */
8213     if (sscanf(message, "Hint: %s", buf1) == 1) {
8214         if (cps == &first && hintRequested) {
8215             hintRequested = FALSE;
8216             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8217                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8218                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8219                                     PosFlags(forwardMostMove),
8220                                     fromY, fromX, toY, toX, promoChar, buf1);
8221                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8222                 DisplayInformation(buf2);
8223             } else {
8224                 /* Hint move could not be parsed!? */
8225               snprintf(buf2, sizeof(buf2),
8226                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8227                         buf1, _(cps->which));
8228                 DisplayError(buf2, 0);
8229             }
8230         } else {
8231           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8232         }
8233         return;
8234     }
8235
8236     /*
8237      * Ignore other messages if game is not in progress
8238      */
8239     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8240         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8241
8242     /*
8243      * look for win, lose, draw, or draw offer
8244      */
8245     if (strncmp(message, "1-0", 3) == 0) {
8246         char *p, *q, *r = "";
8247         p = strchr(message, '{');
8248         if (p) {
8249             q = strchr(p, '}');
8250             if (q) {
8251                 *q = NULLCHAR;
8252                 r = p + 1;
8253             }
8254         }
8255         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8256         return;
8257     } else if (strncmp(message, "0-1", 3) == 0) {
8258         char *p, *q, *r = "";
8259         p = strchr(message, '{');
8260         if (p) {
8261             q = strchr(p, '}');
8262             if (q) {
8263                 *q = NULLCHAR;
8264                 r = p + 1;
8265             }
8266         }
8267         /* Kludge for Arasan 4.1 bug */
8268         if (strcmp(r, "Black resigns") == 0) {
8269             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8270             return;
8271         }
8272         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8273         return;
8274     } else if (strncmp(message, "1/2", 3) == 0) {
8275         char *p, *q, *r = "";
8276         p = strchr(message, '{');
8277         if (p) {
8278             q = strchr(p, '}');
8279             if (q) {
8280                 *q = NULLCHAR;
8281                 r = p + 1;
8282             }
8283         }
8284
8285         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8286         return;
8287
8288     } else if (strncmp(message, "White resign", 12) == 0) {
8289         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8290         return;
8291     } else if (strncmp(message, "Black resign", 12) == 0) {
8292         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8293         return;
8294     } else if (strncmp(message, "White matches", 13) == 0 ||
8295                strncmp(message, "Black matches", 13) == 0   ) {
8296         /* [HGM] ignore GNUShogi noises */
8297         return;
8298     } else if (strncmp(message, "White", 5) == 0 &&
8299                message[5] != '(' &&
8300                StrStr(message, "Black") == NULL) {
8301         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8302         return;
8303     } else if (strncmp(message, "Black", 5) == 0 &&
8304                message[5] != '(') {
8305         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8306         return;
8307     } else if (strcmp(message, "resign") == 0 ||
8308                strcmp(message, "computer resigns") == 0) {
8309         switch (gameMode) {
8310           case MachinePlaysBlack:
8311           case IcsPlayingBlack:
8312             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8313             break;
8314           case MachinePlaysWhite:
8315           case IcsPlayingWhite:
8316             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8317             break;
8318           case TwoMachinesPlay:
8319             if (cps->twoMachinesColor[0] == 'w')
8320               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8321             else
8322               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8323             break;
8324           default:
8325             /* can't happen */
8326             break;
8327         }
8328         return;
8329     } else if (strncmp(message, "opponent mates", 14) == 0) {
8330         switch (gameMode) {
8331           case MachinePlaysBlack:
8332           case IcsPlayingBlack:
8333             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8334             break;
8335           case MachinePlaysWhite:
8336           case IcsPlayingWhite:
8337             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8338             break;
8339           case TwoMachinesPlay:
8340             if (cps->twoMachinesColor[0] == 'w')
8341               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8342             else
8343               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8344             break;
8345           default:
8346             /* can't happen */
8347             break;
8348         }
8349         return;
8350     } else if (strncmp(message, "computer mates", 14) == 0) {
8351         switch (gameMode) {
8352           case MachinePlaysBlack:
8353           case IcsPlayingBlack:
8354             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8355             break;
8356           case MachinePlaysWhite:
8357           case IcsPlayingWhite:
8358             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8359             break;
8360           case TwoMachinesPlay:
8361             if (cps->twoMachinesColor[0] == 'w')
8362               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8363             else
8364               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8365             break;
8366           default:
8367             /* can't happen */
8368             break;
8369         }
8370         return;
8371     } else if (strncmp(message, "checkmate", 9) == 0) {
8372         if (WhiteOnMove(forwardMostMove)) {
8373             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8374         } else {
8375             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8376         }
8377         return;
8378     } else if (strstr(message, "Draw") != NULL ||
8379                strstr(message, "game is a draw") != NULL) {
8380         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8381         return;
8382     } else if (strstr(message, "offer") != NULL &&
8383                strstr(message, "draw") != NULL) {
8384 #if ZIPPY
8385         if (appData.zippyPlay && first.initDone) {
8386             /* Relay offer to ICS */
8387             SendToICS(ics_prefix);
8388             SendToICS("draw\n");
8389         }
8390 #endif
8391         cps->offeredDraw = 2; /* valid until this engine moves twice */
8392         if (gameMode == TwoMachinesPlay) {
8393             if (cps->other->offeredDraw) {
8394                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8395             /* [HGM] in two-machine mode we delay relaying draw offer      */
8396             /* until after we also have move, to see if it is really claim */
8397             }
8398         } else if (gameMode == MachinePlaysWhite ||
8399                    gameMode == MachinePlaysBlack) {
8400           if (userOfferedDraw) {
8401             DisplayInformation(_("Machine accepts your draw offer"));
8402             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8403           } else {
8404             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8405           }
8406         }
8407     }
8408
8409
8410     /*
8411      * Look for thinking output
8412      */
8413     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8414           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8415                                 ) {
8416         int plylev, mvleft, mvtot, curscore, time;
8417         char mvname[MOVE_LEN];
8418         u64 nodes; // [DM]
8419         char plyext;
8420         int ignore = FALSE;
8421         int prefixHint = FALSE;
8422         mvname[0] = NULLCHAR;
8423
8424         switch (gameMode) {
8425           case MachinePlaysBlack:
8426           case IcsPlayingBlack:
8427             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8428             break;
8429           case MachinePlaysWhite:
8430           case IcsPlayingWhite:
8431             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8432             break;
8433           case AnalyzeMode:
8434           case AnalyzeFile:
8435             break;
8436           case IcsObserving: /* [DM] icsEngineAnalyze */
8437             if (!appData.icsEngineAnalyze) ignore = TRUE;
8438             break;
8439           case TwoMachinesPlay:
8440             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8441                 ignore = TRUE;
8442             }
8443             break;
8444           default:
8445             ignore = TRUE;
8446             break;
8447         }
8448
8449         if (!ignore) {
8450             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8451             buf1[0] = NULLCHAR;
8452             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8453                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8454
8455                 if (plyext != ' ' && plyext != '\t') {
8456                     time *= 100;
8457                 }
8458
8459                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8460                 if( cps->scoreIsAbsolute &&
8461                     ( gameMode == MachinePlaysBlack ||
8462                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8463                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8464                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8465                      !WhiteOnMove(currentMove)
8466                     ) )
8467                 {
8468                     curscore = -curscore;
8469                 }
8470
8471
8472                 tempStats.depth = plylev;
8473                 tempStats.nodes = nodes;
8474                 tempStats.time = time;
8475                 tempStats.score = curscore;
8476                 tempStats.got_only_move = 0;
8477
8478                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8479                         int ticklen;
8480
8481                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8482                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8483                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8484                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8485                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8486                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8487                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8488                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8489                 }
8490
8491                 /* Buffer overflow protection */
8492                 if (buf1[0] != NULLCHAR) {
8493                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8494                         && appData.debugMode) {
8495                         fprintf(debugFP,
8496                                 "PV is too long; using the first %u bytes.\n",
8497                                 (unsigned) sizeof(tempStats.movelist) - 1);
8498                     }
8499
8500                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8501                 } else {
8502                     sprintf(tempStats.movelist, " no PV\n");
8503                 }
8504
8505                 if (tempStats.seen_stat) {
8506                     tempStats.ok_to_send = 1;
8507                 }
8508
8509                 if (strchr(tempStats.movelist, '(') != NULL) {
8510                     tempStats.line_is_book = 1;
8511                     tempStats.nr_moves = 0;
8512                     tempStats.moves_left = 0;
8513                 } else {
8514                     tempStats.line_is_book = 0;
8515                 }
8516
8517                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8518                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8519
8520                 SendProgramStatsToFrontend( cps, &tempStats );
8521
8522                 /*
8523                     [AS] Protect the thinkOutput buffer from overflow... this
8524                     is only useful if buf1 hasn't overflowed first!
8525                 */
8526                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8527                          plylev,
8528                          (gameMode == TwoMachinesPlay ?
8529                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8530                          ((double) curscore) / 100.0,
8531                          prefixHint ? lastHint : "",
8532                          prefixHint ? " " : "" );
8533
8534                 if( buf1[0] != NULLCHAR ) {
8535                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8536
8537                     if( strlen(buf1) > max_len ) {
8538                         if( appData.debugMode) {
8539                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8540                         }
8541                         buf1[max_len+1] = '\0';
8542                     }
8543
8544                     strcat( thinkOutput, buf1 );
8545                 }
8546
8547                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8548                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8549                     DisplayMove(currentMove - 1);
8550                 }
8551                 return;
8552
8553             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8554                 /* crafty (9.25+) says "(only move) <move>"
8555                  * if there is only 1 legal move
8556                  */
8557                 sscanf(p, "(only move) %s", buf1);
8558                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8559                 sprintf(programStats.movelist, "%s (only move)", buf1);
8560                 programStats.depth = 1;
8561                 programStats.nr_moves = 1;
8562                 programStats.moves_left = 1;
8563                 programStats.nodes = 1;
8564                 programStats.time = 1;
8565                 programStats.got_only_move = 1;
8566
8567                 /* Not really, but we also use this member to
8568                    mean "line isn't going to change" (Crafty
8569                    isn't searching, so stats won't change) */
8570                 programStats.line_is_book = 1;
8571
8572                 SendProgramStatsToFrontend( cps, &programStats );
8573
8574                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8575                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8576                     DisplayMove(currentMove - 1);
8577                 }
8578                 return;
8579             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8580                               &time, &nodes, &plylev, &mvleft,
8581                               &mvtot, mvname) >= 5) {
8582                 /* The stat01: line is from Crafty (9.29+) in response
8583                    to the "." command */
8584                 programStats.seen_stat = 1;
8585                 cps->maybeThinking = TRUE;
8586
8587                 if (programStats.got_only_move || !appData.periodicUpdates)
8588                   return;
8589
8590                 programStats.depth = plylev;
8591                 programStats.time = time;
8592                 programStats.nodes = nodes;
8593                 programStats.moves_left = mvleft;
8594                 programStats.nr_moves = mvtot;
8595                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8596                 programStats.ok_to_send = 1;
8597                 programStats.movelist[0] = '\0';
8598
8599                 SendProgramStatsToFrontend( cps, &programStats );
8600
8601                 return;
8602
8603             } else if (strncmp(message,"++",2) == 0) {
8604                 /* Crafty 9.29+ outputs this */
8605                 programStats.got_fail = 2;
8606                 return;
8607
8608             } else if (strncmp(message,"--",2) == 0) {
8609                 /* Crafty 9.29+ outputs this */
8610                 programStats.got_fail = 1;
8611                 return;
8612
8613             } else if (thinkOutput[0] != NULLCHAR &&
8614                        strncmp(message, "    ", 4) == 0) {
8615                 unsigned message_len;
8616
8617                 p = message;
8618                 while (*p && *p == ' ') p++;
8619
8620                 message_len = strlen( p );
8621
8622                 /* [AS] Avoid buffer overflow */
8623                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8624                     strcat(thinkOutput, " ");
8625                     strcat(thinkOutput, p);
8626                 }
8627
8628                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8629                     strcat(programStats.movelist, " ");
8630                     strcat(programStats.movelist, p);
8631                 }
8632
8633                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8634                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8635                     DisplayMove(currentMove - 1);
8636                 }
8637                 return;
8638             }
8639         }
8640         else {
8641             buf1[0] = NULLCHAR;
8642
8643             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8644                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8645             {
8646                 ChessProgramStats cpstats;
8647
8648                 if (plyext != ' ' && plyext != '\t') {
8649                     time *= 100;
8650                 }
8651
8652                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8653                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8654                     curscore = -curscore;
8655                 }
8656
8657                 cpstats.depth = plylev;
8658                 cpstats.nodes = nodes;
8659                 cpstats.time = time;
8660                 cpstats.score = curscore;
8661                 cpstats.got_only_move = 0;
8662                 cpstats.movelist[0] = '\0';
8663
8664                 if (buf1[0] != NULLCHAR) {
8665                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8666                 }
8667
8668                 cpstats.ok_to_send = 0;
8669                 cpstats.line_is_book = 0;
8670                 cpstats.nr_moves = 0;
8671                 cpstats.moves_left = 0;
8672
8673                 SendProgramStatsToFrontend( cps, &cpstats );
8674             }
8675         }
8676     }
8677 }
8678
8679
8680 /* Parse a game score from the character string "game", and
8681    record it as the history of the current game.  The game
8682    score is NOT assumed to start from the standard position.
8683    The display is not updated in any way.
8684    */
8685 void
8686 ParseGameHistory(game)
8687      char *game;
8688 {
8689     ChessMove moveType;
8690     int fromX, fromY, toX, toY, boardIndex;
8691     char promoChar;
8692     char *p, *q;
8693     char buf[MSG_SIZ];
8694
8695     if (appData.debugMode)
8696       fprintf(debugFP, "Parsing game history: %s\n", game);
8697
8698     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8699     gameInfo.site = StrSave(appData.icsHost);
8700     gameInfo.date = PGNDate();
8701     gameInfo.round = StrSave("-");
8702
8703     /* Parse out names of players */
8704     while (*game == ' ') game++;
8705     p = buf;
8706     while (*game != ' ') *p++ = *game++;
8707     *p = NULLCHAR;
8708     gameInfo.white = StrSave(buf);
8709     while (*game == ' ') game++;
8710     p = buf;
8711     while (*game != ' ' && *game != '\n') *p++ = *game++;
8712     *p = NULLCHAR;
8713     gameInfo.black = StrSave(buf);
8714
8715     /* Parse moves */
8716     boardIndex = blackPlaysFirst ? 1 : 0;
8717     yynewstr(game);
8718     for (;;) {
8719         yyboardindex = boardIndex;
8720         moveType = (ChessMove) Myylex();
8721         switch (moveType) {
8722           case IllegalMove:             /* maybe suicide chess, etc. */
8723   if (appData.debugMode) {
8724     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8725     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8726     setbuf(debugFP, NULL);
8727   }
8728           case WhitePromotion:
8729           case BlackPromotion:
8730           case WhiteNonPromotion:
8731           case BlackNonPromotion:
8732           case NormalMove:
8733           case WhiteCapturesEnPassant:
8734           case BlackCapturesEnPassant:
8735           case WhiteKingSideCastle:
8736           case WhiteQueenSideCastle:
8737           case BlackKingSideCastle:
8738           case BlackQueenSideCastle:
8739           case WhiteKingSideCastleWild:
8740           case WhiteQueenSideCastleWild:
8741           case BlackKingSideCastleWild:
8742           case BlackQueenSideCastleWild:
8743           /* PUSH Fabien */
8744           case WhiteHSideCastleFR:
8745           case WhiteASideCastleFR:
8746           case BlackHSideCastleFR:
8747           case BlackASideCastleFR:
8748           /* POP Fabien */
8749             fromX = currentMoveString[0] - AAA;
8750             fromY = currentMoveString[1] - ONE;
8751             toX = currentMoveString[2] - AAA;
8752             toY = currentMoveString[3] - ONE;
8753             promoChar = currentMoveString[4];
8754             break;
8755           case WhiteDrop:
8756           case BlackDrop:
8757             fromX = moveType == WhiteDrop ?
8758               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8759             (int) CharToPiece(ToLower(currentMoveString[0]));
8760             fromY = DROP_RANK;
8761             toX = currentMoveString[2] - AAA;
8762             toY = currentMoveString[3] - ONE;
8763             promoChar = NULLCHAR;
8764             break;
8765           case AmbiguousMove:
8766             /* bug? */
8767             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8768   if (appData.debugMode) {
8769     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8770     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8771     setbuf(debugFP, NULL);
8772   }
8773             DisplayError(buf, 0);
8774             return;
8775           case ImpossibleMove:
8776             /* bug? */
8777             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8778   if (appData.debugMode) {
8779     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8780     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8781     setbuf(debugFP, NULL);
8782   }
8783             DisplayError(buf, 0);
8784             return;
8785           case EndOfFile:
8786             if (boardIndex < backwardMostMove) {
8787                 /* Oops, gap.  How did that happen? */
8788                 DisplayError(_("Gap in move list"), 0);
8789                 return;
8790             }
8791             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8792             if (boardIndex > forwardMostMove) {
8793                 forwardMostMove = boardIndex;
8794             }
8795             return;
8796           case ElapsedTime:
8797             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8798                 strcat(parseList[boardIndex-1], " ");
8799                 strcat(parseList[boardIndex-1], yy_text);
8800             }
8801             continue;
8802           case Comment:
8803           case PGNTag:
8804           case NAG:
8805           default:
8806             /* ignore */
8807             continue;
8808           case WhiteWins:
8809           case BlackWins:
8810           case GameIsDrawn:
8811           case GameUnfinished:
8812             if (gameMode == IcsExamining) {
8813                 if (boardIndex < backwardMostMove) {
8814                     /* Oops, gap.  How did that happen? */
8815                     return;
8816                 }
8817                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8818                 return;
8819             }
8820             gameInfo.result = moveType;
8821             p = strchr(yy_text, '{');
8822             if (p == NULL) p = strchr(yy_text, '(');
8823             if (p == NULL) {
8824                 p = yy_text;
8825                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8826             } else {
8827                 q = strchr(p, *p == '{' ? '}' : ')');
8828                 if (q != NULL) *q = NULLCHAR;
8829                 p++;
8830             }
8831             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8832             gameInfo.resultDetails = StrSave(p);
8833             continue;
8834         }
8835         if (boardIndex >= forwardMostMove &&
8836             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8837             backwardMostMove = blackPlaysFirst ? 1 : 0;
8838             return;
8839         }
8840         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8841                                  fromY, fromX, toY, toX, promoChar,
8842                                  parseList[boardIndex]);
8843         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8844         /* currentMoveString is set as a side-effect of yylex */
8845         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8846         strcat(moveList[boardIndex], "\n");
8847         boardIndex++;
8848         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8849         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8850           case MT_NONE:
8851           case MT_STALEMATE:
8852           default:
8853             break;
8854           case MT_CHECK:
8855             if(gameInfo.variant != VariantShogi)
8856                 strcat(parseList[boardIndex - 1], "+");
8857             break;
8858           case MT_CHECKMATE:
8859           case MT_STAINMATE:
8860             strcat(parseList[boardIndex - 1], "#");
8861             break;
8862         }
8863     }
8864 }
8865
8866
8867 /* Apply a move to the given board  */
8868 void
8869 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8870      int fromX, fromY, toX, toY;
8871      int promoChar;
8872      Board board;
8873 {
8874   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8875   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8876
8877     /* [HGM] compute & store e.p. status and castling rights for new position */
8878     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8879
8880       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8881       oldEP = (signed char)board[EP_STATUS];
8882       board[EP_STATUS] = EP_NONE;
8883
8884       if( board[toY][toX] != EmptySquare )
8885            board[EP_STATUS] = EP_CAPTURE;
8886
8887   if (fromY == DROP_RANK) {
8888         /* must be first */
8889         piece = board[toY][toX] = (ChessSquare) fromX;
8890   } else {
8891       int i;
8892
8893       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8894            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8895                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8896       } else
8897       if( board[fromY][fromX] == WhitePawn ) {
8898            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8899                board[EP_STATUS] = EP_PAWN_MOVE;
8900            if( toY-fromY==2) {
8901                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8902                         gameInfo.variant != VariantBerolina || toX < fromX)
8903                       board[EP_STATUS] = toX | berolina;
8904                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8905                         gameInfo.variant != VariantBerolina || toX > fromX)
8906                       board[EP_STATUS] = toX;
8907            }
8908       } else
8909       if( board[fromY][fromX] == BlackPawn ) {
8910            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8911                board[EP_STATUS] = EP_PAWN_MOVE;
8912            if( toY-fromY== -2) {
8913                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8914                         gameInfo.variant != VariantBerolina || toX < fromX)
8915                       board[EP_STATUS] = toX | berolina;
8916                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8917                         gameInfo.variant != VariantBerolina || toX > fromX)
8918                       board[EP_STATUS] = toX;
8919            }
8920        }
8921
8922        for(i=0; i<nrCastlingRights; i++) {
8923            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8924               board[CASTLING][i] == toX   && castlingRank[i] == toY
8925              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8926        }
8927
8928      if (fromX == toX && fromY == toY) return;
8929
8930      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8931      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8932      if(gameInfo.variant == VariantKnightmate)
8933          king += (int) WhiteUnicorn - (int) WhiteKing;
8934
8935     /* Code added by Tord: */
8936     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8937     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8938         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8939       board[fromY][fromX] = EmptySquare;
8940       board[toY][toX] = EmptySquare;
8941       if((toX > fromX) != (piece == WhiteRook)) {
8942         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8943       } else {
8944         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8945       }
8946     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8947                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8948       board[fromY][fromX] = EmptySquare;
8949       board[toY][toX] = EmptySquare;
8950       if((toX > fromX) != (piece == BlackRook)) {
8951         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8952       } else {
8953         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8954       }
8955     /* End of code added by Tord */
8956
8957     } else if (board[fromY][fromX] == king
8958         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8959         && toY == fromY && toX > fromX+1) {
8960         board[fromY][fromX] = EmptySquare;
8961         board[toY][toX] = king;
8962         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8963         board[fromY][BOARD_RGHT-1] = EmptySquare;
8964     } else if (board[fromY][fromX] == king
8965         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8966                && toY == fromY && toX < fromX-1) {
8967         board[fromY][fromX] = EmptySquare;
8968         board[toY][toX] = king;
8969         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8970         board[fromY][BOARD_LEFT] = EmptySquare;
8971     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8972                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8973                && toY >= BOARD_HEIGHT-promoRank
8974                ) {
8975         /* white pawn promotion */
8976         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8977         if (board[toY][toX] == EmptySquare) {
8978             board[toY][toX] = WhiteQueen;
8979         }
8980         if(gameInfo.variant==VariantBughouse ||
8981            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8982             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8983         board[fromY][fromX] = EmptySquare;
8984     } else if ((fromY == BOARD_HEIGHT-4)
8985                && (toX != fromX)
8986                && gameInfo.variant != VariantXiangqi
8987                && gameInfo.variant != VariantBerolina
8988                && (board[fromY][fromX] == WhitePawn)
8989                && (board[toY][toX] == EmptySquare)) {
8990         board[fromY][fromX] = EmptySquare;
8991         board[toY][toX] = WhitePawn;
8992         captured = board[toY - 1][toX];
8993         board[toY - 1][toX] = EmptySquare;
8994     } else if ((fromY == BOARD_HEIGHT-4)
8995                && (toX == fromX)
8996                && gameInfo.variant == VariantBerolina
8997                && (board[fromY][fromX] == WhitePawn)
8998                && (board[toY][toX] == EmptySquare)) {
8999         board[fromY][fromX] = EmptySquare;
9000         board[toY][toX] = WhitePawn;
9001         if(oldEP & EP_BEROLIN_A) {
9002                 captured = board[fromY][fromX-1];
9003                 board[fromY][fromX-1] = EmptySquare;
9004         }else{  captured = board[fromY][fromX+1];
9005                 board[fromY][fromX+1] = EmptySquare;
9006         }
9007     } else if (board[fromY][fromX] == king
9008         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9009                && toY == fromY && toX > fromX+1) {
9010         board[fromY][fromX] = EmptySquare;
9011         board[toY][toX] = king;
9012         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9013         board[fromY][BOARD_RGHT-1] = EmptySquare;
9014     } else if (board[fromY][fromX] == king
9015         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9016                && toY == fromY && toX < fromX-1) {
9017         board[fromY][fromX] = EmptySquare;
9018         board[toY][toX] = king;
9019         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9020         board[fromY][BOARD_LEFT] = EmptySquare;
9021     } else if (fromY == 7 && fromX == 3
9022                && board[fromY][fromX] == BlackKing
9023                && toY == 7 && toX == 5) {
9024         board[fromY][fromX] = EmptySquare;
9025         board[toY][toX] = BlackKing;
9026         board[fromY][7] = EmptySquare;
9027         board[toY][4] = BlackRook;
9028     } else if (fromY == 7 && fromX == 3
9029                && board[fromY][fromX] == BlackKing
9030                && toY == 7 && toX == 1) {
9031         board[fromY][fromX] = EmptySquare;
9032         board[toY][toX] = BlackKing;
9033         board[fromY][0] = EmptySquare;
9034         board[toY][2] = BlackRook;
9035     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9036                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9037                && toY < promoRank
9038                ) {
9039         /* black pawn promotion */
9040         board[toY][toX] = CharToPiece(ToLower(promoChar));
9041         if (board[toY][toX] == EmptySquare) {
9042             board[toY][toX] = BlackQueen;
9043         }
9044         if(gameInfo.variant==VariantBughouse ||
9045            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9046             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9047         board[fromY][fromX] = EmptySquare;
9048     } else if ((fromY == 3)
9049                && (toX != fromX)
9050                && gameInfo.variant != VariantXiangqi
9051                && gameInfo.variant != VariantBerolina
9052                && (board[fromY][fromX] == BlackPawn)
9053                && (board[toY][toX] == EmptySquare)) {
9054         board[fromY][fromX] = EmptySquare;
9055         board[toY][toX] = BlackPawn;
9056         captured = board[toY + 1][toX];
9057         board[toY + 1][toX] = EmptySquare;
9058     } else if ((fromY == 3)
9059                && (toX == fromX)
9060                && gameInfo.variant == VariantBerolina
9061                && (board[fromY][fromX] == BlackPawn)
9062                && (board[toY][toX] == EmptySquare)) {
9063         board[fromY][fromX] = EmptySquare;
9064         board[toY][toX] = BlackPawn;
9065         if(oldEP & EP_BEROLIN_A) {
9066                 captured = board[fromY][fromX-1];
9067                 board[fromY][fromX-1] = EmptySquare;
9068         }else{  captured = board[fromY][fromX+1];
9069                 board[fromY][fromX+1] = EmptySquare;
9070         }
9071     } else {
9072         board[toY][toX] = board[fromY][fromX];
9073         board[fromY][fromX] = EmptySquare;
9074     }
9075   }
9076
9077     if (gameInfo.holdingsWidth != 0) {
9078
9079       /* !!A lot more code needs to be written to support holdings  */
9080       /* [HGM] OK, so I have written it. Holdings are stored in the */
9081       /* penultimate board files, so they are automaticlly stored   */
9082       /* in the game history.                                       */
9083       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9084                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9085         /* Delete from holdings, by decreasing count */
9086         /* and erasing image if necessary            */
9087         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9088         if(p < (int) BlackPawn) { /* white drop */
9089              p -= (int)WhitePawn;
9090                  p = PieceToNumber((ChessSquare)p);
9091              if(p >= gameInfo.holdingsSize) p = 0;
9092              if(--board[p][BOARD_WIDTH-2] <= 0)
9093                   board[p][BOARD_WIDTH-1] = EmptySquare;
9094              if((int)board[p][BOARD_WIDTH-2] < 0)
9095                         board[p][BOARD_WIDTH-2] = 0;
9096         } else {                  /* black drop */
9097              p -= (int)BlackPawn;
9098                  p = PieceToNumber((ChessSquare)p);
9099              if(p >= gameInfo.holdingsSize) p = 0;
9100              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9101                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9102              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9103                         board[BOARD_HEIGHT-1-p][1] = 0;
9104         }
9105       }
9106       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9107           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9108         /* [HGM] holdings: Add to holdings, if holdings exist */
9109         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9110                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9111                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9112         }
9113         p = (int) captured;
9114         if (p >= (int) BlackPawn) {
9115           p -= (int)BlackPawn;
9116           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9117                   /* in Shogi restore piece to its original  first */
9118                   captured = (ChessSquare) (DEMOTED captured);
9119                   p = DEMOTED p;
9120           }
9121           p = PieceToNumber((ChessSquare)p);
9122           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9123           board[p][BOARD_WIDTH-2]++;
9124           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9125         } else {
9126           p -= (int)WhitePawn;
9127           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9128                   captured = (ChessSquare) (DEMOTED captured);
9129                   p = DEMOTED p;
9130           }
9131           p = PieceToNumber((ChessSquare)p);
9132           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9133           board[BOARD_HEIGHT-1-p][1]++;
9134           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9135         }
9136       }
9137     } else if (gameInfo.variant == VariantAtomic) {
9138       if (captured != EmptySquare) {
9139         int y, x;
9140         for (y = toY-1; y <= toY+1; y++) {
9141           for (x = toX-1; x <= toX+1; x++) {
9142             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9143                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9144               board[y][x] = EmptySquare;
9145             }
9146           }
9147         }
9148         board[toY][toX] = EmptySquare;
9149       }
9150     }
9151     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9152         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9153     } else
9154     if(promoChar == '+') {
9155         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9156         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9157     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9158         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9159     }
9160     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9161                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9162         // [HGM] superchess: take promotion piece out of holdings
9163         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9164         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9165             if(!--board[k][BOARD_WIDTH-2])
9166                 board[k][BOARD_WIDTH-1] = EmptySquare;
9167         } else {
9168             if(!--board[BOARD_HEIGHT-1-k][1])
9169                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9170         }
9171     }
9172
9173 }
9174
9175 /* Updates forwardMostMove */
9176 void
9177 MakeMove(fromX, fromY, toX, toY, promoChar)
9178      int fromX, fromY, toX, toY;
9179      int promoChar;
9180 {
9181 //    forwardMostMove++; // [HGM] bare: moved downstream
9182
9183     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9184         int timeLeft; static int lastLoadFlag=0; int king, piece;
9185         piece = boards[forwardMostMove][fromY][fromX];
9186         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9187         if(gameInfo.variant == VariantKnightmate)
9188             king += (int) WhiteUnicorn - (int) WhiteKing;
9189         if(forwardMostMove == 0) {
9190             if(blackPlaysFirst)
9191                 fprintf(serverMoves, "%s;", second.tidy);
9192             fprintf(serverMoves, "%s;", first.tidy);
9193             if(!blackPlaysFirst)
9194                 fprintf(serverMoves, "%s;", second.tidy);
9195         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9196         lastLoadFlag = loadFlag;
9197         // print base move
9198         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9199         // print castling suffix
9200         if( toY == fromY && piece == king ) {
9201             if(toX-fromX > 1)
9202                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9203             if(fromX-toX >1)
9204                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9205         }
9206         // e.p. suffix
9207         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9208              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9209              boards[forwardMostMove][toY][toX] == EmptySquare
9210              && fromX != toX && fromY != toY)
9211                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9212         // promotion suffix
9213         if(promoChar != NULLCHAR)
9214                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9215         if(!loadFlag) {
9216             fprintf(serverMoves, "/%d/%d",
9217                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9218             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9219             else                      timeLeft = blackTimeRemaining/1000;
9220             fprintf(serverMoves, "/%d", timeLeft);
9221         }
9222         fflush(serverMoves);
9223     }
9224
9225     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9226       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9227                         0, 1);
9228       return;
9229     }
9230     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9231     if (commentList[forwardMostMove+1] != NULL) {
9232         free(commentList[forwardMostMove+1]);
9233         commentList[forwardMostMove+1] = NULL;
9234     }
9235     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9236     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9237     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9238     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9239     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9240     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9241     gameInfo.result = GameUnfinished;
9242     if (gameInfo.resultDetails != NULL) {
9243         free(gameInfo.resultDetails);
9244         gameInfo.resultDetails = NULL;
9245     }
9246     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9247                               moveList[forwardMostMove - 1]);
9248     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9249                              PosFlags(forwardMostMove - 1),
9250                              fromY, fromX, toY, toX, promoChar,
9251                              parseList[forwardMostMove - 1]);
9252     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9253       case MT_NONE:
9254       case MT_STALEMATE:
9255       default:
9256         break;
9257       case MT_CHECK:
9258         if(gameInfo.variant != VariantShogi)
9259             strcat(parseList[forwardMostMove - 1], "+");
9260         break;
9261       case MT_CHECKMATE:
9262       case MT_STAINMATE:
9263         strcat(parseList[forwardMostMove - 1], "#");
9264         break;
9265     }
9266     if (appData.debugMode) {
9267         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9268     }
9269
9270 }
9271
9272 /* Updates currentMove if not pausing */
9273 void
9274 ShowMove(fromX, fromY, toX, toY)
9275 {
9276     int instant = (gameMode == PlayFromGameFile) ?
9277         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9278     if(appData.noGUI) return;
9279     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9280         if (!instant) {
9281             if (forwardMostMove == currentMove + 1) {
9282                 AnimateMove(boards[forwardMostMove - 1],
9283                             fromX, fromY, toX, toY);
9284             }
9285             if (appData.highlightLastMove) {
9286                 SetHighlights(fromX, fromY, toX, toY);
9287             }
9288         }
9289         currentMove = forwardMostMove;
9290     }
9291
9292     if (instant) return;
9293
9294     DisplayMove(currentMove - 1);
9295     DrawPosition(FALSE, boards[currentMove]);
9296     DisplayBothClocks();
9297     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9298     DisplayBook(currentMove);
9299 }
9300
9301 void SendEgtPath(ChessProgramState *cps)
9302 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9303         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9304
9305         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9306
9307         while(*p) {
9308             char c, *q = name+1, *r, *s;
9309
9310             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9311             while(*p && *p != ',') *q++ = *p++;
9312             *q++ = ':'; *q = 0;
9313             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9314                 strcmp(name, ",nalimov:") == 0 ) {
9315                 // take nalimov path from the menu-changeable option first, if it is defined
9316               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9317                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9318             } else
9319             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9320                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9321                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9322                 s = r = StrStr(s, ":") + 1; // beginning of path info
9323                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9324                 c = *r; *r = 0;             // temporarily null-terminate path info
9325                     *--q = 0;               // strip of trailig ':' from name
9326                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9327                 *r = c;
9328                 SendToProgram(buf,cps);     // send egtbpath command for this format
9329             }
9330             if(*p == ',') p++; // read away comma to position for next format name
9331         }
9332 }
9333
9334 void
9335 InitChessProgram(cps, setup)
9336      ChessProgramState *cps;
9337      int setup; /* [HGM] needed to setup FRC opening position */
9338 {
9339     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9340     if (appData.noChessProgram) return;
9341     hintRequested = FALSE;
9342     bookRequested = FALSE;
9343
9344     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9345     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9346     if(cps->memSize) { /* [HGM] memory */
9347       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9348         SendToProgram(buf, cps);
9349     }
9350     SendEgtPath(cps); /* [HGM] EGT */
9351     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9352       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9353         SendToProgram(buf, cps);
9354     }
9355
9356     SendToProgram(cps->initString, cps);
9357     if (gameInfo.variant != VariantNormal &&
9358         gameInfo.variant != VariantLoadable
9359         /* [HGM] also send variant if board size non-standard */
9360         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9361                                             ) {
9362       char *v = VariantName(gameInfo.variant);
9363       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9364         /* [HGM] in protocol 1 we have to assume all variants valid */
9365         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9366         DisplayFatalError(buf, 0, 1);
9367         return;
9368       }
9369
9370       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9371       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9372       if( gameInfo.variant == VariantXiangqi )
9373            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9374       if( gameInfo.variant == VariantShogi )
9375            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9376       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9377            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9378       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9379           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9380            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9381       if( gameInfo.variant == VariantCourier )
9382            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9383       if( gameInfo.variant == VariantSuper )
9384            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9385       if( gameInfo.variant == VariantGreat )
9386            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9387       if( gameInfo.variant == VariantSChess )
9388            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9389
9390       if(overruled) {
9391         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9392                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9393            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9394            if(StrStr(cps->variants, b) == NULL) {
9395                // specific sized variant not known, check if general sizing allowed
9396                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9397                    if(StrStr(cps->variants, "boardsize") == NULL) {
9398                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9399                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9400                        DisplayFatalError(buf, 0, 1);
9401                        return;
9402                    }
9403                    /* [HGM] here we really should compare with the maximum supported board size */
9404                }
9405            }
9406       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9407       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9408       SendToProgram(buf, cps);
9409     }
9410     currentlyInitializedVariant = gameInfo.variant;
9411
9412     /* [HGM] send opening position in FRC to first engine */
9413     if(setup) {
9414           SendToProgram("force\n", cps);
9415           SendBoard(cps, 0);
9416           /* engine is now in force mode! Set flag to wake it up after first move. */
9417           setboardSpoiledMachineBlack = 1;
9418     }
9419
9420     if (cps->sendICS) {
9421       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9422       SendToProgram(buf, cps);
9423     }
9424     cps->maybeThinking = FALSE;
9425     cps->offeredDraw = 0;
9426     if (!appData.icsActive) {
9427         SendTimeControl(cps, movesPerSession, timeControl,
9428                         timeIncrement, appData.searchDepth,
9429                         searchTime);
9430     }
9431     if (appData.showThinking
9432         // [HGM] thinking: four options require thinking output to be sent
9433         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9434                                 ) {
9435         SendToProgram("post\n", cps);
9436     }
9437     SendToProgram("hard\n", cps);
9438     if (!appData.ponderNextMove) {
9439         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9440            it without being sure what state we are in first.  "hard"
9441            is not a toggle, so that one is OK.
9442          */
9443         SendToProgram("easy\n", cps);
9444     }
9445     if (cps->usePing) {
9446       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9447       SendToProgram(buf, cps);
9448     }
9449     cps->initDone = TRUE;
9450 }
9451
9452
9453 void
9454 StartChessProgram(cps)
9455      ChessProgramState *cps;
9456 {
9457     char buf[MSG_SIZ];
9458     int err;
9459
9460     if (appData.noChessProgram) return;
9461     cps->initDone = FALSE;
9462
9463     if (strcmp(cps->host, "localhost") == 0) {
9464         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9465     } else if (*appData.remoteShell == NULLCHAR) {
9466         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9467     } else {
9468         if (*appData.remoteUser == NULLCHAR) {
9469           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9470                     cps->program);
9471         } else {
9472           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9473                     cps->host, appData.remoteUser, cps->program);
9474         }
9475         err = StartChildProcess(buf, "", &cps->pr);
9476     }
9477
9478     if (err != 0) {
9479       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9480         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9481         if(cps != &first) return;
9482         appData.noChessProgram = TRUE;
9483         ThawUI();
9484         SetNCPMode();
9485 //      DisplayFatalError(buf, err, 1);
9486 //      cps->pr = NoProc;
9487 //      cps->isr = NULL;
9488         return;
9489     }
9490
9491     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9492     if (cps->protocolVersion > 1) {
9493       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9494       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9495       cps->comboCnt = 0;  //                and values of combo boxes
9496       SendToProgram(buf, cps);
9497     } else {
9498       SendToProgram("xboard\n", cps);
9499     }
9500 }
9501
9502 void
9503 TwoMachinesEventIfReady P((void))
9504 {
9505   static int curMess = 0;
9506   if (first.lastPing != first.lastPong) {
9507     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9508     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9509     return;
9510   }
9511   if (second.lastPing != second.lastPong) {
9512     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9513     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9514     return;
9515   }
9516   DisplayMessage("", ""); curMess = 0;
9517   ThawUI();
9518   TwoMachinesEvent();
9519 }
9520
9521 int
9522 CreateTourney(char *name)
9523 {
9524         FILE *f;
9525         if(name[0] == NULLCHAR) return 0;
9526         f = fopen(appData.tourneyFile, "r");
9527         if(f) { // file exists
9528             ParseArgsFromFile(f); // parse it
9529         } else {
9530             f = fopen(appData.tourneyFile, "w");
9531             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9532                 // create a file with tournament description
9533                 fprintf(f, "-participants {%s}\n", appData.participants);
9534                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9535                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9536                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9537                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9538                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9539                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9540                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9541                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9542                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9543                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9544                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9545                 if(searchTime > 0)
9546                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9547                 else {
9548                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9549                         fprintf(f, "-tc %s\n", appData.timeControl);
9550                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9551                 }
9552                 fprintf(f, "-results \"\"\n");
9553             }
9554         }
9555         fclose(f);
9556         appData.noChessProgram = FALSE;
9557         appData.clockMode = TRUE;
9558         SetGNUMode();
9559         return 1;
9560 }
9561
9562 #define MAXENGINES 1000
9563 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9564
9565 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9566 {
9567     char buf[MSG_SIZ], *p, *q;
9568     int i=1;
9569     while(*names) {
9570         p = names; q = buf;
9571         while(*p && *p != '\n') *q++ = *p++;
9572         *q = 0;
9573         if(engineList[i]) free(engineList[i]);
9574         engineList[i] = strdup(buf);
9575         if(*p == '\n') p++;
9576         TidyProgramName(engineList[i], "localhost", buf);
9577         if(engineMnemonic[i]) free(engineMnemonic[i]);
9578         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9579             strcat(buf, " (");
9580             sscanf(q + 8, "%s", buf + strlen(buf));
9581             strcat(buf, ")");
9582         }
9583         engineMnemonic[i] = strdup(buf);
9584         names = p; i++;
9585       if(i > MAXENGINES - 2) break;
9586     }
9587     engineList[i] = NULL;
9588 }
9589
9590 // following implemented as macro to avoid type limitations
9591 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9592
9593 void SwapEngines(int n)
9594 {   // swap settings for first engine and other engine (so far only some selected options)
9595     int h;
9596     char *p;
9597     if(n == 0) return;
9598     SWAP(directory, p)
9599     SWAP(chessProgram, p)
9600     SWAP(isUCI, h)
9601     SWAP(hasOwnBookUCI, h)
9602     SWAP(protocolVersion, h)
9603     SWAP(reuse, h)
9604     SWAP(scoreIsAbsolute, h)
9605     SWAP(timeOdds, h)
9606     SWAP(logo, p)
9607     SWAP(pgnName, p)
9608 }
9609
9610 void
9611 SetPlayer(int player)
9612 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9613     int i;
9614     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9615     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9616     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9617     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9618     if(mnemonic[i]) {
9619         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9620         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9621         ParseArgsFromString(buf);
9622     }
9623     free(engineName);
9624 }
9625
9626 int
9627 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9628 {   // determine players from game number
9629     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9630
9631     if(appData.tourneyType == 0) {
9632         roundsPerCycle = (nPlayers - 1) | 1;
9633         pairingsPerRound = nPlayers / 2;
9634     } else if(appData.tourneyType > 0) {
9635         roundsPerCycle = nPlayers - appData.tourneyType;
9636         pairingsPerRound = appData.tourneyType;
9637     }
9638     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9639     gamesPerCycle = gamesPerRound * roundsPerCycle;
9640     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9641     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9642     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9643     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9644     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9645     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9646
9647     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9648     if(appData.roundSync) *syncInterval = gamesPerRound;
9649
9650     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9651
9652     if(appData.tourneyType == 0) {
9653         if(curPairing == (nPlayers-1)/2 ) {
9654             *whitePlayer = curRound;
9655             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9656         } else {
9657             *whitePlayer = curRound - pairingsPerRound + curPairing;
9658             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9659             *blackPlayer = curRound + pairingsPerRound - curPairing;
9660             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9661         }
9662     } else if(appData.tourneyType > 0) {
9663         *whitePlayer = curPairing;
9664         *blackPlayer = curRound + appData.tourneyType;
9665     }
9666
9667     // take care of white/black alternation per round. 
9668     // For cycles and games this is already taken care of by default, derived from matchGame!
9669     return curRound & 1;
9670 }
9671
9672 int
9673 NextTourneyGame(int nr, int *swapColors)
9674 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9675     char *p, *q;
9676     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9677     FILE *tf;
9678     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9679     tf = fopen(appData.tourneyFile, "r");
9680     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9681     ParseArgsFromFile(tf); fclose(tf);
9682     InitTimeControls(); // TC might be altered from tourney file
9683
9684     p = appData.participants;
9685     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9686     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9687
9688     if(syncInterval) {
9689         p = q = appData.results;
9690         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9691         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9692             DisplayMessage(_("Waiting for other game(s)"),"");
9693             waitingForGame = TRUE;
9694             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9695             return 0;
9696         }
9697         waitingForGame = FALSE;
9698     }
9699
9700     if(first.pr != NoProc) return 1; // engines already loaded
9701
9702     // redefine engines, engine dir, etc.
9703     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9704     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9705     SwapEngines(1);
9706     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9707     SwapEngines(1);         // and make that valid for second engine by swapping
9708     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9709     InitEngine(&second, 1);
9710     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9711     return 1;
9712 }
9713
9714 void
9715 NextMatchGame()
9716 {   // performs game initialization that does not invoke engines, and then tries to start the game
9717     int firstWhite, swapColors = 0;
9718     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9719     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9720     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9721     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9722     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9723     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9724     Reset(FALSE, first.pr != NoProc);
9725     appData.noChessProgram = FALSE;
9726     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9727     TwoMachinesEvent();
9728 }
9729
9730 void UserAdjudicationEvent( int result )
9731 {
9732     ChessMove gameResult = GameIsDrawn;
9733
9734     if( result > 0 ) {
9735         gameResult = WhiteWins;
9736     }
9737     else if( result < 0 ) {
9738         gameResult = BlackWins;
9739     }
9740
9741     if( gameMode == TwoMachinesPlay ) {
9742         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9743     }
9744 }
9745
9746
9747 // [HGM] save: calculate checksum of game to make games easily identifiable
9748 int StringCheckSum(char *s)
9749 {
9750         int i = 0;
9751         if(s==NULL) return 0;
9752         while(*s) i = i*259 + *s++;
9753         return i;
9754 }
9755
9756 int GameCheckSum()
9757 {
9758         int i, sum=0;
9759         for(i=backwardMostMove; i<forwardMostMove; i++) {
9760                 sum += pvInfoList[i].depth;
9761                 sum += StringCheckSum(parseList[i]);
9762                 sum += StringCheckSum(commentList[i]);
9763                 sum *= 261;
9764         }
9765         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9766         return sum + StringCheckSum(commentList[i]);
9767 } // end of save patch
9768
9769 void
9770 GameEnds(result, resultDetails, whosays)
9771      ChessMove result;
9772      char *resultDetails;
9773      int whosays;
9774 {
9775     GameMode nextGameMode;
9776     int isIcsGame;
9777     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9778
9779     if(endingGame) return; /* [HGM] crash: forbid recursion */
9780     endingGame = 1;
9781     if(twoBoards) { // [HGM] dual: switch back to one board
9782         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9783         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9784     }
9785     if (appData.debugMode) {
9786       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9787               result, resultDetails ? resultDetails : "(null)", whosays);
9788     }
9789
9790     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9791
9792     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9793         /* If we are playing on ICS, the server decides when the
9794            game is over, but the engine can offer to draw, claim
9795            a draw, or resign.
9796          */
9797 #if ZIPPY
9798         if (appData.zippyPlay && first.initDone) {
9799             if (result == GameIsDrawn) {
9800                 /* In case draw still needs to be claimed */
9801                 SendToICS(ics_prefix);
9802                 SendToICS("draw\n");
9803             } else if (StrCaseStr(resultDetails, "resign")) {
9804                 SendToICS(ics_prefix);
9805                 SendToICS("resign\n");
9806             }
9807         }
9808 #endif
9809         endingGame = 0; /* [HGM] crash */
9810         return;
9811     }
9812
9813     /* If we're loading the game from a file, stop */
9814     if (whosays == GE_FILE) {
9815       (void) StopLoadGameTimer();
9816       gameFileFP = NULL;
9817     }
9818
9819     /* Cancel draw offers */
9820     first.offeredDraw = second.offeredDraw = 0;
9821
9822     /* If this is an ICS game, only ICS can really say it's done;
9823        if not, anyone can. */
9824     isIcsGame = (gameMode == IcsPlayingWhite ||
9825                  gameMode == IcsPlayingBlack ||
9826                  gameMode == IcsObserving    ||
9827                  gameMode == IcsExamining);
9828
9829     if (!isIcsGame || whosays == GE_ICS) {
9830         /* OK -- not an ICS game, or ICS said it was done */
9831         StopClocks();
9832         if (!isIcsGame && !appData.noChessProgram)
9833           SetUserThinkingEnables();
9834
9835         /* [HGM] if a machine claims the game end we verify this claim */
9836         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9837             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9838                 char claimer;
9839                 ChessMove trueResult = (ChessMove) -1;
9840
9841                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9842                                             first.twoMachinesColor[0] :
9843                                             second.twoMachinesColor[0] ;
9844
9845                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9846                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9847                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9848                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9849                 } else
9850                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9851                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9852                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9853                 } else
9854                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9855                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9856                 }
9857
9858                 // now verify win claims, but not in drop games, as we don't understand those yet
9859                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9860                                                  || gameInfo.variant == VariantGreat) &&
9861                     (result == WhiteWins && claimer == 'w' ||
9862                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9863                       if (appData.debugMode) {
9864                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9865                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9866                       }
9867                       if(result != trueResult) {
9868                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9869                               result = claimer == 'w' ? BlackWins : WhiteWins;
9870                               resultDetails = buf;
9871                       }
9872                 } else
9873                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9874                     && (forwardMostMove <= backwardMostMove ||
9875                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9876                         (claimer=='b')==(forwardMostMove&1))
9877                                                                                   ) {
9878                       /* [HGM] verify: draws that were not flagged are false claims */
9879                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9880                       result = claimer == 'w' ? BlackWins : WhiteWins;
9881                       resultDetails = buf;
9882                 }
9883                 /* (Claiming a loss is accepted no questions asked!) */
9884             }
9885             /* [HGM] bare: don't allow bare King to win */
9886             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9887                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9888                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9889                && result != GameIsDrawn)
9890             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9891                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9892                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9893                         if(p >= 0 && p <= (int)WhiteKing) k++;
9894                 }
9895                 if (appData.debugMode) {
9896                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9897                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9898                 }
9899                 if(k <= 1) {
9900                         result = GameIsDrawn;
9901                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9902                         resultDetails = buf;
9903                 }
9904             }
9905         }
9906
9907
9908         if(serverMoves != NULL && !loadFlag) { char c = '=';
9909             if(result==WhiteWins) c = '+';
9910             if(result==BlackWins) c = '-';
9911             if(resultDetails != NULL)
9912                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9913         }
9914         if (resultDetails != NULL) {
9915             gameInfo.result = result;
9916             gameInfo.resultDetails = StrSave(resultDetails);
9917
9918             /* display last move only if game was not loaded from file */
9919             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9920                 DisplayMove(currentMove - 1);
9921
9922             if (forwardMostMove != 0) {
9923                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9924                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9925                                                                 ) {
9926                     if (*appData.saveGameFile != NULLCHAR) {
9927                         SaveGameToFile(appData.saveGameFile, TRUE);
9928                     } else if (appData.autoSaveGames) {
9929                         AutoSaveGame();
9930                     }
9931                     if (*appData.savePositionFile != NULLCHAR) {
9932                         SavePositionToFile(appData.savePositionFile);
9933                     }
9934                 }
9935             }
9936
9937             /* Tell program how game ended in case it is learning */
9938             /* [HGM] Moved this to after saving the PGN, just in case */
9939             /* engine died and we got here through time loss. In that */
9940             /* case we will get a fatal error writing the pipe, which */
9941             /* would otherwise lose us the PGN.                       */
9942             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9943             /* output during GameEnds should never be fatal anymore   */
9944             if (gameMode == MachinePlaysWhite ||
9945                 gameMode == MachinePlaysBlack ||
9946                 gameMode == TwoMachinesPlay ||
9947                 gameMode == IcsPlayingWhite ||
9948                 gameMode == IcsPlayingBlack ||
9949                 gameMode == BeginningOfGame) {
9950                 char buf[MSG_SIZ];
9951                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9952                         resultDetails);
9953                 if (first.pr != NoProc) {
9954                     SendToProgram(buf, &first);
9955                 }
9956                 if (second.pr != NoProc &&
9957                     gameMode == TwoMachinesPlay) {
9958                     SendToProgram(buf, &second);
9959                 }
9960             }
9961         }
9962
9963         if (appData.icsActive) {
9964             if (appData.quietPlay &&
9965                 (gameMode == IcsPlayingWhite ||
9966                  gameMode == IcsPlayingBlack)) {
9967                 SendToICS(ics_prefix);
9968                 SendToICS("set shout 1\n");
9969             }
9970             nextGameMode = IcsIdle;
9971             ics_user_moved = FALSE;
9972             /* clean up premove.  It's ugly when the game has ended and the
9973              * premove highlights are still on the board.
9974              */
9975             if (gotPremove) {
9976               gotPremove = FALSE;
9977               ClearPremoveHighlights();
9978               DrawPosition(FALSE, boards[currentMove]);
9979             }
9980             if (whosays == GE_ICS) {
9981                 switch (result) {
9982                 case WhiteWins:
9983                     if (gameMode == IcsPlayingWhite)
9984                         PlayIcsWinSound();
9985                     else if(gameMode == IcsPlayingBlack)
9986                         PlayIcsLossSound();
9987                     break;
9988                 case BlackWins:
9989                     if (gameMode == IcsPlayingBlack)
9990                         PlayIcsWinSound();
9991                     else if(gameMode == IcsPlayingWhite)
9992                         PlayIcsLossSound();
9993                     break;
9994                 case GameIsDrawn:
9995                     PlayIcsDrawSound();
9996                     break;
9997                 default:
9998                     PlayIcsUnfinishedSound();
9999                 }
10000             }
10001         } else if (gameMode == EditGame ||
10002                    gameMode == PlayFromGameFile ||
10003                    gameMode == AnalyzeMode ||
10004                    gameMode == AnalyzeFile) {
10005             nextGameMode = gameMode;
10006         } else {
10007             nextGameMode = EndOfGame;
10008         }
10009         pausing = FALSE;
10010         ModeHighlight();
10011     } else {
10012         nextGameMode = gameMode;
10013     }
10014
10015     if (appData.noChessProgram) {
10016         gameMode = nextGameMode;
10017         ModeHighlight();
10018         endingGame = 0; /* [HGM] crash */
10019         return;
10020     }
10021
10022     if (first.reuse) {
10023         /* Put first chess program into idle state */
10024         if (first.pr != NoProc &&
10025             (gameMode == MachinePlaysWhite ||
10026              gameMode == MachinePlaysBlack ||
10027              gameMode == TwoMachinesPlay ||
10028              gameMode == IcsPlayingWhite ||
10029              gameMode == IcsPlayingBlack ||
10030              gameMode == BeginningOfGame)) {
10031             SendToProgram("force\n", &first);
10032             if (first.usePing) {
10033               char buf[MSG_SIZ];
10034               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10035               SendToProgram(buf, &first);
10036             }
10037         }
10038     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10039         /* Kill off first chess program */
10040         if (first.isr != NULL)
10041           RemoveInputSource(first.isr);
10042         first.isr = NULL;
10043
10044         if (first.pr != NoProc) {
10045             ExitAnalyzeMode();
10046             DoSleep( appData.delayBeforeQuit );
10047             SendToProgram("quit\n", &first);
10048             DoSleep( appData.delayAfterQuit );
10049             DestroyChildProcess(first.pr, first.useSigterm);
10050         }
10051         first.pr = NoProc;
10052     }
10053     if (second.reuse) {
10054         /* Put second chess program into idle state */
10055         if (second.pr != NoProc &&
10056             gameMode == TwoMachinesPlay) {
10057             SendToProgram("force\n", &second);
10058             if (second.usePing) {
10059               char buf[MSG_SIZ];
10060               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10061               SendToProgram(buf, &second);
10062             }
10063         }
10064     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10065         /* Kill off second chess program */
10066         if (second.isr != NULL)
10067           RemoveInputSource(second.isr);
10068         second.isr = NULL;
10069
10070         if (second.pr != NoProc) {
10071             DoSleep( appData.delayBeforeQuit );
10072             SendToProgram("quit\n", &second);
10073             DoSleep( appData.delayAfterQuit );
10074             DestroyChildProcess(second.pr, second.useSigterm);
10075         }
10076         second.pr = NoProc;
10077     }
10078
10079     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10080         char resChar = '=';
10081         switch (result) {
10082         case WhiteWins:
10083           resChar = '+';
10084           if (first.twoMachinesColor[0] == 'w') {
10085             first.matchWins++;
10086           } else {
10087             second.matchWins++;
10088           }
10089           break;
10090         case BlackWins:
10091           resChar = '-';
10092           if (first.twoMachinesColor[0] == 'b') {
10093             first.matchWins++;
10094           } else {
10095             second.matchWins++;
10096           }
10097           break;
10098         case GameUnfinished:
10099           resChar = ' ';
10100         default:
10101           break;
10102         }
10103
10104         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10105         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10106             ReserveGame(nextGame, resChar); // sets nextGame
10107             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10108         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10109
10110         if (nextGame <= appData.matchGames && !abortMatch) {
10111             gameMode = nextGameMode;
10112             matchGame = nextGame; // this will be overruled in tourney mode!
10113             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10114             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10115             endingGame = 0; /* [HGM] crash */
10116             return;
10117         } else {
10118             gameMode = nextGameMode;
10119             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10120                      first.tidy, second.tidy,
10121                      first.matchWins, second.matchWins,
10122                      appData.matchGames - (first.matchWins + second.matchWins));
10123             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10124             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10125                 first.twoMachinesColor = "black\n";
10126                 second.twoMachinesColor = "white\n";
10127             } else {
10128                 first.twoMachinesColor = "white\n";
10129                 second.twoMachinesColor = "black\n";
10130             }
10131         }
10132     }
10133     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10134         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10135       ExitAnalyzeMode();
10136     gameMode = nextGameMode;
10137     ModeHighlight();
10138     endingGame = 0;  /* [HGM] crash */
10139     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10140         if(matchMode == TRUE) { // match through command line: exit with or without popup
10141             if(ranking) {
10142                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10143                 else ExitEvent(0);
10144             } else DisplayFatalError(buf, 0, 0);
10145         } else { // match through menu; just stop, with or without popup
10146             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10147             if(ranking){
10148                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10149             } else DisplayNote(buf);
10150       }
10151       if(ranking) free(ranking);
10152     }
10153 }
10154
10155 /* Assumes program was just initialized (initString sent).
10156    Leaves program in force mode. */
10157 void
10158 FeedMovesToProgram(cps, upto)
10159      ChessProgramState *cps;
10160      int upto;
10161 {
10162     int i;
10163
10164     if (appData.debugMode)
10165       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10166               startedFromSetupPosition ? "position and " : "",
10167               backwardMostMove, upto, cps->which);
10168     if(currentlyInitializedVariant != gameInfo.variant) {
10169       char buf[MSG_SIZ];
10170         // [HGM] variantswitch: make engine aware of new variant
10171         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10172                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10173         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10174         SendToProgram(buf, cps);
10175         currentlyInitializedVariant = gameInfo.variant;
10176     }
10177     SendToProgram("force\n", cps);
10178     if (startedFromSetupPosition) {
10179         SendBoard(cps, backwardMostMove);
10180     if (appData.debugMode) {
10181         fprintf(debugFP, "feedMoves\n");
10182     }
10183     }
10184     for (i = backwardMostMove; i < upto; i++) {
10185         SendMoveToProgram(i, cps);
10186     }
10187 }
10188
10189
10190 int
10191 ResurrectChessProgram()
10192 {
10193      /* The chess program may have exited.
10194         If so, restart it and feed it all the moves made so far. */
10195     static int doInit = 0;
10196
10197     if (appData.noChessProgram) return 1;
10198
10199     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10200         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10201         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10202         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10203     } else {
10204         if (first.pr != NoProc) return 1;
10205         StartChessProgram(&first);
10206     }
10207     InitChessProgram(&first, FALSE);
10208     FeedMovesToProgram(&first, currentMove);
10209
10210     if (!first.sendTime) {
10211         /* can't tell gnuchess what its clock should read,
10212            so we bow to its notion. */
10213         ResetClocks();
10214         timeRemaining[0][currentMove] = whiteTimeRemaining;
10215         timeRemaining[1][currentMove] = blackTimeRemaining;
10216     }
10217
10218     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10219                 appData.icsEngineAnalyze) && first.analysisSupport) {
10220       SendToProgram("analyze\n", &first);
10221       first.analyzing = TRUE;
10222     }
10223     return 1;
10224 }
10225
10226 /*
10227  * Button procedures
10228  */
10229 void
10230 Reset(redraw, init)
10231      int redraw, init;
10232 {
10233     int i;
10234
10235     if (appData.debugMode) {
10236         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10237                 redraw, init, gameMode);
10238     }
10239     CleanupTail(); // [HGM] vari: delete any stored variations
10240     pausing = pauseExamInvalid = FALSE;
10241     startedFromSetupPosition = blackPlaysFirst = FALSE;
10242     firstMove = TRUE;
10243     whiteFlag = blackFlag = FALSE;
10244     userOfferedDraw = FALSE;
10245     hintRequested = bookRequested = FALSE;
10246     first.maybeThinking = FALSE;
10247     second.maybeThinking = FALSE;
10248     first.bookSuspend = FALSE; // [HGM] book
10249     second.bookSuspend = FALSE;
10250     thinkOutput[0] = NULLCHAR;
10251     lastHint[0] = NULLCHAR;
10252     ClearGameInfo(&gameInfo);
10253     gameInfo.variant = StringToVariant(appData.variant);
10254     ics_user_moved = ics_clock_paused = FALSE;
10255     ics_getting_history = H_FALSE;
10256     ics_gamenum = -1;
10257     white_holding[0] = black_holding[0] = NULLCHAR;
10258     ClearProgramStats();
10259     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10260
10261     ResetFrontEnd();
10262     ClearHighlights();
10263     flipView = appData.flipView;
10264     ClearPremoveHighlights();
10265     gotPremove = FALSE;
10266     alarmSounded = FALSE;
10267
10268     GameEnds(EndOfFile, NULL, GE_PLAYER);
10269     if(appData.serverMovesName != NULL) {
10270         /* [HGM] prepare to make moves file for broadcasting */
10271         clock_t t = clock();
10272         if(serverMoves != NULL) fclose(serverMoves);
10273         serverMoves = fopen(appData.serverMovesName, "r");
10274         if(serverMoves != NULL) {
10275             fclose(serverMoves);
10276             /* delay 15 sec before overwriting, so all clients can see end */
10277             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10278         }
10279         serverMoves = fopen(appData.serverMovesName, "w");
10280     }
10281
10282     ExitAnalyzeMode();
10283     gameMode = BeginningOfGame;
10284     ModeHighlight();
10285     if(appData.icsActive) gameInfo.variant = VariantNormal;
10286     currentMove = forwardMostMove = backwardMostMove = 0;
10287     InitPosition(redraw);
10288     for (i = 0; i < MAX_MOVES; i++) {
10289         if (commentList[i] != NULL) {
10290             free(commentList[i]);
10291             commentList[i] = NULL;
10292         }
10293     }
10294     ResetClocks();
10295     timeRemaining[0][0] = whiteTimeRemaining;
10296     timeRemaining[1][0] = blackTimeRemaining;
10297
10298     if (first.pr == NULL) {
10299         StartChessProgram(&first);
10300     }
10301     if (init) {
10302             InitChessProgram(&first, startedFromSetupPosition);
10303     }
10304     DisplayTitle("");
10305     DisplayMessage("", "");
10306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10307     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10308 }
10309
10310 void
10311 AutoPlayGameLoop()
10312 {
10313     for (;;) {
10314         if (!AutoPlayOneMove())
10315           return;
10316         if (matchMode || appData.timeDelay == 0)
10317           continue;
10318         if (appData.timeDelay < 0)
10319           return;
10320         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10321         break;
10322     }
10323 }
10324
10325
10326 int
10327 AutoPlayOneMove()
10328 {
10329     int fromX, fromY, toX, toY;
10330
10331     if (appData.debugMode) {
10332       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10333     }
10334
10335     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10336       return FALSE;
10337
10338     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10339       pvInfoList[currentMove].depth = programStats.depth;
10340       pvInfoList[currentMove].score = programStats.score;
10341       pvInfoList[currentMove].time  = 0;
10342       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10343     }
10344
10345     if (currentMove >= forwardMostMove) {
10346       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10347       gameMode = EditGame;
10348       ModeHighlight();
10349
10350       /* [AS] Clear current move marker at the end of a game */
10351       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10352
10353       return FALSE;
10354     }
10355
10356     toX = moveList[currentMove][2] - AAA;
10357     toY = moveList[currentMove][3] - ONE;
10358
10359     if (moveList[currentMove][1] == '@') {
10360         if (appData.highlightLastMove) {
10361             SetHighlights(-1, -1, toX, toY);
10362         }
10363     } else {
10364         fromX = moveList[currentMove][0] - AAA;
10365         fromY = moveList[currentMove][1] - ONE;
10366
10367         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10368
10369         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10370
10371         if (appData.highlightLastMove) {
10372             SetHighlights(fromX, fromY, toX, toY);
10373         }
10374     }
10375     DisplayMove(currentMove);
10376     SendMoveToProgram(currentMove++, &first);
10377     DisplayBothClocks();
10378     DrawPosition(FALSE, boards[currentMove]);
10379     // [HGM] PV info: always display, routine tests if empty
10380     DisplayComment(currentMove - 1, commentList[currentMove]);
10381     return TRUE;
10382 }
10383
10384
10385 int
10386 LoadGameOneMove(readAhead)
10387      ChessMove readAhead;
10388 {
10389     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10390     char promoChar = NULLCHAR;
10391     ChessMove moveType;
10392     char move[MSG_SIZ];
10393     char *p, *q;
10394
10395     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10396         gameMode != AnalyzeMode && gameMode != Training) {
10397         gameFileFP = NULL;
10398         return FALSE;
10399     }
10400
10401     yyboardindex = forwardMostMove;
10402     if (readAhead != EndOfFile) {
10403       moveType = readAhead;
10404     } else {
10405       if (gameFileFP == NULL)
10406           return FALSE;
10407       moveType = (ChessMove) Myylex();
10408     }
10409
10410     done = FALSE;
10411     switch (moveType) {
10412       case Comment:
10413         if (appData.debugMode)
10414           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10415         p = yy_text;
10416
10417         /* append the comment but don't display it */
10418         AppendComment(currentMove, p, FALSE);
10419         return TRUE;
10420
10421       case WhiteCapturesEnPassant:
10422       case BlackCapturesEnPassant:
10423       case WhitePromotion:
10424       case BlackPromotion:
10425       case WhiteNonPromotion:
10426       case BlackNonPromotion:
10427       case NormalMove:
10428       case WhiteKingSideCastle:
10429       case WhiteQueenSideCastle:
10430       case BlackKingSideCastle:
10431       case BlackQueenSideCastle:
10432       case WhiteKingSideCastleWild:
10433       case WhiteQueenSideCastleWild:
10434       case BlackKingSideCastleWild:
10435       case BlackQueenSideCastleWild:
10436       /* PUSH Fabien */
10437       case WhiteHSideCastleFR:
10438       case WhiteASideCastleFR:
10439       case BlackHSideCastleFR:
10440       case BlackASideCastleFR:
10441       /* POP Fabien */
10442         if (appData.debugMode)
10443           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10444         fromX = currentMoveString[0] - AAA;
10445         fromY = currentMoveString[1] - ONE;
10446         toX = currentMoveString[2] - AAA;
10447         toY = currentMoveString[3] - ONE;
10448         promoChar = currentMoveString[4];
10449         break;
10450
10451       case WhiteDrop:
10452       case BlackDrop:
10453         if (appData.debugMode)
10454           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10455         fromX = moveType == WhiteDrop ?
10456           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10457         (int) CharToPiece(ToLower(currentMoveString[0]));
10458         fromY = DROP_RANK;
10459         toX = currentMoveString[2] - AAA;
10460         toY = currentMoveString[3] - ONE;
10461         break;
10462
10463       case WhiteWins:
10464       case BlackWins:
10465       case GameIsDrawn:
10466       case GameUnfinished:
10467         if (appData.debugMode)
10468           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10469         p = strchr(yy_text, '{');
10470         if (p == NULL) p = strchr(yy_text, '(');
10471         if (p == NULL) {
10472             p = yy_text;
10473             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10474         } else {
10475             q = strchr(p, *p == '{' ? '}' : ')');
10476             if (q != NULL) *q = NULLCHAR;
10477             p++;
10478         }
10479         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10480         GameEnds(moveType, p, GE_FILE);
10481         done = TRUE;
10482         if (cmailMsgLoaded) {
10483             ClearHighlights();
10484             flipView = WhiteOnMove(currentMove);
10485             if (moveType == GameUnfinished) flipView = !flipView;
10486             if (appData.debugMode)
10487               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10488         }
10489         break;
10490
10491       case EndOfFile:
10492         if (appData.debugMode)
10493           fprintf(debugFP, "Parser hit end of file\n");
10494         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10495           case MT_NONE:
10496           case MT_CHECK:
10497             break;
10498           case MT_CHECKMATE:
10499           case MT_STAINMATE:
10500             if (WhiteOnMove(currentMove)) {
10501                 GameEnds(BlackWins, "Black mates", GE_FILE);
10502             } else {
10503                 GameEnds(WhiteWins, "White mates", GE_FILE);
10504             }
10505             break;
10506           case MT_STALEMATE:
10507             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10508             break;
10509         }
10510         done = TRUE;
10511         break;
10512
10513       case MoveNumberOne:
10514         if (lastLoadGameStart == GNUChessGame) {
10515             /* GNUChessGames have numbers, but they aren't move numbers */
10516             if (appData.debugMode)
10517               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10518                       yy_text, (int) moveType);
10519             return LoadGameOneMove(EndOfFile); /* tail recursion */
10520         }
10521         /* else fall thru */
10522
10523       case XBoardGame:
10524       case GNUChessGame:
10525       case PGNTag:
10526         /* Reached start of next game in file */
10527         if (appData.debugMode)
10528           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10529         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10530           case MT_NONE:
10531           case MT_CHECK:
10532             break;
10533           case MT_CHECKMATE:
10534           case MT_STAINMATE:
10535             if (WhiteOnMove(currentMove)) {
10536                 GameEnds(BlackWins, "Black mates", GE_FILE);
10537             } else {
10538                 GameEnds(WhiteWins, "White mates", GE_FILE);
10539             }
10540             break;
10541           case MT_STALEMATE:
10542             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10543             break;
10544         }
10545         done = TRUE;
10546         break;
10547
10548       case PositionDiagram:     /* should not happen; ignore */
10549       case ElapsedTime:         /* ignore */
10550       case NAG:                 /* ignore */
10551         if (appData.debugMode)
10552           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10553                   yy_text, (int) moveType);
10554         return LoadGameOneMove(EndOfFile); /* tail recursion */
10555
10556       case IllegalMove:
10557         if (appData.testLegality) {
10558             if (appData.debugMode)
10559               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10560             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10561                     (forwardMostMove / 2) + 1,
10562                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10563             DisplayError(move, 0);
10564             done = TRUE;
10565         } else {
10566             if (appData.debugMode)
10567               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10568                       yy_text, currentMoveString);
10569             fromX = currentMoveString[0] - AAA;
10570             fromY = currentMoveString[1] - ONE;
10571             toX = currentMoveString[2] - AAA;
10572             toY = currentMoveString[3] - ONE;
10573             promoChar = currentMoveString[4];
10574         }
10575         break;
10576
10577       case AmbiguousMove:
10578         if (appData.debugMode)
10579           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10580         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10581                 (forwardMostMove / 2) + 1,
10582                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10583         DisplayError(move, 0);
10584         done = TRUE;
10585         break;
10586
10587       default:
10588       case ImpossibleMove:
10589         if (appData.debugMode)
10590           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10591         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10592                 (forwardMostMove / 2) + 1,
10593                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10594         DisplayError(move, 0);
10595         done = TRUE;
10596         break;
10597     }
10598
10599     if (done) {
10600         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10601             DrawPosition(FALSE, boards[currentMove]);
10602             DisplayBothClocks();
10603             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10604               DisplayComment(currentMove - 1, commentList[currentMove]);
10605         }
10606         (void) StopLoadGameTimer();
10607         gameFileFP = NULL;
10608         cmailOldMove = forwardMostMove;
10609         return FALSE;
10610     } else {
10611         /* currentMoveString is set as a side-effect of yylex */
10612
10613         thinkOutput[0] = NULLCHAR;
10614         MakeMove(fromX, fromY, toX, toY, promoChar);
10615         currentMove = forwardMostMove;
10616         return TRUE;
10617     }
10618 }
10619
10620 /* Load the nth game from the given file */
10621 int
10622 LoadGameFromFile(filename, n, title, useList)
10623      char *filename;
10624      int n;
10625      char *title;
10626      /*Boolean*/ int useList;
10627 {
10628     FILE *f;
10629     char buf[MSG_SIZ];
10630
10631     if (strcmp(filename, "-") == 0) {
10632         f = stdin;
10633         title = "stdin";
10634     } else {
10635         f = fopen(filename, "rb");
10636         if (f == NULL) {
10637           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10638             DisplayError(buf, errno);
10639             return FALSE;
10640         }
10641     }
10642     if (fseek(f, 0, 0) == -1) {
10643         /* f is not seekable; probably a pipe */
10644         useList = FALSE;
10645     }
10646     if (useList && n == 0) {
10647         int error = GameListBuild(f);
10648         if (error) {
10649             DisplayError(_("Cannot build game list"), error);
10650         } else if (!ListEmpty(&gameList) &&
10651                    ((ListGame *) gameList.tailPred)->number > 1) {
10652             GameListPopUp(f, title);
10653             return TRUE;
10654         }
10655         GameListDestroy();
10656         n = 1;
10657     }
10658     if (n == 0) n = 1;
10659     return LoadGame(f, n, title, FALSE);
10660 }
10661
10662
10663 void
10664 MakeRegisteredMove()
10665 {
10666     int fromX, fromY, toX, toY;
10667     char promoChar;
10668     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10669         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10670           case CMAIL_MOVE:
10671           case CMAIL_DRAW:
10672             if (appData.debugMode)
10673               fprintf(debugFP, "Restoring %s for game %d\n",
10674                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10675
10676             thinkOutput[0] = NULLCHAR;
10677             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10678             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10679             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10680             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10681             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10682             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10683             MakeMove(fromX, fromY, toX, toY, promoChar);
10684             ShowMove(fromX, fromY, toX, toY);
10685
10686             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10687               case MT_NONE:
10688               case MT_CHECK:
10689                 break;
10690
10691               case MT_CHECKMATE:
10692               case MT_STAINMATE:
10693                 if (WhiteOnMove(currentMove)) {
10694                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10695                 } else {
10696                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10697                 }
10698                 break;
10699
10700               case MT_STALEMATE:
10701                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10702                 break;
10703             }
10704
10705             break;
10706
10707           case CMAIL_RESIGN:
10708             if (WhiteOnMove(currentMove)) {
10709                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10710             } else {
10711                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10712             }
10713             break;
10714
10715           case CMAIL_ACCEPT:
10716             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10717             break;
10718
10719           default:
10720             break;
10721         }
10722     }
10723
10724     return;
10725 }
10726
10727 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10728 int
10729 CmailLoadGame(f, gameNumber, title, useList)
10730      FILE *f;
10731      int gameNumber;
10732      char *title;
10733      int useList;
10734 {
10735     int retVal;
10736
10737     if (gameNumber > nCmailGames) {
10738         DisplayError(_("No more games in this message"), 0);
10739         return FALSE;
10740     }
10741     if (f == lastLoadGameFP) {
10742         int offset = gameNumber - lastLoadGameNumber;
10743         if (offset == 0) {
10744             cmailMsg[0] = NULLCHAR;
10745             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10746                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10747                 nCmailMovesRegistered--;
10748             }
10749             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10750             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10751                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10752             }
10753         } else {
10754             if (! RegisterMove()) return FALSE;
10755         }
10756     }
10757
10758     retVal = LoadGame(f, gameNumber, title, useList);
10759
10760     /* Make move registered during previous look at this game, if any */
10761     MakeRegisteredMove();
10762
10763     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10764         commentList[currentMove]
10765           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10766         DisplayComment(currentMove - 1, commentList[currentMove]);
10767     }
10768
10769     return retVal;
10770 }
10771
10772 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10773 int
10774 ReloadGame(offset)
10775      int offset;
10776 {
10777     int gameNumber = lastLoadGameNumber + offset;
10778     if (lastLoadGameFP == NULL) {
10779         DisplayError(_("No game has been loaded yet"), 0);
10780         return FALSE;
10781     }
10782     if (gameNumber <= 0) {
10783         DisplayError(_("Can't back up any further"), 0);
10784         return FALSE;
10785     }
10786     if (cmailMsgLoaded) {
10787         return CmailLoadGame(lastLoadGameFP, gameNumber,
10788                              lastLoadGameTitle, lastLoadGameUseList);
10789     } else {
10790         return LoadGame(lastLoadGameFP, gameNumber,
10791                         lastLoadGameTitle, lastLoadGameUseList);
10792     }
10793 }
10794
10795
10796
10797 /* Load the nth game from open file f */
10798 int
10799 LoadGame(f, gameNumber, title, useList)
10800      FILE *f;
10801      int gameNumber;
10802      char *title;
10803      int useList;
10804 {
10805     ChessMove cm;
10806     char buf[MSG_SIZ];
10807     int gn = gameNumber;
10808     ListGame *lg = NULL;
10809     int numPGNTags = 0;
10810     int err;
10811     GameMode oldGameMode;
10812     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10813
10814     if (appData.debugMode)
10815         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10816
10817     if (gameMode == Training )
10818         SetTrainingModeOff();
10819
10820     oldGameMode = gameMode;
10821     if (gameMode != BeginningOfGame) {
10822       Reset(FALSE, TRUE);
10823     }
10824
10825     gameFileFP = f;
10826     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10827         fclose(lastLoadGameFP);
10828     }
10829
10830     if (useList) {
10831         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10832
10833         if (lg) {
10834             fseek(f, lg->offset, 0);
10835             GameListHighlight(gameNumber);
10836             gn = 1;
10837         }
10838         else {
10839             DisplayError(_("Game number out of range"), 0);
10840             return FALSE;
10841         }
10842     } else {
10843         GameListDestroy();
10844         if (fseek(f, 0, 0) == -1) {
10845             if (f == lastLoadGameFP ?
10846                 gameNumber == lastLoadGameNumber + 1 :
10847                 gameNumber == 1) {
10848                 gn = 1;
10849             } else {
10850                 DisplayError(_("Can't seek on game file"), 0);
10851                 return FALSE;
10852             }
10853         }
10854     }
10855     lastLoadGameFP = f;
10856     lastLoadGameNumber = gameNumber;
10857     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10858     lastLoadGameUseList = useList;
10859
10860     yynewfile(f);
10861
10862     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10863       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10864                 lg->gameInfo.black);
10865             DisplayTitle(buf);
10866     } else if (*title != NULLCHAR) {
10867         if (gameNumber > 1) {
10868           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10869             DisplayTitle(buf);
10870         } else {
10871             DisplayTitle(title);
10872         }
10873     }
10874
10875     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10876         gameMode = PlayFromGameFile;
10877         ModeHighlight();
10878     }
10879
10880     currentMove = forwardMostMove = backwardMostMove = 0;
10881     CopyBoard(boards[0], initialPosition);
10882     StopClocks();
10883
10884     /*
10885      * Skip the first gn-1 games in the file.
10886      * Also skip over anything that precedes an identifiable
10887      * start of game marker, to avoid being confused by
10888      * garbage at the start of the file.  Currently
10889      * recognized start of game markers are the move number "1",
10890      * the pattern "gnuchess .* game", the pattern
10891      * "^[#;%] [^ ]* game file", and a PGN tag block.
10892      * A game that starts with one of the latter two patterns
10893      * will also have a move number 1, possibly
10894      * following a position diagram.
10895      * 5-4-02: Let's try being more lenient and allowing a game to
10896      * start with an unnumbered move.  Does that break anything?
10897      */
10898     cm = lastLoadGameStart = EndOfFile;
10899     while (gn > 0) {
10900         yyboardindex = forwardMostMove;
10901         cm = (ChessMove) Myylex();
10902         switch (cm) {
10903           case EndOfFile:
10904             if (cmailMsgLoaded) {
10905                 nCmailGames = CMAIL_MAX_GAMES - gn;
10906             } else {
10907                 Reset(TRUE, TRUE);
10908                 DisplayError(_("Game not found in file"), 0);
10909             }
10910             return FALSE;
10911
10912           case GNUChessGame:
10913           case XBoardGame:
10914             gn--;
10915             lastLoadGameStart = cm;
10916             break;
10917
10918           case MoveNumberOne:
10919             switch (lastLoadGameStart) {
10920               case GNUChessGame:
10921               case XBoardGame:
10922               case PGNTag:
10923                 break;
10924               case MoveNumberOne:
10925               case EndOfFile:
10926                 gn--;           /* count this game */
10927                 lastLoadGameStart = cm;
10928                 break;
10929               default:
10930                 /* impossible */
10931                 break;
10932             }
10933             break;
10934
10935           case PGNTag:
10936             switch (lastLoadGameStart) {
10937               case GNUChessGame:
10938               case PGNTag:
10939               case MoveNumberOne:
10940               case EndOfFile:
10941                 gn--;           /* count this game */
10942                 lastLoadGameStart = cm;
10943                 break;
10944               case XBoardGame:
10945                 lastLoadGameStart = cm; /* game counted already */
10946                 break;
10947               default:
10948                 /* impossible */
10949                 break;
10950             }
10951             if (gn > 0) {
10952                 do {
10953                     yyboardindex = forwardMostMove;
10954                     cm = (ChessMove) Myylex();
10955                 } while (cm == PGNTag || cm == Comment);
10956             }
10957             break;
10958
10959           case WhiteWins:
10960           case BlackWins:
10961           case GameIsDrawn:
10962             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10963                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10964                     != CMAIL_OLD_RESULT) {
10965                     nCmailResults ++ ;
10966                     cmailResult[  CMAIL_MAX_GAMES
10967                                 - gn - 1] = CMAIL_OLD_RESULT;
10968                 }
10969             }
10970             break;
10971
10972           case NormalMove:
10973             /* Only a NormalMove can be at the start of a game
10974              * without a position diagram. */
10975             if (lastLoadGameStart == EndOfFile ) {
10976               gn--;
10977               lastLoadGameStart = MoveNumberOne;
10978             }
10979             break;
10980
10981           default:
10982             break;
10983         }
10984     }
10985
10986     if (appData.debugMode)
10987       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10988
10989     if (cm == XBoardGame) {
10990         /* Skip any header junk before position diagram and/or move 1 */
10991         for (;;) {
10992             yyboardindex = forwardMostMove;
10993             cm = (ChessMove) Myylex();
10994
10995             if (cm == EndOfFile ||
10996                 cm == GNUChessGame || cm == XBoardGame) {
10997                 /* Empty game; pretend end-of-file and handle later */
10998                 cm = EndOfFile;
10999                 break;
11000             }
11001
11002             if (cm == MoveNumberOne || cm == PositionDiagram ||
11003                 cm == PGNTag || cm == Comment)
11004               break;
11005         }
11006     } else if (cm == GNUChessGame) {
11007         if (gameInfo.event != NULL) {
11008             free(gameInfo.event);
11009         }
11010         gameInfo.event = StrSave(yy_text);
11011     }
11012
11013     startedFromSetupPosition = FALSE;
11014     while (cm == PGNTag) {
11015         if (appData.debugMode)
11016           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11017         err = ParsePGNTag(yy_text, &gameInfo);
11018         if (!err) numPGNTags++;
11019
11020         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11021         if(gameInfo.variant != oldVariant) {
11022             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11023             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11024             InitPosition(TRUE);
11025             oldVariant = gameInfo.variant;
11026             if (appData.debugMode)
11027               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11028         }
11029
11030
11031         if (gameInfo.fen != NULL) {
11032           Board initial_position;
11033           startedFromSetupPosition = TRUE;
11034           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11035             Reset(TRUE, TRUE);
11036             DisplayError(_("Bad FEN position in file"), 0);
11037             return FALSE;
11038           }
11039           CopyBoard(boards[0], initial_position);
11040           if (blackPlaysFirst) {
11041             currentMove = forwardMostMove = backwardMostMove = 1;
11042             CopyBoard(boards[1], initial_position);
11043             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11044             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11045             timeRemaining[0][1] = whiteTimeRemaining;
11046             timeRemaining[1][1] = blackTimeRemaining;
11047             if (commentList[0] != NULL) {
11048               commentList[1] = commentList[0];
11049               commentList[0] = NULL;
11050             }
11051           } else {
11052             currentMove = forwardMostMove = backwardMostMove = 0;
11053           }
11054           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11055           {   int i;
11056               initialRulePlies = FENrulePlies;
11057               for( i=0; i< nrCastlingRights; i++ )
11058                   initialRights[i] = initial_position[CASTLING][i];
11059           }
11060           yyboardindex = forwardMostMove;
11061           free(gameInfo.fen);
11062           gameInfo.fen = NULL;
11063         }
11064
11065         yyboardindex = forwardMostMove;
11066         cm = (ChessMove) Myylex();
11067
11068         /* Handle comments interspersed among the tags */
11069         while (cm == Comment) {
11070             char *p;
11071             if (appData.debugMode)
11072               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11073             p = yy_text;
11074             AppendComment(currentMove, p, FALSE);
11075             yyboardindex = forwardMostMove;
11076             cm = (ChessMove) Myylex();
11077         }
11078     }
11079
11080     /* don't rely on existence of Event tag since if game was
11081      * pasted from clipboard the Event tag may not exist
11082      */
11083     if (numPGNTags > 0){
11084         char *tags;
11085         if (gameInfo.variant == VariantNormal) {
11086           VariantClass v = StringToVariant(gameInfo.event);
11087           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11088           if(v < VariantShogi) gameInfo.variant = v;
11089         }
11090         if (!matchMode) {
11091           if( appData.autoDisplayTags ) {
11092             tags = PGNTags(&gameInfo);
11093             TagsPopUp(tags, CmailMsg());
11094             free(tags);
11095           }
11096         }
11097     } else {
11098         /* Make something up, but don't display it now */
11099         SetGameInfo();
11100         TagsPopDown();
11101     }
11102
11103     if (cm == PositionDiagram) {
11104         int i, j;
11105         char *p;
11106         Board initial_position;
11107
11108         if (appData.debugMode)
11109           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11110
11111         if (!startedFromSetupPosition) {
11112             p = yy_text;
11113             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11114               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11115                 switch (*p) {
11116                   case '{':
11117                   case '[':
11118                   case '-':
11119                   case ' ':
11120                   case '\t':
11121                   case '\n':
11122                   case '\r':
11123                     break;
11124                   default:
11125                     initial_position[i][j++] = CharToPiece(*p);
11126                     break;
11127                 }
11128             while (*p == ' ' || *p == '\t' ||
11129                    *p == '\n' || *p == '\r') p++;
11130
11131             if (strncmp(p, "black", strlen("black"))==0)
11132               blackPlaysFirst = TRUE;
11133             else
11134               blackPlaysFirst = FALSE;
11135             startedFromSetupPosition = TRUE;
11136
11137             CopyBoard(boards[0], initial_position);
11138             if (blackPlaysFirst) {
11139                 currentMove = forwardMostMove = backwardMostMove = 1;
11140                 CopyBoard(boards[1], initial_position);
11141                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11142                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11143                 timeRemaining[0][1] = whiteTimeRemaining;
11144                 timeRemaining[1][1] = blackTimeRemaining;
11145                 if (commentList[0] != NULL) {
11146                     commentList[1] = commentList[0];
11147                     commentList[0] = NULL;
11148                 }
11149             } else {
11150                 currentMove = forwardMostMove = backwardMostMove = 0;
11151             }
11152         }
11153         yyboardindex = forwardMostMove;
11154         cm = (ChessMove) Myylex();
11155     }
11156
11157     if (first.pr == NoProc) {
11158         StartChessProgram(&first);
11159     }
11160     InitChessProgram(&first, FALSE);
11161     SendToProgram("force\n", &first);
11162     if (startedFromSetupPosition) {
11163         SendBoard(&first, forwardMostMove);
11164     if (appData.debugMode) {
11165         fprintf(debugFP, "Load Game\n");
11166     }
11167         DisplayBothClocks();
11168     }
11169
11170     /* [HGM] server: flag to write setup moves in broadcast file as one */
11171     loadFlag = appData.suppressLoadMoves;
11172
11173     while (cm == Comment) {
11174         char *p;
11175         if (appData.debugMode)
11176           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11177         p = yy_text;
11178         AppendComment(currentMove, p, FALSE);
11179         yyboardindex = forwardMostMove;
11180         cm = (ChessMove) Myylex();
11181     }
11182
11183     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11184         cm == WhiteWins || cm == BlackWins ||
11185         cm == GameIsDrawn || cm == GameUnfinished) {
11186         DisplayMessage("", _("No moves in game"));
11187         if (cmailMsgLoaded) {
11188             if (appData.debugMode)
11189               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11190             ClearHighlights();
11191             flipView = FALSE;
11192         }
11193         DrawPosition(FALSE, boards[currentMove]);
11194         DisplayBothClocks();
11195         gameMode = EditGame;
11196         ModeHighlight();
11197         gameFileFP = NULL;
11198         cmailOldMove = 0;
11199         return TRUE;
11200     }
11201
11202     // [HGM] PV info: routine tests if comment empty
11203     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11204         DisplayComment(currentMove - 1, commentList[currentMove]);
11205     }
11206     if (!matchMode && appData.timeDelay != 0)
11207       DrawPosition(FALSE, boards[currentMove]);
11208
11209     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11210       programStats.ok_to_send = 1;
11211     }
11212
11213     /* if the first token after the PGN tags is a move
11214      * and not move number 1, retrieve it from the parser
11215      */
11216     if (cm != MoveNumberOne)
11217         LoadGameOneMove(cm);
11218
11219     /* load the remaining moves from the file */
11220     while (LoadGameOneMove(EndOfFile)) {
11221       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11222       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11223     }
11224
11225     /* rewind to the start of the game */
11226     currentMove = backwardMostMove;
11227
11228     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11229
11230     if (oldGameMode == AnalyzeFile ||
11231         oldGameMode == AnalyzeMode) {
11232       AnalyzeFileEvent();
11233     }
11234
11235     if (matchMode || appData.timeDelay == 0) {
11236       ToEndEvent();
11237       gameMode = EditGame;
11238       ModeHighlight();
11239     } else if (appData.timeDelay > 0) {
11240       AutoPlayGameLoop();
11241     }
11242
11243     if (appData.debugMode)
11244         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11245
11246     loadFlag = 0; /* [HGM] true game starts */
11247     return TRUE;
11248 }
11249
11250 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11251 int
11252 ReloadPosition(offset)
11253      int offset;
11254 {
11255     int positionNumber = lastLoadPositionNumber + offset;
11256     if (lastLoadPositionFP == NULL) {
11257         DisplayError(_("No position has been loaded yet"), 0);
11258         return FALSE;
11259     }
11260     if (positionNumber <= 0) {
11261         DisplayError(_("Can't back up any further"), 0);
11262         return FALSE;
11263     }
11264     return LoadPosition(lastLoadPositionFP, positionNumber,
11265                         lastLoadPositionTitle);
11266 }
11267
11268 /* Load the nth position from the given file */
11269 int
11270 LoadPositionFromFile(filename, n, title)
11271      char *filename;
11272      int n;
11273      char *title;
11274 {
11275     FILE *f;
11276     char buf[MSG_SIZ];
11277
11278     if (strcmp(filename, "-") == 0) {
11279         return LoadPosition(stdin, n, "stdin");
11280     } else {
11281         f = fopen(filename, "rb");
11282         if (f == NULL) {
11283             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11284             DisplayError(buf, errno);
11285             return FALSE;
11286         } else {
11287             return LoadPosition(f, n, title);
11288         }
11289     }
11290 }
11291
11292 /* Load the nth position from the given open file, and close it */
11293 int
11294 LoadPosition(f, positionNumber, title)
11295      FILE *f;
11296      int positionNumber;
11297      char *title;
11298 {
11299     char *p, line[MSG_SIZ];
11300     Board initial_position;
11301     int i, j, fenMode, pn;
11302
11303     if (gameMode == Training )
11304         SetTrainingModeOff();
11305
11306     if (gameMode != BeginningOfGame) {
11307         Reset(FALSE, TRUE);
11308     }
11309     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11310         fclose(lastLoadPositionFP);
11311     }
11312     if (positionNumber == 0) positionNumber = 1;
11313     lastLoadPositionFP = f;
11314     lastLoadPositionNumber = positionNumber;
11315     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11316     if (first.pr == NoProc) {
11317       StartChessProgram(&first);
11318       InitChessProgram(&first, FALSE);
11319     }
11320     pn = positionNumber;
11321     if (positionNumber < 0) {
11322         /* Negative position number means to seek to that byte offset */
11323         if (fseek(f, -positionNumber, 0) == -1) {
11324             DisplayError(_("Can't seek on position file"), 0);
11325             return FALSE;
11326         };
11327         pn = 1;
11328     } else {
11329         if (fseek(f, 0, 0) == -1) {
11330             if (f == lastLoadPositionFP ?
11331                 positionNumber == lastLoadPositionNumber + 1 :
11332                 positionNumber == 1) {
11333                 pn = 1;
11334             } else {
11335                 DisplayError(_("Can't seek on position file"), 0);
11336                 return FALSE;
11337             }
11338         }
11339     }
11340     /* See if this file is FEN or old-style xboard */
11341     if (fgets(line, MSG_SIZ, f) == NULL) {
11342         DisplayError(_("Position not found in file"), 0);
11343         return FALSE;
11344     }
11345     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11346     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11347
11348     if (pn >= 2) {
11349         if (fenMode || line[0] == '#') pn--;
11350         while (pn > 0) {
11351             /* skip positions before number pn */
11352             if (fgets(line, MSG_SIZ, f) == NULL) {
11353                 Reset(TRUE, TRUE);
11354                 DisplayError(_("Position not found in file"), 0);
11355                 return FALSE;
11356             }
11357             if (fenMode || line[0] == '#') pn--;
11358         }
11359     }
11360
11361     if (fenMode) {
11362         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11363             DisplayError(_("Bad FEN position in file"), 0);
11364             return FALSE;
11365         }
11366     } else {
11367         (void) fgets(line, MSG_SIZ, f);
11368         (void) fgets(line, MSG_SIZ, f);
11369
11370         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11371             (void) fgets(line, MSG_SIZ, f);
11372             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11373                 if (*p == ' ')
11374                   continue;
11375                 initial_position[i][j++] = CharToPiece(*p);
11376             }
11377         }
11378
11379         blackPlaysFirst = FALSE;
11380         if (!feof(f)) {
11381             (void) fgets(line, MSG_SIZ, f);
11382             if (strncmp(line, "black", strlen("black"))==0)
11383               blackPlaysFirst = TRUE;
11384         }
11385     }
11386     startedFromSetupPosition = TRUE;
11387
11388     SendToProgram("force\n", &first);
11389     CopyBoard(boards[0], initial_position);
11390     if (blackPlaysFirst) {
11391         currentMove = forwardMostMove = backwardMostMove = 1;
11392         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11393         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11394         CopyBoard(boards[1], initial_position);
11395         DisplayMessage("", _("Black to play"));
11396     } else {
11397         currentMove = forwardMostMove = backwardMostMove = 0;
11398         DisplayMessage("", _("White to play"));
11399     }
11400     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11401     SendBoard(&first, forwardMostMove);
11402     if (appData.debugMode) {
11403 int i, j;
11404   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11405   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11406         fprintf(debugFP, "Load Position\n");
11407     }
11408
11409     if (positionNumber > 1) {
11410       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11411         DisplayTitle(line);
11412     } else {
11413         DisplayTitle(title);
11414     }
11415     gameMode = EditGame;
11416     ModeHighlight();
11417     ResetClocks();
11418     timeRemaining[0][1] = whiteTimeRemaining;
11419     timeRemaining[1][1] = blackTimeRemaining;
11420     DrawPosition(FALSE, boards[currentMove]);
11421
11422     return TRUE;
11423 }
11424
11425
11426 void
11427 CopyPlayerNameIntoFileName(dest, src)
11428      char **dest, *src;
11429 {
11430     while (*src != NULLCHAR && *src != ',') {
11431         if (*src == ' ') {
11432             *(*dest)++ = '_';
11433             src++;
11434         } else {
11435             *(*dest)++ = *src++;
11436         }
11437     }
11438 }
11439
11440 char *DefaultFileName(ext)
11441      char *ext;
11442 {
11443     static char def[MSG_SIZ];
11444     char *p;
11445
11446     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11447         p = def;
11448         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11449         *p++ = '-';
11450         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11451         *p++ = '.';
11452         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11453     } else {
11454         def[0] = NULLCHAR;
11455     }
11456     return def;
11457 }
11458
11459 /* Save the current game to the given file */
11460 int
11461 SaveGameToFile(filename, append)
11462      char *filename;
11463      int append;
11464 {
11465     FILE *f;
11466     char buf[MSG_SIZ];
11467     int result;
11468
11469     if (strcmp(filename, "-") == 0) {
11470         return SaveGame(stdout, 0, NULL);
11471     } else {
11472         f = fopen(filename, append ? "a" : "w");
11473         if (f == NULL) {
11474             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11475             DisplayError(buf, errno);
11476             return FALSE;
11477         } else {
11478             safeStrCpy(buf, lastMsg, MSG_SIZ);
11479             DisplayMessage(_("Waiting for access to save file"), "");
11480             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11481             DisplayMessage(_("Saving game"), "");
11482             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11483             result = SaveGame(f, 0, NULL);
11484             DisplayMessage(buf, "");
11485             return result;
11486         }
11487     }
11488 }
11489
11490 char *
11491 SavePart(str)
11492      char *str;
11493 {
11494     static char buf[MSG_SIZ];
11495     char *p;
11496
11497     p = strchr(str, ' ');
11498     if (p == NULL) return str;
11499     strncpy(buf, str, p - str);
11500     buf[p - str] = NULLCHAR;
11501     return buf;
11502 }
11503
11504 #define PGN_MAX_LINE 75
11505
11506 #define PGN_SIDE_WHITE  0
11507 #define PGN_SIDE_BLACK  1
11508
11509 /* [AS] */
11510 static int FindFirstMoveOutOfBook( int side )
11511 {
11512     int result = -1;
11513
11514     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11515         int index = backwardMostMove;
11516         int has_book_hit = 0;
11517
11518         if( (index % 2) != side ) {
11519             index++;
11520         }
11521
11522         while( index < forwardMostMove ) {
11523             /* Check to see if engine is in book */
11524             int depth = pvInfoList[index].depth;
11525             int score = pvInfoList[index].score;
11526             int in_book = 0;
11527
11528             if( depth <= 2 ) {
11529                 in_book = 1;
11530             }
11531             else if( score == 0 && depth == 63 ) {
11532                 in_book = 1; /* Zappa */
11533             }
11534             else if( score == 2 && depth == 99 ) {
11535                 in_book = 1; /* Abrok */
11536             }
11537
11538             has_book_hit += in_book;
11539
11540             if( ! in_book ) {
11541                 result = index;
11542
11543                 break;
11544             }
11545
11546             index += 2;
11547         }
11548     }
11549
11550     return result;
11551 }
11552
11553 /* [AS] */
11554 void GetOutOfBookInfo( char * buf )
11555 {
11556     int oob[2];
11557     int i;
11558     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11559
11560     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11561     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11562
11563     *buf = '\0';
11564
11565     if( oob[0] >= 0 || oob[1] >= 0 ) {
11566         for( i=0; i<2; i++ ) {
11567             int idx = oob[i];
11568
11569             if( idx >= 0 ) {
11570                 if( i > 0 && oob[0] >= 0 ) {
11571                     strcat( buf, "   " );
11572                 }
11573
11574                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11575                 sprintf( buf+strlen(buf), "%s%.2f",
11576                     pvInfoList[idx].score >= 0 ? "+" : "",
11577                     pvInfoList[idx].score / 100.0 );
11578             }
11579         }
11580     }
11581 }
11582
11583 /* Save game in PGN style and close the file */
11584 int
11585 SaveGamePGN(f)
11586      FILE *f;
11587 {
11588     int i, offset, linelen, newblock;
11589     time_t tm;
11590 //    char *movetext;
11591     char numtext[32];
11592     int movelen, numlen, blank;
11593     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11594
11595     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11596
11597     tm = time((time_t *) NULL);
11598
11599     PrintPGNTags(f, &gameInfo);
11600
11601     if (backwardMostMove > 0 || startedFromSetupPosition) {
11602         char *fen = PositionToFEN(backwardMostMove, NULL);
11603         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11604         fprintf(f, "\n{--------------\n");
11605         PrintPosition(f, backwardMostMove);
11606         fprintf(f, "--------------}\n");
11607         free(fen);
11608     }
11609     else {
11610         /* [AS] Out of book annotation */
11611         if( appData.saveOutOfBookInfo ) {
11612             char buf[64];
11613
11614             GetOutOfBookInfo( buf );
11615
11616             if( buf[0] != '\0' ) {
11617                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11618             }
11619         }
11620
11621         fprintf(f, "\n");
11622     }
11623
11624     i = backwardMostMove;
11625     linelen = 0;
11626     newblock = TRUE;
11627
11628     while (i < forwardMostMove) {
11629         /* Print comments preceding this move */
11630         if (commentList[i] != NULL) {
11631             if (linelen > 0) fprintf(f, "\n");
11632             fprintf(f, "%s", commentList[i]);
11633             linelen = 0;
11634             newblock = TRUE;
11635         }
11636
11637         /* Format move number */
11638         if ((i % 2) == 0)
11639           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11640         else
11641           if (newblock)
11642             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11643           else
11644             numtext[0] = NULLCHAR;
11645
11646         numlen = strlen(numtext);
11647         newblock = FALSE;
11648
11649         /* Print move number */
11650         blank = linelen > 0 && numlen > 0;
11651         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11652             fprintf(f, "\n");
11653             linelen = 0;
11654             blank = 0;
11655         }
11656         if (blank) {
11657             fprintf(f, " ");
11658             linelen++;
11659         }
11660         fprintf(f, "%s", numtext);
11661         linelen += numlen;
11662
11663         /* Get move */
11664         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11665         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11666
11667         /* Print move */
11668         blank = linelen > 0 && movelen > 0;
11669         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11670             fprintf(f, "\n");
11671             linelen = 0;
11672             blank = 0;
11673         }
11674         if (blank) {
11675             fprintf(f, " ");
11676             linelen++;
11677         }
11678         fprintf(f, "%s", move_buffer);
11679         linelen += movelen;
11680
11681         /* [AS] Add PV info if present */
11682         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11683             /* [HGM] add time */
11684             char buf[MSG_SIZ]; int seconds;
11685
11686             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11687
11688             if( seconds <= 0)
11689               buf[0] = 0;
11690             else
11691               if( seconds < 30 )
11692                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11693               else
11694                 {
11695                   seconds = (seconds + 4)/10; // round to full seconds
11696                   if( seconds < 60 )
11697                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11698                   else
11699                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11700                 }
11701
11702             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11703                       pvInfoList[i].score >= 0 ? "+" : "",
11704                       pvInfoList[i].score / 100.0,
11705                       pvInfoList[i].depth,
11706                       buf );
11707
11708             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11709
11710             /* Print score/depth */
11711             blank = linelen > 0 && movelen > 0;
11712             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11713                 fprintf(f, "\n");
11714                 linelen = 0;
11715                 blank = 0;
11716             }
11717             if (blank) {
11718                 fprintf(f, " ");
11719                 linelen++;
11720             }
11721             fprintf(f, "%s", move_buffer);
11722             linelen += movelen;
11723         }
11724
11725         i++;
11726     }
11727
11728     /* Start a new line */
11729     if (linelen > 0) fprintf(f, "\n");
11730
11731     /* Print comments after last move */
11732     if (commentList[i] != NULL) {
11733         fprintf(f, "%s\n", commentList[i]);
11734     }
11735
11736     /* Print result */
11737     if (gameInfo.resultDetails != NULL &&
11738         gameInfo.resultDetails[0] != NULLCHAR) {
11739         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11740                 PGNResult(gameInfo.result));
11741     } else {
11742         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11743     }
11744
11745     fclose(f);
11746     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11747     return TRUE;
11748 }
11749
11750 /* Save game in old style and close the file */
11751 int
11752 SaveGameOldStyle(f)
11753      FILE *f;
11754 {
11755     int i, offset;
11756     time_t tm;
11757
11758     tm = time((time_t *) NULL);
11759
11760     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11761     PrintOpponents(f);
11762
11763     if (backwardMostMove > 0 || startedFromSetupPosition) {
11764         fprintf(f, "\n[--------------\n");
11765         PrintPosition(f, backwardMostMove);
11766         fprintf(f, "--------------]\n");
11767     } else {
11768         fprintf(f, "\n");
11769     }
11770
11771     i = backwardMostMove;
11772     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11773
11774     while (i < forwardMostMove) {
11775         if (commentList[i] != NULL) {
11776             fprintf(f, "[%s]\n", commentList[i]);
11777         }
11778
11779         if ((i % 2) == 1) {
11780             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11781             i++;
11782         } else {
11783             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11784             i++;
11785             if (commentList[i] != NULL) {
11786                 fprintf(f, "\n");
11787                 continue;
11788             }
11789             if (i >= forwardMostMove) {
11790                 fprintf(f, "\n");
11791                 break;
11792             }
11793             fprintf(f, "%s\n", parseList[i]);
11794             i++;
11795         }
11796     }
11797
11798     if (commentList[i] != NULL) {
11799         fprintf(f, "[%s]\n", commentList[i]);
11800     }
11801
11802     /* This isn't really the old style, but it's close enough */
11803     if (gameInfo.resultDetails != NULL &&
11804         gameInfo.resultDetails[0] != NULLCHAR) {
11805         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11806                 gameInfo.resultDetails);
11807     } else {
11808         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11809     }
11810
11811     fclose(f);
11812     return TRUE;
11813 }
11814
11815 /* Save the current game to open file f and close the file */
11816 int
11817 SaveGame(f, dummy, dummy2)
11818      FILE *f;
11819      int dummy;
11820      char *dummy2;
11821 {
11822     if (gameMode == EditPosition) EditPositionDone(TRUE);
11823     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11824     if (appData.oldSaveStyle)
11825       return SaveGameOldStyle(f);
11826     else
11827       return SaveGamePGN(f);
11828 }
11829
11830 /* Save the current position to the given file */
11831 int
11832 SavePositionToFile(filename)
11833      char *filename;
11834 {
11835     FILE *f;
11836     char buf[MSG_SIZ];
11837
11838     if (strcmp(filename, "-") == 0) {
11839         return SavePosition(stdout, 0, NULL);
11840     } else {
11841         f = fopen(filename, "a");
11842         if (f == NULL) {
11843             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11844             DisplayError(buf, errno);
11845             return FALSE;
11846         } else {
11847             safeStrCpy(buf, lastMsg, MSG_SIZ);
11848             DisplayMessage(_("Waiting for access to save file"), "");
11849             flock(fileno(f), LOCK_EX); // [HGM] lock
11850             DisplayMessage(_("Saving position"), "");
11851             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11852             SavePosition(f, 0, NULL);
11853             DisplayMessage(buf, "");
11854             return TRUE;
11855         }
11856     }
11857 }
11858
11859 /* Save the current position to the given open file and close the file */
11860 int
11861 SavePosition(f, dummy, dummy2)
11862      FILE *f;
11863      int dummy;
11864      char *dummy2;
11865 {
11866     time_t tm;
11867     char *fen;
11868
11869     if (gameMode == EditPosition) EditPositionDone(TRUE);
11870     if (appData.oldSaveStyle) {
11871         tm = time((time_t *) NULL);
11872
11873         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11874         PrintOpponents(f);
11875         fprintf(f, "[--------------\n");
11876         PrintPosition(f, currentMove);
11877         fprintf(f, "--------------]\n");
11878     } else {
11879         fen = PositionToFEN(currentMove, NULL);
11880         fprintf(f, "%s\n", fen);
11881         free(fen);
11882     }
11883     fclose(f);
11884     return TRUE;
11885 }
11886
11887 void
11888 ReloadCmailMsgEvent(unregister)
11889      int unregister;
11890 {
11891 #if !WIN32
11892     static char *inFilename = NULL;
11893     static char *outFilename;
11894     int i;
11895     struct stat inbuf, outbuf;
11896     int status;
11897
11898     /* Any registered moves are unregistered if unregister is set, */
11899     /* i.e. invoked by the signal handler */
11900     if (unregister) {
11901         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11902             cmailMoveRegistered[i] = FALSE;
11903             if (cmailCommentList[i] != NULL) {
11904                 free(cmailCommentList[i]);
11905                 cmailCommentList[i] = NULL;
11906             }
11907         }
11908         nCmailMovesRegistered = 0;
11909     }
11910
11911     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11912         cmailResult[i] = CMAIL_NOT_RESULT;
11913     }
11914     nCmailResults = 0;
11915
11916     if (inFilename == NULL) {
11917         /* Because the filenames are static they only get malloced once  */
11918         /* and they never get freed                                      */
11919         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11920         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11921
11922         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11923         sprintf(outFilename, "%s.out", appData.cmailGameName);
11924     }
11925
11926     status = stat(outFilename, &outbuf);
11927     if (status < 0) {
11928         cmailMailedMove = FALSE;
11929     } else {
11930         status = stat(inFilename, &inbuf);
11931         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11932     }
11933
11934     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11935        counts the games, notes how each one terminated, etc.
11936
11937        It would be nice to remove this kludge and instead gather all
11938        the information while building the game list.  (And to keep it
11939        in the game list nodes instead of having a bunch of fixed-size
11940        parallel arrays.)  Note this will require getting each game's
11941        termination from the PGN tags, as the game list builder does
11942        not process the game moves.  --mann
11943        */
11944     cmailMsgLoaded = TRUE;
11945     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11946
11947     /* Load first game in the file or popup game menu */
11948     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11949
11950 #endif /* !WIN32 */
11951     return;
11952 }
11953
11954 int
11955 RegisterMove()
11956 {
11957     FILE *f;
11958     char string[MSG_SIZ];
11959
11960     if (   cmailMailedMove
11961         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11962         return TRUE;            /* Allow free viewing  */
11963     }
11964
11965     /* Unregister move to ensure that we don't leave RegisterMove        */
11966     /* with the move registered when the conditions for registering no   */
11967     /* longer hold                                                       */
11968     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11969         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11970         nCmailMovesRegistered --;
11971
11972         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11973           {
11974               free(cmailCommentList[lastLoadGameNumber - 1]);
11975               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11976           }
11977     }
11978
11979     if (cmailOldMove == -1) {
11980         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11981         return FALSE;
11982     }
11983
11984     if (currentMove > cmailOldMove + 1) {
11985         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11986         return FALSE;
11987     }
11988
11989     if (currentMove < cmailOldMove) {
11990         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11991         return FALSE;
11992     }
11993
11994     if (forwardMostMove > currentMove) {
11995         /* Silently truncate extra moves */
11996         TruncateGame();
11997     }
11998
11999     if (   (currentMove == cmailOldMove + 1)
12000         || (   (currentMove == cmailOldMove)
12001             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12002                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12003         if (gameInfo.result != GameUnfinished) {
12004             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12005         }
12006
12007         if (commentList[currentMove] != NULL) {
12008             cmailCommentList[lastLoadGameNumber - 1]
12009               = StrSave(commentList[currentMove]);
12010         }
12011         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12012
12013         if (appData.debugMode)
12014           fprintf(debugFP, "Saving %s for game %d\n",
12015                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12016
12017         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12018
12019         f = fopen(string, "w");
12020         if (appData.oldSaveStyle) {
12021             SaveGameOldStyle(f); /* also closes the file */
12022
12023             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12024             f = fopen(string, "w");
12025             SavePosition(f, 0, NULL); /* also closes the file */
12026         } else {
12027             fprintf(f, "{--------------\n");
12028             PrintPosition(f, currentMove);
12029             fprintf(f, "--------------}\n\n");
12030
12031             SaveGame(f, 0, NULL); /* also closes the file*/
12032         }
12033
12034         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12035         nCmailMovesRegistered ++;
12036     } else if (nCmailGames == 1) {
12037         DisplayError(_("You have not made a move yet"), 0);
12038         return FALSE;
12039     }
12040
12041     return TRUE;
12042 }
12043
12044 void
12045 MailMoveEvent()
12046 {
12047 #if !WIN32
12048     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12049     FILE *commandOutput;
12050     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12051     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12052     int nBuffers;
12053     int i;
12054     int archived;
12055     char *arcDir;
12056
12057     if (! cmailMsgLoaded) {
12058         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12059         return;
12060     }
12061
12062     if (nCmailGames == nCmailResults) {
12063         DisplayError(_("No unfinished games"), 0);
12064         return;
12065     }
12066
12067 #if CMAIL_PROHIBIT_REMAIL
12068     if (cmailMailedMove) {
12069       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);
12070         DisplayError(msg, 0);
12071         return;
12072     }
12073 #endif
12074
12075     if (! (cmailMailedMove || RegisterMove())) return;
12076
12077     if (   cmailMailedMove
12078         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12079       snprintf(string, MSG_SIZ, partCommandString,
12080                appData.debugMode ? " -v" : "", appData.cmailGameName);
12081         commandOutput = popen(string, "r");
12082
12083         if (commandOutput == NULL) {
12084             DisplayError(_("Failed to invoke cmail"), 0);
12085         } else {
12086             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12087                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12088             }
12089             if (nBuffers > 1) {
12090                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12091                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12092                 nBytes = MSG_SIZ - 1;
12093             } else {
12094                 (void) memcpy(msg, buffer, nBytes);
12095             }
12096             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12097
12098             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12099                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12100
12101                 archived = TRUE;
12102                 for (i = 0; i < nCmailGames; i ++) {
12103                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12104                         archived = FALSE;
12105                     }
12106                 }
12107                 if (   archived
12108                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12109                         != NULL)) {
12110                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12111                            arcDir,
12112                            appData.cmailGameName,
12113                            gameInfo.date);
12114                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12115                     cmailMsgLoaded = FALSE;
12116                 }
12117             }
12118
12119             DisplayInformation(msg);
12120             pclose(commandOutput);
12121         }
12122     } else {
12123         if ((*cmailMsg) != '\0') {
12124             DisplayInformation(cmailMsg);
12125         }
12126     }
12127
12128     return;
12129 #endif /* !WIN32 */
12130 }
12131
12132 char *
12133 CmailMsg()
12134 {
12135 #if WIN32
12136     return NULL;
12137 #else
12138     int  prependComma = 0;
12139     char number[5];
12140     char string[MSG_SIZ];       /* Space for game-list */
12141     int  i;
12142
12143     if (!cmailMsgLoaded) return "";
12144
12145     if (cmailMailedMove) {
12146       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12147     } else {
12148         /* Create a list of games left */
12149       snprintf(string, MSG_SIZ, "[");
12150         for (i = 0; i < nCmailGames; i ++) {
12151             if (! (   cmailMoveRegistered[i]
12152                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12153                 if (prependComma) {
12154                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12155                 } else {
12156                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12157                     prependComma = 1;
12158                 }
12159
12160                 strcat(string, number);
12161             }
12162         }
12163         strcat(string, "]");
12164
12165         if (nCmailMovesRegistered + nCmailResults == 0) {
12166             switch (nCmailGames) {
12167               case 1:
12168                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12169                 break;
12170
12171               case 2:
12172                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12173                 break;
12174
12175               default:
12176                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12177                          nCmailGames);
12178                 break;
12179             }
12180         } else {
12181             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12182               case 1:
12183                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12184                          string);
12185                 break;
12186
12187               case 0:
12188                 if (nCmailResults == nCmailGames) {
12189                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12190                 } else {
12191                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12192                 }
12193                 break;
12194
12195               default:
12196                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12197                          string);
12198             }
12199         }
12200     }
12201     return cmailMsg;
12202 #endif /* WIN32 */
12203 }
12204
12205 void
12206 ResetGameEvent()
12207 {
12208     if (gameMode == Training)
12209       SetTrainingModeOff();
12210
12211     Reset(TRUE, TRUE);
12212     cmailMsgLoaded = FALSE;
12213     if (appData.icsActive) {
12214       SendToICS(ics_prefix);
12215       SendToICS("refresh\n");
12216     }
12217 }
12218
12219 void
12220 ExitEvent(status)
12221      int status;
12222 {
12223     exiting++;
12224     if (exiting > 2) {
12225       /* Give up on clean exit */
12226       exit(status);
12227     }
12228     if (exiting > 1) {
12229       /* Keep trying for clean exit */
12230       return;
12231     }
12232
12233     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12234
12235     if (telnetISR != NULL) {
12236       RemoveInputSource(telnetISR);
12237     }
12238     if (icsPR != NoProc) {
12239       DestroyChildProcess(icsPR, TRUE);
12240     }
12241
12242     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12243     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12244
12245     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12246     /* make sure this other one finishes before killing it!                  */
12247     if(endingGame) { int count = 0;
12248         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12249         while(endingGame && count++ < 10) DoSleep(1);
12250         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12251     }
12252
12253     /* Kill off chess programs */
12254     if (first.pr != NoProc) {
12255         ExitAnalyzeMode();
12256
12257         DoSleep( appData.delayBeforeQuit );
12258         SendToProgram("quit\n", &first);
12259         DoSleep( appData.delayAfterQuit );
12260         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12261     }
12262     if (second.pr != NoProc) {
12263         DoSleep( appData.delayBeforeQuit );
12264         SendToProgram("quit\n", &second);
12265         DoSleep( appData.delayAfterQuit );
12266         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12267     }
12268     if (first.isr != NULL) {
12269         RemoveInputSource(first.isr);
12270     }
12271     if (second.isr != NULL) {
12272         RemoveInputSource(second.isr);
12273     }
12274
12275     ShutDownFrontEnd();
12276     exit(status);
12277 }
12278
12279 void
12280 PauseEvent()
12281 {
12282     if (appData.debugMode)
12283         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12284     if (pausing) {
12285         pausing = FALSE;
12286         ModeHighlight();
12287         if (gameMode == MachinePlaysWhite ||
12288             gameMode == MachinePlaysBlack) {
12289             StartClocks();
12290         } else {
12291             DisplayBothClocks();
12292         }
12293         if (gameMode == PlayFromGameFile) {
12294             if (appData.timeDelay >= 0)
12295                 AutoPlayGameLoop();
12296         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12297             Reset(FALSE, TRUE);
12298             SendToICS(ics_prefix);
12299             SendToICS("refresh\n");
12300         } else if (currentMove < forwardMostMove) {
12301             ForwardInner(forwardMostMove);
12302         }
12303         pauseExamInvalid = FALSE;
12304     } else {
12305         switch (gameMode) {
12306           default:
12307             return;
12308           case IcsExamining:
12309             pauseExamForwardMostMove = forwardMostMove;
12310             pauseExamInvalid = FALSE;
12311             /* fall through */
12312           case IcsObserving:
12313           case IcsPlayingWhite:
12314           case IcsPlayingBlack:
12315             pausing = TRUE;
12316             ModeHighlight();
12317             return;
12318           case PlayFromGameFile:
12319             (void) StopLoadGameTimer();
12320             pausing = TRUE;
12321             ModeHighlight();
12322             break;
12323           case BeginningOfGame:
12324             if (appData.icsActive) return;
12325             /* else fall through */
12326           case MachinePlaysWhite:
12327           case MachinePlaysBlack:
12328           case TwoMachinesPlay:
12329             if (forwardMostMove == 0)
12330               return;           /* don't pause if no one has moved */
12331             if ((gameMode == MachinePlaysWhite &&
12332                  !WhiteOnMove(forwardMostMove)) ||
12333                 (gameMode == MachinePlaysBlack &&
12334                  WhiteOnMove(forwardMostMove))) {
12335                 StopClocks();
12336             }
12337             pausing = TRUE;
12338             ModeHighlight();
12339             break;
12340         }
12341     }
12342 }
12343
12344 void
12345 EditCommentEvent()
12346 {
12347     char title[MSG_SIZ];
12348
12349     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12350       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12351     } else {
12352       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12353                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12354                parseList[currentMove - 1]);
12355     }
12356
12357     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12358 }
12359
12360
12361 void
12362 EditTagsEvent()
12363 {
12364     char *tags = PGNTags(&gameInfo);
12365     bookUp = FALSE;
12366     EditTagsPopUp(tags, NULL);
12367     free(tags);
12368 }
12369
12370 void
12371 AnalyzeModeEvent()
12372 {
12373     if (appData.noChessProgram || gameMode == AnalyzeMode)
12374       return;
12375
12376     if (gameMode != AnalyzeFile) {
12377         if (!appData.icsEngineAnalyze) {
12378                EditGameEvent();
12379                if (gameMode != EditGame) return;
12380         }
12381         ResurrectChessProgram();
12382         SendToProgram("analyze\n", &first);
12383         first.analyzing = TRUE;
12384         /*first.maybeThinking = TRUE;*/
12385         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12386         EngineOutputPopUp();
12387     }
12388     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12389     pausing = FALSE;
12390     ModeHighlight();
12391     SetGameInfo();
12392
12393     StartAnalysisClock();
12394     GetTimeMark(&lastNodeCountTime);
12395     lastNodeCount = 0;
12396 }
12397
12398 void
12399 AnalyzeFileEvent()
12400 {
12401     if (appData.noChessProgram || gameMode == AnalyzeFile)
12402       return;
12403
12404     if (gameMode != AnalyzeMode) {
12405         EditGameEvent();
12406         if (gameMode != EditGame) return;
12407         ResurrectChessProgram();
12408         SendToProgram("analyze\n", &first);
12409         first.analyzing = TRUE;
12410         /*first.maybeThinking = TRUE;*/
12411         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12412         EngineOutputPopUp();
12413     }
12414     gameMode = AnalyzeFile;
12415     pausing = FALSE;
12416     ModeHighlight();
12417     SetGameInfo();
12418
12419     StartAnalysisClock();
12420     GetTimeMark(&lastNodeCountTime);
12421     lastNodeCount = 0;
12422 }
12423
12424 void
12425 MachineWhiteEvent()
12426 {
12427     char buf[MSG_SIZ];
12428     char *bookHit = NULL;
12429
12430     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12431       return;
12432
12433
12434     if (gameMode == PlayFromGameFile ||
12435         gameMode == TwoMachinesPlay  ||
12436         gameMode == Training         ||
12437         gameMode == AnalyzeMode      ||
12438         gameMode == EndOfGame)
12439         EditGameEvent();
12440
12441     if (gameMode == EditPosition)
12442         EditPositionDone(TRUE);
12443
12444     if (!WhiteOnMove(currentMove)) {
12445         DisplayError(_("It is not White's turn"), 0);
12446         return;
12447     }
12448
12449     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12450       ExitAnalyzeMode();
12451
12452     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12453         gameMode == AnalyzeFile)
12454         TruncateGame();
12455
12456     ResurrectChessProgram();    /* in case it isn't running */
12457     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12458         gameMode = MachinePlaysWhite;
12459         ResetClocks();
12460     } else
12461     gameMode = MachinePlaysWhite;
12462     pausing = FALSE;
12463     ModeHighlight();
12464     SetGameInfo();
12465     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12466     DisplayTitle(buf);
12467     if (first.sendName) {
12468       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12469       SendToProgram(buf, &first);
12470     }
12471     if (first.sendTime) {
12472       if (first.useColors) {
12473         SendToProgram("black\n", &first); /*gnu kludge*/
12474       }
12475       SendTimeRemaining(&first, TRUE);
12476     }
12477     if (first.useColors) {
12478       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12479     }
12480     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12481     SetMachineThinkingEnables();
12482     first.maybeThinking = TRUE;
12483     StartClocks();
12484     firstMove = FALSE;
12485
12486     if (appData.autoFlipView && !flipView) {
12487       flipView = !flipView;
12488       DrawPosition(FALSE, NULL);
12489       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12490     }
12491
12492     if(bookHit) { // [HGM] book: simulate book reply
12493         static char bookMove[MSG_SIZ]; // a bit generous?
12494
12495         programStats.nodes = programStats.depth = programStats.time =
12496         programStats.score = programStats.got_only_move = 0;
12497         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12498
12499         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12500         strcat(bookMove, bookHit);
12501         HandleMachineMove(bookMove, &first);
12502     }
12503 }
12504
12505 void
12506 MachineBlackEvent()
12507 {
12508   char buf[MSG_SIZ];
12509   char *bookHit = NULL;
12510
12511     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12512         return;
12513
12514
12515     if (gameMode == PlayFromGameFile ||
12516         gameMode == TwoMachinesPlay  ||
12517         gameMode == Training         ||
12518         gameMode == AnalyzeMode      ||
12519         gameMode == EndOfGame)
12520         EditGameEvent();
12521
12522     if (gameMode == EditPosition)
12523         EditPositionDone(TRUE);
12524
12525     if (WhiteOnMove(currentMove)) {
12526         DisplayError(_("It is not Black's turn"), 0);
12527         return;
12528     }
12529
12530     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12531       ExitAnalyzeMode();
12532
12533     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12534         gameMode == AnalyzeFile)
12535         TruncateGame();
12536
12537     ResurrectChessProgram();    /* in case it isn't running */
12538     gameMode = MachinePlaysBlack;
12539     pausing = FALSE;
12540     ModeHighlight();
12541     SetGameInfo();
12542     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12543     DisplayTitle(buf);
12544     if (first.sendName) {
12545       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12546       SendToProgram(buf, &first);
12547     }
12548     if (first.sendTime) {
12549       if (first.useColors) {
12550         SendToProgram("white\n", &first); /*gnu kludge*/
12551       }
12552       SendTimeRemaining(&first, FALSE);
12553     }
12554     if (first.useColors) {
12555       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12556     }
12557     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12558     SetMachineThinkingEnables();
12559     first.maybeThinking = TRUE;
12560     StartClocks();
12561
12562     if (appData.autoFlipView && flipView) {
12563       flipView = !flipView;
12564       DrawPosition(FALSE, NULL);
12565       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12566     }
12567     if(bookHit) { // [HGM] book: simulate book reply
12568         static char bookMove[MSG_SIZ]; // a bit generous?
12569
12570         programStats.nodes = programStats.depth = programStats.time =
12571         programStats.score = programStats.got_only_move = 0;
12572         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12573
12574         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12575         strcat(bookMove, bookHit);
12576         HandleMachineMove(bookMove, &first);
12577     }
12578 }
12579
12580
12581 void
12582 DisplayTwoMachinesTitle()
12583 {
12584     char buf[MSG_SIZ];
12585     if (appData.matchGames > 0) {
12586         if (first.twoMachinesColor[0] == 'w') {
12587           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12588                    gameInfo.white, gameInfo.black,
12589                    first.matchWins, second.matchWins,
12590                    matchGame - 1 - (first.matchWins + second.matchWins));
12591         } else {
12592           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12593                    gameInfo.white, gameInfo.black,
12594                    second.matchWins, first.matchWins,
12595                    matchGame - 1 - (first.matchWins + second.matchWins));
12596         }
12597     } else {
12598       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12599     }
12600     DisplayTitle(buf);
12601 }
12602
12603 void
12604 SettingsMenuIfReady()
12605 {
12606   if (second.lastPing != second.lastPong) {
12607     DisplayMessage("", _("Waiting for second chess program"));
12608     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12609     return;
12610   }
12611   ThawUI();
12612   DisplayMessage("", "");
12613   SettingsPopUp(&second);
12614 }
12615
12616 int
12617 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12618 {
12619     char buf[MSG_SIZ];
12620     if (cps->pr == NULL) {
12621         StartChessProgram(cps);
12622         if (cps->protocolVersion == 1) {
12623           retry();
12624         } else {
12625           /* kludge: allow timeout for initial "feature" command */
12626           FreezeUI();
12627           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12628           DisplayMessage("", buf);
12629           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12630         }
12631         return 1;
12632     }
12633     return 0;
12634 }
12635
12636 void
12637 TwoMachinesEvent P((void))
12638 {
12639     int i;
12640     char buf[MSG_SIZ];
12641     ChessProgramState *onmove;
12642     char *bookHit = NULL;
12643     static int stalling = 0;
12644     TimeMark now;
12645     long wait;
12646
12647     if (appData.noChessProgram) return;
12648
12649     switch (gameMode) {
12650       case TwoMachinesPlay:
12651         return;
12652       case MachinePlaysWhite:
12653       case MachinePlaysBlack:
12654         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12655             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12656             return;
12657         }
12658         /* fall through */
12659       case BeginningOfGame:
12660       case PlayFromGameFile:
12661       case EndOfGame:
12662         EditGameEvent();
12663         if (gameMode != EditGame) return;
12664         break;
12665       case EditPosition:
12666         EditPositionDone(TRUE);
12667         break;
12668       case AnalyzeMode:
12669       case AnalyzeFile:
12670         ExitAnalyzeMode();
12671         break;
12672       case EditGame:
12673       default:
12674         break;
12675     }
12676
12677 //    forwardMostMove = currentMove;
12678     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12679
12680     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12681
12682     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12683     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12684       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12685       return;
12686     }
12687     if(!stalling) {
12688       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12689       SendToProgram("force\n", &second);
12690       stalling = 1;
12691       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12692       return;
12693     }
12694     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12695     if(appData.matchPause>10000 || appData.matchPause<10)
12696                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12697     wait = SubtractTimeMarks(&now, &pauseStart);
12698     if(wait < appData.matchPause) {
12699         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12700         return;
12701     }
12702     stalling = 0;
12703     DisplayMessage("", "");
12704     if (startedFromSetupPosition) {
12705         SendBoard(&second, backwardMostMove);
12706     if (appData.debugMode) {
12707         fprintf(debugFP, "Two Machines\n");
12708     }
12709     }
12710     for (i = backwardMostMove; i < forwardMostMove; i++) {
12711         SendMoveToProgram(i, &second);
12712     }
12713
12714     gameMode = TwoMachinesPlay;
12715     pausing = FALSE;
12716     ModeHighlight();
12717     SetGameInfo();
12718     DisplayTwoMachinesTitle();
12719     firstMove = TRUE;
12720     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12721         onmove = &first;
12722     } else {
12723         onmove = &second;
12724     }
12725     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12726     SendToProgram(first.computerString, &first);
12727     if (first.sendName) {
12728       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12729       SendToProgram(buf, &first);
12730     }
12731     SendToProgram(second.computerString, &second);
12732     if (second.sendName) {
12733       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12734       SendToProgram(buf, &second);
12735     }
12736
12737     ResetClocks();
12738     if (!first.sendTime || !second.sendTime) {
12739         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12740         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12741     }
12742     if (onmove->sendTime) {
12743       if (onmove->useColors) {
12744         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12745       }
12746       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12747     }
12748     if (onmove->useColors) {
12749       SendToProgram(onmove->twoMachinesColor, onmove);
12750     }
12751     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12752 //    SendToProgram("go\n", onmove);
12753     onmove->maybeThinking = TRUE;
12754     SetMachineThinkingEnables();
12755
12756     StartClocks();
12757
12758     if(bookHit) { // [HGM] book: simulate book reply
12759         static char bookMove[MSG_SIZ]; // a bit generous?
12760
12761         programStats.nodes = programStats.depth = programStats.time =
12762         programStats.score = programStats.got_only_move = 0;
12763         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12764
12765         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12766         strcat(bookMove, bookHit);
12767         savedMessage = bookMove; // args for deferred call
12768         savedState = onmove;
12769         ScheduleDelayedEvent(DeferredBookMove, 1);
12770     }
12771 }
12772
12773 void
12774 TrainingEvent()
12775 {
12776     if (gameMode == Training) {
12777       SetTrainingModeOff();
12778       gameMode = PlayFromGameFile;
12779       DisplayMessage("", _("Training mode off"));
12780     } else {
12781       gameMode = Training;
12782       animateTraining = appData.animate;
12783
12784       /* make sure we are not already at the end of the game */
12785       if (currentMove < forwardMostMove) {
12786         SetTrainingModeOn();
12787         DisplayMessage("", _("Training mode on"));
12788       } else {
12789         gameMode = PlayFromGameFile;
12790         DisplayError(_("Already at end of game"), 0);
12791       }
12792     }
12793     ModeHighlight();
12794 }
12795
12796 void
12797 IcsClientEvent()
12798 {
12799     if (!appData.icsActive) return;
12800     switch (gameMode) {
12801       case IcsPlayingWhite:
12802       case IcsPlayingBlack:
12803       case IcsObserving:
12804       case IcsIdle:
12805       case BeginningOfGame:
12806       case IcsExamining:
12807         return;
12808
12809       case EditGame:
12810         break;
12811
12812       case EditPosition:
12813         EditPositionDone(TRUE);
12814         break;
12815
12816       case AnalyzeMode:
12817       case AnalyzeFile:
12818         ExitAnalyzeMode();
12819         break;
12820
12821       default:
12822         EditGameEvent();
12823         break;
12824     }
12825
12826     gameMode = IcsIdle;
12827     ModeHighlight();
12828     return;
12829 }
12830
12831
12832 void
12833 EditGameEvent()
12834 {
12835     int i;
12836
12837     switch (gameMode) {
12838       case Training:
12839         SetTrainingModeOff();
12840         break;
12841       case MachinePlaysWhite:
12842       case MachinePlaysBlack:
12843       case BeginningOfGame:
12844         SendToProgram("force\n", &first);
12845         SetUserThinkingEnables();
12846         break;
12847       case PlayFromGameFile:
12848         (void) StopLoadGameTimer();
12849         if (gameFileFP != NULL) {
12850             gameFileFP = NULL;
12851         }
12852         break;
12853       case EditPosition:
12854         EditPositionDone(TRUE);
12855         break;
12856       case AnalyzeMode:
12857       case AnalyzeFile:
12858         ExitAnalyzeMode();
12859         SendToProgram("force\n", &first);
12860         break;
12861       case TwoMachinesPlay:
12862         GameEnds(EndOfFile, NULL, GE_PLAYER);
12863         ResurrectChessProgram();
12864         SetUserThinkingEnables();
12865         break;
12866       case EndOfGame:
12867         ResurrectChessProgram();
12868         break;
12869       case IcsPlayingBlack:
12870       case IcsPlayingWhite:
12871         DisplayError(_("Warning: You are still playing a game"), 0);
12872         break;
12873       case IcsObserving:
12874         DisplayError(_("Warning: You are still observing a game"), 0);
12875         break;
12876       case IcsExamining:
12877         DisplayError(_("Warning: You are still examining a game"), 0);
12878         break;
12879       case IcsIdle:
12880         break;
12881       case EditGame:
12882       default:
12883         return;
12884     }
12885
12886     pausing = FALSE;
12887     StopClocks();
12888     first.offeredDraw = second.offeredDraw = 0;
12889
12890     if (gameMode == PlayFromGameFile) {
12891         whiteTimeRemaining = timeRemaining[0][currentMove];
12892         blackTimeRemaining = timeRemaining[1][currentMove];
12893         DisplayTitle("");
12894     }
12895
12896     if (gameMode == MachinePlaysWhite ||
12897         gameMode == MachinePlaysBlack ||
12898         gameMode == TwoMachinesPlay ||
12899         gameMode == EndOfGame) {
12900         i = forwardMostMove;
12901         while (i > currentMove) {
12902             SendToProgram("undo\n", &first);
12903             i--;
12904         }
12905         whiteTimeRemaining = timeRemaining[0][currentMove];
12906         blackTimeRemaining = timeRemaining[1][currentMove];
12907         DisplayBothClocks();
12908         if (whiteFlag || blackFlag) {
12909             whiteFlag = blackFlag = 0;
12910         }
12911         DisplayTitle("");
12912     }
12913
12914     gameMode = EditGame;
12915     ModeHighlight();
12916     SetGameInfo();
12917 }
12918
12919
12920 void
12921 EditPositionEvent()
12922 {
12923     if (gameMode == EditPosition) {
12924         EditGameEvent();
12925         return;
12926     }
12927
12928     EditGameEvent();
12929     if (gameMode != EditGame) return;
12930
12931     gameMode = EditPosition;
12932     ModeHighlight();
12933     SetGameInfo();
12934     if (currentMove > 0)
12935       CopyBoard(boards[0], boards[currentMove]);
12936
12937     blackPlaysFirst = !WhiteOnMove(currentMove);
12938     ResetClocks();
12939     currentMove = forwardMostMove = backwardMostMove = 0;
12940     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12941     DisplayMove(-1);
12942 }
12943
12944 void
12945 ExitAnalyzeMode()
12946 {
12947     /* [DM] icsEngineAnalyze - possible call from other functions */
12948     if (appData.icsEngineAnalyze) {
12949         appData.icsEngineAnalyze = FALSE;
12950
12951         DisplayMessage("",_("Close ICS engine analyze..."));
12952     }
12953     if (first.analysisSupport && first.analyzing) {
12954       SendToProgram("exit\n", &first);
12955       first.analyzing = FALSE;
12956     }
12957     thinkOutput[0] = NULLCHAR;
12958 }
12959
12960 void
12961 EditPositionDone(Boolean fakeRights)
12962 {
12963     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12964
12965     startedFromSetupPosition = TRUE;
12966     InitChessProgram(&first, FALSE);
12967     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12968       boards[0][EP_STATUS] = EP_NONE;
12969       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12970     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12971         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12972         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12973       } else boards[0][CASTLING][2] = NoRights;
12974     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12975         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12976         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12977       } else boards[0][CASTLING][5] = NoRights;
12978     }
12979     SendToProgram("force\n", &first);
12980     if (blackPlaysFirst) {
12981         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12982         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12983         currentMove = forwardMostMove = backwardMostMove = 1;
12984         CopyBoard(boards[1], boards[0]);
12985     } else {
12986         currentMove = forwardMostMove = backwardMostMove = 0;
12987     }
12988     SendBoard(&first, forwardMostMove);
12989     if (appData.debugMode) {
12990         fprintf(debugFP, "EditPosDone\n");
12991     }
12992     DisplayTitle("");
12993     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12994     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12995     gameMode = EditGame;
12996     ModeHighlight();
12997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12998     ClearHighlights(); /* [AS] */
12999 }
13000
13001 /* Pause for `ms' milliseconds */
13002 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13003 void
13004 TimeDelay(ms)
13005      long ms;
13006 {
13007     TimeMark m1, m2;
13008
13009     GetTimeMark(&m1);
13010     do {
13011         GetTimeMark(&m2);
13012     } while (SubtractTimeMarks(&m2, &m1) < ms);
13013 }
13014
13015 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13016 void
13017 SendMultiLineToICS(buf)
13018      char *buf;
13019 {
13020     char temp[MSG_SIZ+1], *p;
13021     int len;
13022
13023     len = strlen(buf);
13024     if (len > MSG_SIZ)
13025       len = MSG_SIZ;
13026
13027     strncpy(temp, buf, len);
13028     temp[len] = 0;
13029
13030     p = temp;
13031     while (*p) {
13032         if (*p == '\n' || *p == '\r')
13033           *p = ' ';
13034         ++p;
13035     }
13036
13037     strcat(temp, "\n");
13038     SendToICS(temp);
13039     SendToPlayer(temp, strlen(temp));
13040 }
13041
13042 void
13043 SetWhiteToPlayEvent()
13044 {
13045     if (gameMode == EditPosition) {
13046         blackPlaysFirst = FALSE;
13047         DisplayBothClocks();    /* works because currentMove is 0 */
13048     } else if (gameMode == IcsExamining) {
13049         SendToICS(ics_prefix);
13050         SendToICS("tomove white\n");
13051     }
13052 }
13053
13054 void
13055 SetBlackToPlayEvent()
13056 {
13057     if (gameMode == EditPosition) {
13058         blackPlaysFirst = TRUE;
13059         currentMove = 1;        /* kludge */
13060         DisplayBothClocks();
13061         currentMove = 0;
13062     } else if (gameMode == IcsExamining) {
13063         SendToICS(ics_prefix);
13064         SendToICS("tomove black\n");
13065     }
13066 }
13067
13068 void
13069 EditPositionMenuEvent(selection, x, y)
13070      ChessSquare selection;
13071      int x, y;
13072 {
13073     char buf[MSG_SIZ];
13074     ChessSquare piece = boards[0][y][x];
13075
13076     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13077
13078     switch (selection) {
13079       case ClearBoard:
13080         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13081             SendToICS(ics_prefix);
13082             SendToICS("bsetup clear\n");
13083         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13084             SendToICS(ics_prefix);
13085             SendToICS("clearboard\n");
13086         } else {
13087             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13088                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13089                 for (y = 0; y < BOARD_HEIGHT; y++) {
13090                     if (gameMode == IcsExamining) {
13091                         if (boards[currentMove][y][x] != EmptySquare) {
13092                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13093                                     AAA + x, ONE + y);
13094                             SendToICS(buf);
13095                         }
13096                     } else {
13097                         boards[0][y][x] = p;
13098                     }
13099                 }
13100             }
13101         }
13102         if (gameMode == EditPosition) {
13103             DrawPosition(FALSE, boards[0]);
13104         }
13105         break;
13106
13107       case WhitePlay:
13108         SetWhiteToPlayEvent();
13109         break;
13110
13111       case BlackPlay:
13112         SetBlackToPlayEvent();
13113         break;
13114
13115       case EmptySquare:
13116         if (gameMode == IcsExamining) {
13117             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13118             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13119             SendToICS(buf);
13120         } else {
13121             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13122                 if(x == BOARD_LEFT-2) {
13123                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13124                     boards[0][y][1] = 0;
13125                 } else
13126                 if(x == BOARD_RGHT+1) {
13127                     if(y >= gameInfo.holdingsSize) break;
13128                     boards[0][y][BOARD_WIDTH-2] = 0;
13129                 } else break;
13130             }
13131             boards[0][y][x] = EmptySquare;
13132             DrawPosition(FALSE, boards[0]);
13133         }
13134         break;
13135
13136       case PromotePiece:
13137         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13138            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13139             selection = (ChessSquare) (PROMOTED piece);
13140         } else if(piece == EmptySquare) selection = WhiteSilver;
13141         else selection = (ChessSquare)((int)piece - 1);
13142         goto defaultlabel;
13143
13144       case DemotePiece:
13145         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13146            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13147             selection = (ChessSquare) (DEMOTED piece);
13148         } else if(piece == EmptySquare) selection = BlackSilver;
13149         else selection = (ChessSquare)((int)piece + 1);
13150         goto defaultlabel;
13151
13152       case WhiteQueen:
13153       case BlackQueen:
13154         if(gameInfo.variant == VariantShatranj ||
13155            gameInfo.variant == VariantXiangqi  ||
13156            gameInfo.variant == VariantCourier  ||
13157            gameInfo.variant == VariantMakruk     )
13158             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13159         goto defaultlabel;
13160
13161       case WhiteKing:
13162       case BlackKing:
13163         if(gameInfo.variant == VariantXiangqi)
13164             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13165         if(gameInfo.variant == VariantKnightmate)
13166             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13167       default:
13168         defaultlabel:
13169         if (gameMode == IcsExamining) {
13170             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13171             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13172                      PieceToChar(selection), AAA + x, ONE + y);
13173             SendToICS(buf);
13174         } else {
13175             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13176                 int n;
13177                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13178                     n = PieceToNumber(selection - BlackPawn);
13179                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13180                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13181                     boards[0][BOARD_HEIGHT-1-n][1]++;
13182                 } else
13183                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13184                     n = PieceToNumber(selection);
13185                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13186                     boards[0][n][BOARD_WIDTH-1] = selection;
13187                     boards[0][n][BOARD_WIDTH-2]++;
13188                 }
13189             } else
13190             boards[0][y][x] = selection;
13191             DrawPosition(TRUE, boards[0]);
13192         }
13193         break;
13194     }
13195 }
13196
13197
13198 void
13199 DropMenuEvent(selection, x, y)
13200      ChessSquare selection;
13201      int x, y;
13202 {
13203     ChessMove moveType;
13204
13205     switch (gameMode) {
13206       case IcsPlayingWhite:
13207       case MachinePlaysBlack:
13208         if (!WhiteOnMove(currentMove)) {
13209             DisplayMoveError(_("It is Black's turn"));
13210             return;
13211         }
13212         moveType = WhiteDrop;
13213         break;
13214       case IcsPlayingBlack:
13215       case MachinePlaysWhite:
13216         if (WhiteOnMove(currentMove)) {
13217             DisplayMoveError(_("It is White's turn"));
13218             return;
13219         }
13220         moveType = BlackDrop;
13221         break;
13222       case EditGame:
13223         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13224         break;
13225       default:
13226         return;
13227     }
13228
13229     if (moveType == BlackDrop && selection < BlackPawn) {
13230       selection = (ChessSquare) ((int) selection
13231                                  + (int) BlackPawn - (int) WhitePawn);
13232     }
13233     if (boards[currentMove][y][x] != EmptySquare) {
13234         DisplayMoveError(_("That square is occupied"));
13235         return;
13236     }
13237
13238     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13239 }
13240
13241 void
13242 AcceptEvent()
13243 {
13244     /* Accept a pending offer of any kind from opponent */
13245
13246     if (appData.icsActive) {
13247         SendToICS(ics_prefix);
13248         SendToICS("accept\n");
13249     } else if (cmailMsgLoaded) {
13250         if (currentMove == cmailOldMove &&
13251             commentList[cmailOldMove] != NULL &&
13252             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13253                    "Black offers a draw" : "White offers a draw")) {
13254             TruncateGame();
13255             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13256             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13257         } else {
13258             DisplayError(_("There is no pending offer on this move"), 0);
13259             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13260         }
13261     } else {
13262         /* Not used for offers from chess program */
13263     }
13264 }
13265
13266 void
13267 DeclineEvent()
13268 {
13269     /* Decline a pending offer of any kind from opponent */
13270
13271     if (appData.icsActive) {
13272         SendToICS(ics_prefix);
13273         SendToICS("decline\n");
13274     } else if (cmailMsgLoaded) {
13275         if (currentMove == cmailOldMove &&
13276             commentList[cmailOldMove] != NULL &&
13277             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13278                    "Black offers a draw" : "White offers a draw")) {
13279 #ifdef NOTDEF
13280             AppendComment(cmailOldMove, "Draw declined", TRUE);
13281             DisplayComment(cmailOldMove - 1, "Draw declined");
13282 #endif /*NOTDEF*/
13283         } else {
13284             DisplayError(_("There is no pending offer on this move"), 0);
13285         }
13286     } else {
13287         /* Not used for offers from chess program */
13288     }
13289 }
13290
13291 void
13292 RematchEvent()
13293 {
13294     /* Issue ICS rematch command */
13295     if (appData.icsActive) {
13296         SendToICS(ics_prefix);
13297         SendToICS("rematch\n");
13298     }
13299 }
13300
13301 void
13302 CallFlagEvent()
13303 {
13304     /* Call your opponent's flag (claim a win on time) */
13305     if (appData.icsActive) {
13306         SendToICS(ics_prefix);
13307         SendToICS("flag\n");
13308     } else {
13309         switch (gameMode) {
13310           default:
13311             return;
13312           case MachinePlaysWhite:
13313             if (whiteFlag) {
13314                 if (blackFlag)
13315                   GameEnds(GameIsDrawn, "Both players ran out of time",
13316                            GE_PLAYER);
13317                 else
13318                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13319             } else {
13320                 DisplayError(_("Your opponent is not out of time"), 0);
13321             }
13322             break;
13323           case MachinePlaysBlack:
13324             if (blackFlag) {
13325                 if (whiteFlag)
13326                   GameEnds(GameIsDrawn, "Both players ran out of time",
13327                            GE_PLAYER);
13328                 else
13329                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13330             } else {
13331                 DisplayError(_("Your opponent is not out of time"), 0);
13332             }
13333             break;
13334         }
13335     }
13336 }
13337
13338 void
13339 ClockClick(int which)
13340 {       // [HGM] code moved to back-end from winboard.c
13341         if(which) { // black clock
13342           if (gameMode == EditPosition || gameMode == IcsExamining) {
13343             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13344             SetBlackToPlayEvent();
13345           } else if (gameMode == EditGame || shiftKey) {
13346             AdjustClock(which, -1);
13347           } else if (gameMode == IcsPlayingWhite ||
13348                      gameMode == MachinePlaysBlack) {
13349             CallFlagEvent();
13350           }
13351         } else { // white clock
13352           if (gameMode == EditPosition || gameMode == IcsExamining) {
13353             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13354             SetWhiteToPlayEvent();
13355           } else if (gameMode == EditGame || shiftKey) {
13356             AdjustClock(which, -1);
13357           } else if (gameMode == IcsPlayingBlack ||
13358                    gameMode == MachinePlaysWhite) {
13359             CallFlagEvent();
13360           }
13361         }
13362 }
13363
13364 void
13365 DrawEvent()
13366 {
13367     /* Offer draw or accept pending draw offer from opponent */
13368
13369     if (appData.icsActive) {
13370         /* Note: tournament rules require draw offers to be
13371            made after you make your move but before you punch
13372            your clock.  Currently ICS doesn't let you do that;
13373            instead, you immediately punch your clock after making
13374            a move, but you can offer a draw at any time. */
13375
13376         SendToICS(ics_prefix);
13377         SendToICS("draw\n");
13378         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13379     } else if (cmailMsgLoaded) {
13380         if (currentMove == cmailOldMove &&
13381             commentList[cmailOldMove] != NULL &&
13382             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13383                    "Black offers a draw" : "White offers a draw")) {
13384             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13386         } else if (currentMove == cmailOldMove + 1) {
13387             char *offer = WhiteOnMove(cmailOldMove) ?
13388               "White offers a draw" : "Black offers a draw";
13389             AppendComment(currentMove, offer, TRUE);
13390             DisplayComment(currentMove - 1, offer);
13391             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13392         } else {
13393             DisplayError(_("You must make your move before offering a draw"), 0);
13394             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13395         }
13396     } else if (first.offeredDraw) {
13397         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13398     } else {
13399         if (first.sendDrawOffers) {
13400             SendToProgram("draw\n", &first);
13401             userOfferedDraw = TRUE;
13402         }
13403     }
13404 }
13405
13406 void
13407 AdjournEvent()
13408 {
13409     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13410
13411     if (appData.icsActive) {
13412         SendToICS(ics_prefix);
13413         SendToICS("adjourn\n");
13414     } else {
13415         /* Currently GNU Chess doesn't offer or accept Adjourns */
13416     }
13417 }
13418
13419
13420 void
13421 AbortEvent()
13422 {
13423     /* Offer Abort or accept pending Abort offer from opponent */
13424
13425     if (appData.icsActive) {
13426         SendToICS(ics_prefix);
13427         SendToICS("abort\n");
13428     } else {
13429         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13430     }
13431 }
13432
13433 void
13434 ResignEvent()
13435 {
13436     /* Resign.  You can do this even if it's not your turn. */
13437
13438     if (appData.icsActive) {
13439         SendToICS(ics_prefix);
13440         SendToICS("resign\n");
13441     } else {
13442         switch (gameMode) {
13443           case MachinePlaysWhite:
13444             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13445             break;
13446           case MachinePlaysBlack:
13447             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13448             break;
13449           case EditGame:
13450             if (cmailMsgLoaded) {
13451                 TruncateGame();
13452                 if (WhiteOnMove(cmailOldMove)) {
13453                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13454                 } else {
13455                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13456                 }
13457                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13458             }
13459             break;
13460           default:
13461             break;
13462         }
13463     }
13464 }
13465
13466
13467 void
13468 StopObservingEvent()
13469 {
13470     /* Stop observing current games */
13471     SendToICS(ics_prefix);
13472     SendToICS("unobserve\n");
13473 }
13474
13475 void
13476 StopExaminingEvent()
13477 {
13478     /* Stop observing current game */
13479     SendToICS(ics_prefix);
13480     SendToICS("unexamine\n");
13481 }
13482
13483 void
13484 ForwardInner(target)
13485      int target;
13486 {
13487     int limit;
13488
13489     if (appData.debugMode)
13490         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13491                 target, currentMove, forwardMostMove);
13492
13493     if (gameMode == EditPosition)
13494       return;
13495
13496     if (gameMode == PlayFromGameFile && !pausing)
13497       PauseEvent();
13498
13499     if (gameMode == IcsExamining && pausing)
13500       limit = pauseExamForwardMostMove;
13501     else
13502       limit = forwardMostMove;
13503
13504     if (target > limit) target = limit;
13505
13506     if (target > 0 && moveList[target - 1][0]) {
13507         int fromX, fromY, toX, toY;
13508         toX = moveList[target - 1][2] - AAA;
13509         toY = moveList[target - 1][3] - ONE;
13510         if (moveList[target - 1][1] == '@') {
13511             if (appData.highlightLastMove) {
13512                 SetHighlights(-1, -1, toX, toY);
13513             }
13514         } else {
13515             fromX = moveList[target - 1][0] - AAA;
13516             fromY = moveList[target - 1][1] - ONE;
13517             if (target == currentMove + 1) {
13518                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13519             }
13520             if (appData.highlightLastMove) {
13521                 SetHighlights(fromX, fromY, toX, toY);
13522             }
13523         }
13524     }
13525     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13526         gameMode == Training || gameMode == PlayFromGameFile ||
13527         gameMode == AnalyzeFile) {
13528         while (currentMove < target) {
13529             SendMoveToProgram(currentMove++, &first);
13530         }
13531     } else {
13532         currentMove = target;
13533     }
13534
13535     if (gameMode == EditGame || gameMode == EndOfGame) {
13536         whiteTimeRemaining = timeRemaining[0][currentMove];
13537         blackTimeRemaining = timeRemaining[1][currentMove];
13538     }
13539     DisplayBothClocks();
13540     DisplayMove(currentMove - 1);
13541     DrawPosition(FALSE, boards[currentMove]);
13542     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13543     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13544         DisplayComment(currentMove - 1, commentList[currentMove]);
13545     }
13546     DisplayBook(currentMove);
13547 }
13548
13549
13550 void
13551 ForwardEvent()
13552 {
13553     if (gameMode == IcsExamining && !pausing) {
13554         SendToICS(ics_prefix);
13555         SendToICS("forward\n");
13556     } else {
13557         ForwardInner(currentMove + 1);
13558     }
13559 }
13560
13561 void
13562 ToEndEvent()
13563 {
13564     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13565         /* to optimze, we temporarily turn off analysis mode while we feed
13566          * the remaining moves to the engine. Otherwise we get analysis output
13567          * after each move.
13568          */
13569         if (first.analysisSupport) {
13570           SendToProgram("exit\nforce\n", &first);
13571           first.analyzing = FALSE;
13572         }
13573     }
13574
13575     if (gameMode == IcsExamining && !pausing) {
13576         SendToICS(ics_prefix);
13577         SendToICS("forward 999999\n");
13578     } else {
13579         ForwardInner(forwardMostMove);
13580     }
13581
13582     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13583         /* we have fed all the moves, so reactivate analysis mode */
13584         SendToProgram("analyze\n", &first);
13585         first.analyzing = TRUE;
13586         /*first.maybeThinking = TRUE;*/
13587         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13588     }
13589 }
13590
13591 void
13592 BackwardInner(target)
13593      int target;
13594 {
13595     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13596
13597     if (appData.debugMode)
13598         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13599                 target, currentMove, forwardMostMove);
13600
13601     if (gameMode == EditPosition) return;
13602     if (currentMove <= backwardMostMove) {
13603         ClearHighlights();
13604         DrawPosition(full_redraw, boards[currentMove]);
13605         return;
13606     }
13607     if (gameMode == PlayFromGameFile && !pausing)
13608       PauseEvent();
13609
13610     if (moveList[target][0]) {
13611         int fromX, fromY, toX, toY;
13612         toX = moveList[target][2] - AAA;
13613         toY = moveList[target][3] - ONE;
13614         if (moveList[target][1] == '@') {
13615             if (appData.highlightLastMove) {
13616                 SetHighlights(-1, -1, toX, toY);
13617             }
13618         } else {
13619             fromX = moveList[target][0] - AAA;
13620             fromY = moveList[target][1] - ONE;
13621             if (target == currentMove - 1) {
13622                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13623             }
13624             if (appData.highlightLastMove) {
13625                 SetHighlights(fromX, fromY, toX, toY);
13626             }
13627         }
13628     }
13629     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13630         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13631         while (currentMove > target) {
13632             SendToProgram("undo\n", &first);
13633             currentMove--;
13634         }
13635     } else {
13636         currentMove = target;
13637     }
13638
13639     if (gameMode == EditGame || gameMode == EndOfGame) {
13640         whiteTimeRemaining = timeRemaining[0][currentMove];
13641         blackTimeRemaining = timeRemaining[1][currentMove];
13642     }
13643     DisplayBothClocks();
13644     DisplayMove(currentMove - 1);
13645     DrawPosition(full_redraw, boards[currentMove]);
13646     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13647     // [HGM] PV info: routine tests if comment empty
13648     DisplayComment(currentMove - 1, commentList[currentMove]);
13649     DisplayBook(currentMove);
13650 }
13651
13652 void
13653 BackwardEvent()
13654 {
13655     if (gameMode == IcsExamining && !pausing) {
13656         SendToICS(ics_prefix);
13657         SendToICS("backward\n");
13658     } else {
13659         BackwardInner(currentMove - 1);
13660     }
13661 }
13662
13663 void
13664 ToStartEvent()
13665 {
13666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13667         /* to optimize, we temporarily turn off analysis mode while we undo
13668          * all the moves. Otherwise we get analysis output after each undo.
13669          */
13670         if (first.analysisSupport) {
13671           SendToProgram("exit\nforce\n", &first);
13672           first.analyzing = FALSE;
13673         }
13674     }
13675
13676     if (gameMode == IcsExamining && !pausing) {
13677         SendToICS(ics_prefix);
13678         SendToICS("backward 999999\n");
13679     } else {
13680         BackwardInner(backwardMostMove);
13681     }
13682
13683     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13684         /* we have fed all the moves, so reactivate analysis mode */
13685         SendToProgram("analyze\n", &first);
13686         first.analyzing = TRUE;
13687         /*first.maybeThinking = TRUE;*/
13688         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13689     }
13690 }
13691
13692 void
13693 ToNrEvent(int to)
13694 {
13695   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13696   if (to >= forwardMostMove) to = forwardMostMove;
13697   if (to <= backwardMostMove) to = backwardMostMove;
13698   if (to < currentMove) {
13699     BackwardInner(to);
13700   } else {
13701     ForwardInner(to);
13702   }
13703 }
13704
13705 void
13706 RevertEvent(Boolean annotate)
13707 {
13708     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13709         return;
13710     }
13711     if (gameMode != IcsExamining) {
13712         DisplayError(_("You are not examining a game"), 0);
13713         return;
13714     }
13715     if (pausing) {
13716         DisplayError(_("You can't revert while pausing"), 0);
13717         return;
13718     }
13719     SendToICS(ics_prefix);
13720     SendToICS("revert\n");
13721 }
13722
13723 void
13724 RetractMoveEvent()
13725 {
13726     switch (gameMode) {
13727       case MachinePlaysWhite:
13728       case MachinePlaysBlack:
13729         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13730             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13731             return;
13732         }
13733         if (forwardMostMove < 2) return;
13734         currentMove = forwardMostMove = forwardMostMove - 2;
13735         whiteTimeRemaining = timeRemaining[0][currentMove];
13736         blackTimeRemaining = timeRemaining[1][currentMove];
13737         DisplayBothClocks();
13738         DisplayMove(currentMove - 1);
13739         ClearHighlights();/*!! could figure this out*/
13740         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13741         SendToProgram("remove\n", &first);
13742         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13743         break;
13744
13745       case BeginningOfGame:
13746       default:
13747         break;
13748
13749       case IcsPlayingWhite:
13750       case IcsPlayingBlack:
13751         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13752             SendToICS(ics_prefix);
13753             SendToICS("takeback 2\n");
13754         } else {
13755             SendToICS(ics_prefix);
13756             SendToICS("takeback 1\n");
13757         }
13758         break;
13759     }
13760 }
13761
13762 void
13763 MoveNowEvent()
13764 {
13765     ChessProgramState *cps;
13766
13767     switch (gameMode) {
13768       case MachinePlaysWhite:
13769         if (!WhiteOnMove(forwardMostMove)) {
13770             DisplayError(_("It is your turn"), 0);
13771             return;
13772         }
13773         cps = &first;
13774         break;
13775       case MachinePlaysBlack:
13776         if (WhiteOnMove(forwardMostMove)) {
13777             DisplayError(_("It is your turn"), 0);
13778             return;
13779         }
13780         cps = &first;
13781         break;
13782       case TwoMachinesPlay:
13783         if (WhiteOnMove(forwardMostMove) ==
13784             (first.twoMachinesColor[0] == 'w')) {
13785             cps = &first;
13786         } else {
13787             cps = &second;
13788         }
13789         break;
13790       case BeginningOfGame:
13791       default:
13792         return;
13793     }
13794     SendToProgram("?\n", cps);
13795 }
13796
13797 void
13798 TruncateGameEvent()
13799 {
13800     EditGameEvent();
13801     if (gameMode != EditGame) return;
13802     TruncateGame();
13803 }
13804
13805 void
13806 TruncateGame()
13807 {
13808     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13809     if (forwardMostMove > currentMove) {
13810         if (gameInfo.resultDetails != NULL) {
13811             free(gameInfo.resultDetails);
13812             gameInfo.resultDetails = NULL;
13813             gameInfo.result = GameUnfinished;
13814         }
13815         forwardMostMove = currentMove;
13816         HistorySet(parseList, backwardMostMove, forwardMostMove,
13817                    currentMove-1);
13818     }
13819 }
13820
13821 void
13822 HintEvent()
13823 {
13824     if (appData.noChessProgram) return;
13825     switch (gameMode) {
13826       case MachinePlaysWhite:
13827         if (WhiteOnMove(forwardMostMove)) {
13828             DisplayError(_("Wait until your turn"), 0);
13829             return;
13830         }
13831         break;
13832       case BeginningOfGame:
13833       case MachinePlaysBlack:
13834         if (!WhiteOnMove(forwardMostMove)) {
13835             DisplayError(_("Wait until your turn"), 0);
13836             return;
13837         }
13838         break;
13839       default:
13840         DisplayError(_("No hint available"), 0);
13841         return;
13842     }
13843     SendToProgram("hint\n", &first);
13844     hintRequested = TRUE;
13845 }
13846
13847 void
13848 BookEvent()
13849 {
13850     if (appData.noChessProgram) return;
13851     switch (gameMode) {
13852       case MachinePlaysWhite:
13853         if (WhiteOnMove(forwardMostMove)) {
13854             DisplayError(_("Wait until your turn"), 0);
13855             return;
13856         }
13857         break;
13858       case BeginningOfGame:
13859       case MachinePlaysBlack:
13860         if (!WhiteOnMove(forwardMostMove)) {
13861             DisplayError(_("Wait until your turn"), 0);
13862             return;
13863         }
13864         break;
13865       case EditPosition:
13866         EditPositionDone(TRUE);
13867         break;
13868       case TwoMachinesPlay:
13869         return;
13870       default:
13871         break;
13872     }
13873     SendToProgram("bk\n", &first);
13874     bookOutput[0] = NULLCHAR;
13875     bookRequested = TRUE;
13876 }
13877
13878 void
13879 AboutGameEvent()
13880 {
13881     char *tags = PGNTags(&gameInfo);
13882     TagsPopUp(tags, CmailMsg());
13883     free(tags);
13884 }
13885
13886 /* end button procedures */
13887
13888 void
13889 PrintPosition(fp, move)
13890      FILE *fp;
13891      int move;
13892 {
13893     int i, j;
13894
13895     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13896         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13897             char c = PieceToChar(boards[move][i][j]);
13898             fputc(c == 'x' ? '.' : c, fp);
13899             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13900         }
13901     }
13902     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13903       fprintf(fp, "white to play\n");
13904     else
13905       fprintf(fp, "black to play\n");
13906 }
13907
13908 void
13909 PrintOpponents(fp)
13910      FILE *fp;
13911 {
13912     if (gameInfo.white != NULL) {
13913         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13914     } else {
13915         fprintf(fp, "\n");
13916     }
13917 }
13918
13919 /* Find last component of program's own name, using some heuristics */
13920 void
13921 TidyProgramName(prog, host, buf)
13922      char *prog, *host, buf[MSG_SIZ];
13923 {
13924     char *p, *q;
13925     int local = (strcmp(host, "localhost") == 0);
13926     while (!local && (p = strchr(prog, ';')) != NULL) {
13927         p++;
13928         while (*p == ' ') p++;
13929         prog = p;
13930     }
13931     if (*prog == '"' || *prog == '\'') {
13932         q = strchr(prog + 1, *prog);
13933     } else {
13934         q = strchr(prog, ' ');
13935     }
13936     if (q == NULL) q = prog + strlen(prog);
13937     p = q;
13938     while (p >= prog && *p != '/' && *p != '\\') p--;
13939     p++;
13940     if(p == prog && *p == '"') p++;
13941     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13942     memcpy(buf, p, q - p);
13943     buf[q - p] = NULLCHAR;
13944     if (!local) {
13945         strcat(buf, "@");
13946         strcat(buf, host);
13947     }
13948 }
13949
13950 char *
13951 TimeControlTagValue()
13952 {
13953     char buf[MSG_SIZ];
13954     if (!appData.clockMode) {
13955       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13956     } else if (movesPerSession > 0) {
13957       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13958     } else if (timeIncrement == 0) {
13959       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13960     } else {
13961       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13962     }
13963     return StrSave(buf);
13964 }
13965
13966 void
13967 SetGameInfo()
13968 {
13969     /* This routine is used only for certain modes */
13970     VariantClass v = gameInfo.variant;
13971     ChessMove r = GameUnfinished;
13972     char *p = NULL;
13973
13974     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13975         r = gameInfo.result;
13976         p = gameInfo.resultDetails;
13977         gameInfo.resultDetails = NULL;
13978     }
13979     ClearGameInfo(&gameInfo);
13980     gameInfo.variant = v;
13981
13982     switch (gameMode) {
13983       case MachinePlaysWhite:
13984         gameInfo.event = StrSave( appData.pgnEventHeader );
13985         gameInfo.site = StrSave(HostName());
13986         gameInfo.date = PGNDate();
13987         gameInfo.round = StrSave("-");
13988         gameInfo.white = StrSave(first.tidy);
13989         gameInfo.black = StrSave(UserName());
13990         gameInfo.timeControl = TimeControlTagValue();
13991         break;
13992
13993       case MachinePlaysBlack:
13994         gameInfo.event = StrSave( appData.pgnEventHeader );
13995         gameInfo.site = StrSave(HostName());
13996         gameInfo.date = PGNDate();
13997         gameInfo.round = StrSave("-");
13998         gameInfo.white = StrSave(UserName());
13999         gameInfo.black = StrSave(first.tidy);
14000         gameInfo.timeControl = TimeControlTagValue();
14001         break;
14002
14003       case TwoMachinesPlay:
14004         gameInfo.event = StrSave( appData.pgnEventHeader );
14005         gameInfo.site = StrSave(HostName());
14006         gameInfo.date = PGNDate();
14007         if (roundNr > 0) {
14008             char buf[MSG_SIZ];
14009             snprintf(buf, MSG_SIZ, "%d", roundNr);
14010             gameInfo.round = StrSave(buf);
14011         } else {
14012             gameInfo.round = StrSave("-");
14013         }
14014         if (first.twoMachinesColor[0] == 'w') {
14015             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14016             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14017         } else {
14018             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14019             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14020         }
14021         gameInfo.timeControl = TimeControlTagValue();
14022         break;
14023
14024       case EditGame:
14025         gameInfo.event = StrSave("Edited game");
14026         gameInfo.site = StrSave(HostName());
14027         gameInfo.date = PGNDate();
14028         gameInfo.round = StrSave("-");
14029         gameInfo.white = StrSave("-");
14030         gameInfo.black = StrSave("-");
14031         gameInfo.result = r;
14032         gameInfo.resultDetails = p;
14033         break;
14034
14035       case EditPosition:
14036         gameInfo.event = StrSave("Edited position");
14037         gameInfo.site = StrSave(HostName());
14038         gameInfo.date = PGNDate();
14039         gameInfo.round = StrSave("-");
14040         gameInfo.white = StrSave("-");
14041         gameInfo.black = StrSave("-");
14042         break;
14043
14044       case IcsPlayingWhite:
14045       case IcsPlayingBlack:
14046       case IcsObserving:
14047       case IcsExamining:
14048         break;
14049
14050       case PlayFromGameFile:
14051         gameInfo.event = StrSave("Game from non-PGN file");
14052         gameInfo.site = StrSave(HostName());
14053         gameInfo.date = PGNDate();
14054         gameInfo.round = StrSave("-");
14055         gameInfo.white = StrSave("?");
14056         gameInfo.black = StrSave("?");
14057         break;
14058
14059       default:
14060         break;
14061     }
14062 }
14063
14064 void
14065 ReplaceComment(index, text)
14066      int index;
14067      char *text;
14068 {
14069     int len;
14070     char *p;
14071     float score;
14072
14073     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14074        pvInfoList[index-1].depth == len &&
14075        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14076        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14077     while (*text == '\n') text++;
14078     len = strlen(text);
14079     while (len > 0 && text[len - 1] == '\n') len--;
14080
14081     if (commentList[index] != NULL)
14082       free(commentList[index]);
14083
14084     if (len == 0) {
14085         commentList[index] = NULL;
14086         return;
14087     }
14088   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14089       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14090       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14091     commentList[index] = (char *) malloc(len + 2);
14092     strncpy(commentList[index], text, len);
14093     commentList[index][len] = '\n';
14094     commentList[index][len + 1] = NULLCHAR;
14095   } else {
14096     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14097     char *p;
14098     commentList[index] = (char *) malloc(len + 7);
14099     safeStrCpy(commentList[index], "{\n", 3);
14100     safeStrCpy(commentList[index]+2, text, len+1);
14101     commentList[index][len+2] = NULLCHAR;
14102     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14103     strcat(commentList[index], "\n}\n");
14104   }
14105 }
14106
14107 void
14108 CrushCRs(text)
14109      char *text;
14110 {
14111   char *p = text;
14112   char *q = text;
14113   char ch;
14114
14115   do {
14116     ch = *p++;
14117     if (ch == '\r') continue;
14118     *q++ = ch;
14119   } while (ch != '\0');
14120 }
14121
14122 void
14123 AppendComment(index, text, addBraces)
14124      int index;
14125      char *text;
14126      Boolean addBraces; // [HGM] braces: tells if we should add {}
14127 {
14128     int oldlen, len;
14129     char *old;
14130
14131 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14132     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14133
14134     CrushCRs(text);
14135     while (*text == '\n') text++;
14136     len = strlen(text);
14137     while (len > 0 && text[len - 1] == '\n') len--;
14138
14139     if (len == 0) return;
14140
14141     if (commentList[index] != NULL) {
14142         old = commentList[index];
14143         oldlen = strlen(old);
14144         while(commentList[index][oldlen-1] ==  '\n')
14145           commentList[index][--oldlen] = NULLCHAR;
14146         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14147         safeStrCpy(commentList[index], old, oldlen + len + 6);
14148         free(old);
14149         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14150         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14151           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14152           while (*text == '\n') { text++; len--; }
14153           commentList[index][--oldlen] = NULLCHAR;
14154       }
14155         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14156         else          strcat(commentList[index], "\n");
14157         strcat(commentList[index], text);
14158         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14159         else          strcat(commentList[index], "\n");
14160     } else {
14161         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14162         if(addBraces)
14163           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14164         else commentList[index][0] = NULLCHAR;
14165         strcat(commentList[index], text);
14166         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14167         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14168     }
14169 }
14170
14171 static char * FindStr( char * text, char * sub_text )
14172 {
14173     char * result = strstr( text, sub_text );
14174
14175     if( result != NULL ) {
14176         result += strlen( sub_text );
14177     }
14178
14179     return result;
14180 }
14181
14182 /* [AS] Try to extract PV info from PGN comment */
14183 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14184 char *GetInfoFromComment( int index, char * text )
14185 {
14186     char * sep = text, *p;
14187
14188     if( text != NULL && index > 0 ) {
14189         int score = 0;
14190         int depth = 0;
14191         int time = -1, sec = 0, deci;
14192         char * s_eval = FindStr( text, "[%eval " );
14193         char * s_emt = FindStr( text, "[%emt " );
14194
14195         if( s_eval != NULL || s_emt != NULL ) {
14196             /* New style */
14197             char delim;
14198
14199             if( s_eval != NULL ) {
14200                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14201                     return text;
14202                 }
14203
14204                 if( delim != ']' ) {
14205                     return text;
14206                 }
14207             }
14208
14209             if( s_emt != NULL ) {
14210             }
14211                 return text;
14212         }
14213         else {
14214             /* We expect something like: [+|-]nnn.nn/dd */
14215             int score_lo = 0;
14216
14217             if(*text != '{') return text; // [HGM] braces: must be normal comment
14218
14219             sep = strchr( text, '/' );
14220             if( sep == NULL || sep < (text+4) ) {
14221                 return text;
14222             }
14223
14224             p = text;
14225             if(p[1] == '(') { // comment starts with PV
14226                p = strchr(p, ')'); // locate end of PV
14227                if(p == NULL || sep < p+5) return text;
14228                // at this point we have something like "{(.*) +0.23/6 ..."
14229                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14230                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14231                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14232             }
14233             time = -1; sec = -1; deci = -1;
14234             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14235                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14236                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14237                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14238                 return text;
14239             }
14240
14241             if( score_lo < 0 || score_lo >= 100 ) {
14242                 return text;
14243             }
14244
14245             if(sec >= 0) time = 600*time + 10*sec; else
14246             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14247
14248             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14249
14250             /* [HGM] PV time: now locate end of PV info */
14251             while( *++sep >= '0' && *sep <= '9'); // strip depth
14252             if(time >= 0)
14253             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14254             if(sec >= 0)
14255             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14256             if(deci >= 0)
14257             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14258             while(*sep == ' ') sep++;
14259         }
14260
14261         if( depth <= 0 ) {
14262             return text;
14263         }
14264
14265         if( time < 0 ) {
14266             time = -1;
14267         }
14268
14269         pvInfoList[index-1].depth = depth;
14270         pvInfoList[index-1].score = score;
14271         pvInfoList[index-1].time  = 10*time; // centi-sec
14272         if(*sep == '}') *sep = 0; else *--sep = '{';
14273         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14274     }
14275     return sep;
14276 }
14277
14278 void
14279 SendToProgram(message, cps)
14280      char *message;
14281      ChessProgramState *cps;
14282 {
14283     int count, outCount, error;
14284     char buf[MSG_SIZ];
14285
14286     if (cps->pr == NULL) return;
14287     Attention(cps);
14288
14289     if (appData.debugMode) {
14290         TimeMark now;
14291         GetTimeMark(&now);
14292         fprintf(debugFP, "%ld >%-6s: %s",
14293                 SubtractTimeMarks(&now, &programStartTime),
14294                 cps->which, message);
14295     }
14296
14297     count = strlen(message);
14298     outCount = OutputToProcess(cps->pr, message, count, &error);
14299     if (outCount < count && !exiting
14300                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14301       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14302       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14303         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14304             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14305                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14306                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14307                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14308             } else {
14309                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14310                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14311                 gameInfo.result = res;
14312             }
14313             gameInfo.resultDetails = StrSave(buf);
14314         }
14315         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14316         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14317     }
14318 }
14319
14320 void
14321 ReceiveFromProgram(isr, closure, message, count, error)
14322      InputSourceRef isr;
14323      VOIDSTAR closure;
14324      char *message;
14325      int count;
14326      int error;
14327 {
14328     char *end_str;
14329     char buf[MSG_SIZ];
14330     ChessProgramState *cps = (ChessProgramState *)closure;
14331
14332     if (isr != cps->isr) return; /* Killed intentionally */
14333     if (count <= 0) {
14334         if (count == 0) {
14335             RemoveInputSource(cps->isr);
14336             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14337                     _(cps->which), cps->program);
14338         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14339                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14340                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14341                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14342                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14343                 } else {
14344                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14345                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14346                     gameInfo.result = res;
14347                 }
14348                 gameInfo.resultDetails = StrSave(buf);
14349             }
14350             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14351             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14352         } else {
14353             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14354                     _(cps->which), cps->program);
14355             RemoveInputSource(cps->isr);
14356
14357             /* [AS] Program is misbehaving badly... kill it */
14358             if( count == -2 ) {
14359                 DestroyChildProcess( cps->pr, 9 );
14360                 cps->pr = NoProc;
14361             }
14362
14363             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14364         }
14365         return;
14366     }
14367
14368     if ((end_str = strchr(message, '\r')) != NULL)
14369       *end_str = NULLCHAR;
14370     if ((end_str = strchr(message, '\n')) != NULL)
14371       *end_str = NULLCHAR;
14372
14373     if (appData.debugMode) {
14374         TimeMark now; int print = 1;
14375         char *quote = ""; char c; int i;
14376
14377         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14378                 char start = message[0];
14379                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14380                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14381                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14382                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14383                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14384                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14385                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14386                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14387                    sscanf(message, "hint: %c", &c)!=1 && 
14388                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14389                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14390                     print = (appData.engineComments >= 2);
14391                 }
14392                 message[0] = start; // restore original message
14393         }
14394         if(print) {
14395                 GetTimeMark(&now);
14396                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14397                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14398                         quote,
14399                         message);
14400         }
14401     }
14402
14403     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14404     if (appData.icsEngineAnalyze) {
14405         if (strstr(message, "whisper") != NULL ||
14406              strstr(message, "kibitz") != NULL ||
14407             strstr(message, "tellics") != NULL) return;
14408     }
14409
14410     HandleMachineMove(message, cps);
14411 }
14412
14413
14414 void
14415 SendTimeControl(cps, mps, tc, inc, sd, st)
14416      ChessProgramState *cps;
14417      int mps, inc, sd, st;
14418      long tc;
14419 {
14420     char buf[MSG_SIZ];
14421     int seconds;
14422
14423     if( timeControl_2 > 0 ) {
14424         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14425             tc = timeControl_2;
14426         }
14427     }
14428     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14429     inc /= cps->timeOdds;
14430     st  /= cps->timeOdds;
14431
14432     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14433
14434     if (st > 0) {
14435       /* Set exact time per move, normally using st command */
14436       if (cps->stKludge) {
14437         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14438         seconds = st % 60;
14439         if (seconds == 0) {
14440           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14441         } else {
14442           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14443         }
14444       } else {
14445         snprintf(buf, MSG_SIZ, "st %d\n", st);
14446       }
14447     } else {
14448       /* Set conventional or incremental time control, using level command */
14449       if (seconds == 0) {
14450         /* Note old gnuchess bug -- minutes:seconds used to not work.
14451            Fixed in later versions, but still avoid :seconds
14452            when seconds is 0. */
14453         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14454       } else {
14455         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14456                  seconds, inc/1000.);
14457       }
14458     }
14459     SendToProgram(buf, cps);
14460
14461     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14462     /* Orthogonally, limit search to given depth */
14463     if (sd > 0) {
14464       if (cps->sdKludge) {
14465         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14466       } else {
14467         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14468       }
14469       SendToProgram(buf, cps);
14470     }
14471
14472     if(cps->nps >= 0) { /* [HGM] nps */
14473         if(cps->supportsNPS == FALSE)
14474           cps->nps = -1; // don't use if engine explicitly says not supported!
14475         else {
14476           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14477           SendToProgram(buf, cps);
14478         }
14479     }
14480 }
14481
14482 ChessProgramState *WhitePlayer()
14483 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14484 {
14485     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14486        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14487         return &second;
14488     return &first;
14489 }
14490
14491 void
14492 SendTimeRemaining(cps, machineWhite)
14493      ChessProgramState *cps;
14494      int /*boolean*/ machineWhite;
14495 {
14496     char message[MSG_SIZ];
14497     long time, otime;
14498
14499     /* Note: this routine must be called when the clocks are stopped
14500        or when they have *just* been set or switched; otherwise
14501        it will be off by the time since the current tick started.
14502     */
14503     if (machineWhite) {
14504         time = whiteTimeRemaining / 10;
14505         otime = blackTimeRemaining / 10;
14506     } else {
14507         time = blackTimeRemaining / 10;
14508         otime = whiteTimeRemaining / 10;
14509     }
14510     /* [HGM] translate opponent's time by time-odds factor */
14511     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14512     if (appData.debugMode) {
14513         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14514     }
14515
14516     if (time <= 0) time = 1;
14517     if (otime <= 0) otime = 1;
14518
14519     snprintf(message, MSG_SIZ, "time %ld\n", time);
14520     SendToProgram(message, cps);
14521
14522     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14523     SendToProgram(message, cps);
14524 }
14525
14526 int
14527 BoolFeature(p, name, loc, cps)
14528      char **p;
14529      char *name;
14530      int *loc;
14531      ChessProgramState *cps;
14532 {
14533   char buf[MSG_SIZ];
14534   int len = strlen(name);
14535   int val;
14536
14537   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14538     (*p) += len + 1;
14539     sscanf(*p, "%d", &val);
14540     *loc = (val != 0);
14541     while (**p && **p != ' ')
14542       (*p)++;
14543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14544     SendToProgram(buf, cps);
14545     return TRUE;
14546   }
14547   return FALSE;
14548 }
14549
14550 int
14551 IntFeature(p, name, loc, cps)
14552      char **p;
14553      char *name;
14554      int *loc;
14555      ChessProgramState *cps;
14556 {
14557   char buf[MSG_SIZ];
14558   int len = strlen(name);
14559   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14560     (*p) += len + 1;
14561     sscanf(*p, "%d", loc);
14562     while (**p && **p != ' ') (*p)++;
14563     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14564     SendToProgram(buf, cps);
14565     return TRUE;
14566   }
14567   return FALSE;
14568 }
14569
14570 int
14571 StringFeature(p, name, loc, cps)
14572      char **p;
14573      char *name;
14574      char loc[];
14575      ChessProgramState *cps;
14576 {
14577   char buf[MSG_SIZ];
14578   int len = strlen(name);
14579   if (strncmp((*p), name, len) == 0
14580       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14581     (*p) += len + 2;
14582     sscanf(*p, "%[^\"]", loc);
14583     while (**p && **p != '\"') (*p)++;
14584     if (**p == '\"') (*p)++;
14585     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14586     SendToProgram(buf, cps);
14587     return TRUE;
14588   }
14589   return FALSE;
14590 }
14591
14592 int
14593 ParseOption(Option *opt, ChessProgramState *cps)
14594 // [HGM] options: process the string that defines an engine option, and determine
14595 // name, type, default value, and allowed value range
14596 {
14597         char *p, *q, buf[MSG_SIZ];
14598         int n, min = (-1)<<31, max = 1<<31, def;
14599
14600         if(p = strstr(opt->name, " -spin ")) {
14601             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14602             if(max < min) max = min; // enforce consistency
14603             if(def < min) def = min;
14604             if(def > max) def = max;
14605             opt->value = def;
14606             opt->min = min;
14607             opt->max = max;
14608             opt->type = Spin;
14609         } else if((p = strstr(opt->name, " -slider "))) {
14610             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14611             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14612             if(max < min) max = min; // enforce consistency
14613             if(def < min) def = min;
14614             if(def > max) def = max;
14615             opt->value = def;
14616             opt->min = min;
14617             opt->max = max;
14618             opt->type = Spin; // Slider;
14619         } else if((p = strstr(opt->name, " -string "))) {
14620             opt->textValue = p+9;
14621             opt->type = TextBox;
14622         } else if((p = strstr(opt->name, " -file "))) {
14623             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14624             opt->textValue = p+7;
14625             opt->type = FileName; // FileName;
14626         } else if((p = strstr(opt->name, " -path "))) {
14627             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14628             opt->textValue = p+7;
14629             opt->type = PathName; // PathName;
14630         } else if(p = strstr(opt->name, " -check ")) {
14631             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14632             opt->value = (def != 0);
14633             opt->type = CheckBox;
14634         } else if(p = strstr(opt->name, " -combo ")) {
14635             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14636             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14637             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14638             opt->value = n = 0;
14639             while(q = StrStr(q, " /// ")) {
14640                 n++; *q = 0;    // count choices, and null-terminate each of them
14641                 q += 5;
14642                 if(*q == '*') { // remember default, which is marked with * prefix
14643                     q++;
14644                     opt->value = n;
14645                 }
14646                 cps->comboList[cps->comboCnt++] = q;
14647             }
14648             cps->comboList[cps->comboCnt++] = NULL;
14649             opt->max = n + 1;
14650             opt->type = ComboBox;
14651         } else if(p = strstr(opt->name, " -button")) {
14652             opt->type = Button;
14653         } else if(p = strstr(opt->name, " -save")) {
14654             opt->type = SaveButton;
14655         } else return FALSE;
14656         *p = 0; // terminate option name
14657         // now look if the command-line options define a setting for this engine option.
14658         if(cps->optionSettings && cps->optionSettings[0])
14659             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14660         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14661           snprintf(buf, MSG_SIZ, "option %s", p);
14662                 if(p = strstr(buf, ",")) *p = 0;
14663                 if(q = strchr(buf, '=')) switch(opt->type) {
14664                     case ComboBox:
14665                         for(n=0; n<opt->max; n++)
14666                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14667                         break;
14668                     case TextBox:
14669                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14670                         break;
14671                     case Spin:
14672                     case CheckBox:
14673                         opt->value = atoi(q+1);
14674                     default:
14675                         break;
14676                 }
14677                 strcat(buf, "\n");
14678                 SendToProgram(buf, cps);
14679         }
14680         return TRUE;
14681 }
14682
14683 void
14684 FeatureDone(cps, val)
14685      ChessProgramState* cps;
14686      int val;
14687 {
14688   DelayedEventCallback cb = GetDelayedEvent();
14689   if ((cb == InitBackEnd3 && cps == &first) ||
14690       (cb == SettingsMenuIfReady && cps == &second) ||
14691       (cb == LoadEngine) ||
14692       (cb == TwoMachinesEventIfReady)) {
14693     CancelDelayedEvent();
14694     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14695   }
14696   cps->initDone = val;
14697 }
14698
14699 /* Parse feature command from engine */
14700 void
14701 ParseFeatures(args, cps)
14702      char* args;
14703      ChessProgramState *cps;
14704 {
14705   char *p = args;
14706   char *q;
14707   int val;
14708   char buf[MSG_SIZ];
14709
14710   for (;;) {
14711     while (*p == ' ') p++;
14712     if (*p == NULLCHAR) return;
14713
14714     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14715     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14716     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14717     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14718     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14719     if (BoolFeature(&p, "reuse", &val, cps)) {
14720       /* Engine can disable reuse, but can't enable it if user said no */
14721       if (!val) cps->reuse = FALSE;
14722       continue;
14723     }
14724     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14725     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14726       if (gameMode == TwoMachinesPlay) {
14727         DisplayTwoMachinesTitle();
14728       } else {
14729         DisplayTitle("");
14730       }
14731       continue;
14732     }
14733     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14734     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14735     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14736     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14737     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14738     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14739     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14740     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14741     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14742     if (IntFeature(&p, "done", &val, cps)) {
14743       FeatureDone(cps, val);
14744       continue;
14745     }
14746     /* Added by Tord: */
14747     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14748     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14749     /* End of additions by Tord */
14750
14751     /* [HGM] added features: */
14752     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14753     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14754     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14755     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14756     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14757     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14758     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14759         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14760           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14761             SendToProgram(buf, cps);
14762             continue;
14763         }
14764         if(cps->nrOptions >= MAX_OPTIONS) {
14765             cps->nrOptions--;
14766             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14767             DisplayError(buf, 0);
14768         }
14769         continue;
14770     }
14771     /* End of additions by HGM */
14772
14773     /* unknown feature: complain and skip */
14774     q = p;
14775     while (*q && *q != '=') q++;
14776     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14777     SendToProgram(buf, cps);
14778     p = q;
14779     if (*p == '=') {
14780       p++;
14781       if (*p == '\"') {
14782         p++;
14783         while (*p && *p != '\"') p++;
14784         if (*p == '\"') p++;
14785       } else {
14786         while (*p && *p != ' ') p++;
14787       }
14788     }
14789   }
14790
14791 }
14792
14793 void
14794 PeriodicUpdatesEvent(newState)
14795      int newState;
14796 {
14797     if (newState == appData.periodicUpdates)
14798       return;
14799
14800     appData.periodicUpdates=newState;
14801
14802     /* Display type changes, so update it now */
14803 //    DisplayAnalysis();
14804
14805     /* Get the ball rolling again... */
14806     if (newState) {
14807         AnalysisPeriodicEvent(1);
14808         StartAnalysisClock();
14809     }
14810 }
14811
14812 void
14813 PonderNextMoveEvent(newState)
14814      int newState;
14815 {
14816     if (newState == appData.ponderNextMove) return;
14817     if (gameMode == EditPosition) EditPositionDone(TRUE);
14818     if (newState) {
14819         SendToProgram("hard\n", &first);
14820         if (gameMode == TwoMachinesPlay) {
14821             SendToProgram("hard\n", &second);
14822         }
14823     } else {
14824         SendToProgram("easy\n", &first);
14825         thinkOutput[0] = NULLCHAR;
14826         if (gameMode == TwoMachinesPlay) {
14827             SendToProgram("easy\n", &second);
14828         }
14829     }
14830     appData.ponderNextMove = newState;
14831 }
14832
14833 void
14834 NewSettingEvent(option, feature, command, value)
14835      char *command;
14836      int option, value, *feature;
14837 {
14838     char buf[MSG_SIZ];
14839
14840     if (gameMode == EditPosition) EditPositionDone(TRUE);
14841     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14842     if(feature == NULL || *feature) SendToProgram(buf, &first);
14843     if (gameMode == TwoMachinesPlay) {
14844         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14845     }
14846 }
14847
14848 void
14849 ShowThinkingEvent()
14850 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14851 {
14852     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14853     int newState = appData.showThinking
14854         // [HGM] thinking: other features now need thinking output as well
14855         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14856
14857     if (oldState == newState) return;
14858     oldState = newState;
14859     if (gameMode == EditPosition) EditPositionDone(TRUE);
14860     if (oldState) {
14861         SendToProgram("post\n", &first);
14862         if (gameMode == TwoMachinesPlay) {
14863             SendToProgram("post\n", &second);
14864         }
14865     } else {
14866         SendToProgram("nopost\n", &first);
14867         thinkOutput[0] = NULLCHAR;
14868         if (gameMode == TwoMachinesPlay) {
14869             SendToProgram("nopost\n", &second);
14870         }
14871     }
14872 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14873 }
14874
14875 void
14876 AskQuestionEvent(title, question, replyPrefix, which)
14877      char *title; char *question; char *replyPrefix; char *which;
14878 {
14879   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14880   if (pr == NoProc) return;
14881   AskQuestion(title, question, replyPrefix, pr);
14882 }
14883
14884 void
14885 TypeInEvent(char firstChar)
14886 {
14887     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14888         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14889         gameMode == AnalyzeMode || gameMode == EditGame || \r
14890         gameMode == EditPosition || gameMode == IcsExamining ||\r
14891         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14892         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14893                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14894                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14895         gameMode == Training) PopUpMoveDialog(firstChar);
14896 }
14897
14898 void
14899 TypeInDoneEvent(char *move)
14900 {
14901         Board board;
14902         int n, fromX, fromY, toX, toY;
14903         char promoChar;
14904         ChessMove moveType;\r
14905
14906         // [HGM] FENedit\r
14907         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14908                 EditPositionPasteFEN(move);\r
14909                 return;\r
14910         }\r
14911         // [HGM] movenum: allow move number to be typed in any mode\r
14912         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14913           ToNrEvent(2*n-1);\r
14914           return;\r
14915         }\r
14916
14917       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14918         gameMode != Training) {\r
14919         DisplayMoveError(_("Displayed move is not current"));\r
14920       } else {\r
14921         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14922           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14923         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14924         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14925           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14926           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14927         } else {\r
14928           DisplayMoveError(_("Could not parse move"));\r
14929         }
14930       }\r
14931 }\r
14932
14933 void
14934 DisplayMove(moveNumber)
14935      int moveNumber;
14936 {
14937     char message[MSG_SIZ];
14938     char res[MSG_SIZ];
14939     char cpThinkOutput[MSG_SIZ];
14940
14941     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14942
14943     if (moveNumber == forwardMostMove - 1 ||
14944         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14945
14946         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14947
14948         if (strchr(cpThinkOutput, '\n')) {
14949             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14950         }
14951     } else {
14952         *cpThinkOutput = NULLCHAR;
14953     }
14954
14955     /* [AS] Hide thinking from human user */
14956     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14957         *cpThinkOutput = NULLCHAR;
14958         if( thinkOutput[0] != NULLCHAR ) {
14959             int i;
14960
14961             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14962                 cpThinkOutput[i] = '.';
14963             }
14964             cpThinkOutput[i] = NULLCHAR;
14965             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14966         }
14967     }
14968
14969     if (moveNumber == forwardMostMove - 1 &&
14970         gameInfo.resultDetails != NULL) {
14971         if (gameInfo.resultDetails[0] == NULLCHAR) {
14972           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14973         } else {
14974           snprintf(res, MSG_SIZ, " {%s} %s",
14975                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14976         }
14977     } else {
14978         res[0] = NULLCHAR;
14979     }
14980
14981     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14982         DisplayMessage(res, cpThinkOutput);
14983     } else {
14984       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14985                 WhiteOnMove(moveNumber) ? " " : ".. ",
14986                 parseList[moveNumber], res);
14987         DisplayMessage(message, cpThinkOutput);
14988     }
14989 }
14990
14991 void
14992 DisplayComment(moveNumber, text)
14993      int moveNumber;
14994      char *text;
14995 {
14996     char title[MSG_SIZ];
14997     char buf[8000]; // comment can be long!
14998     int score, depth;
14999
15000     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15001       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15002     } else {
15003       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15004               WhiteOnMove(moveNumber) ? " " : ".. ",
15005               parseList[moveNumber]);
15006     }
15007     // [HGM] PV info: display PV info together with (or as) comment
15008     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15009       if(text == NULL) text = "";
15010       score = pvInfoList[moveNumber].score;
15011       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15012               depth, (pvInfoList[moveNumber].time+50)/100, text);
15013       text = buf;
15014     }
15015     if (text != NULL && (appData.autoDisplayComment || commentUp))
15016         CommentPopUp(title, text);
15017 }
15018
15019 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15020  * might be busy thinking or pondering.  It can be omitted if your
15021  * gnuchess is configured to stop thinking immediately on any user
15022  * input.  However, that gnuchess feature depends on the FIONREAD
15023  * ioctl, which does not work properly on some flavors of Unix.
15024  */
15025 void
15026 Attention(cps)
15027      ChessProgramState *cps;
15028 {
15029 #if ATTENTION
15030     if (!cps->useSigint) return;
15031     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15032     switch (gameMode) {
15033       case MachinePlaysWhite:
15034       case MachinePlaysBlack:
15035       case TwoMachinesPlay:
15036       case IcsPlayingWhite:
15037       case IcsPlayingBlack:
15038       case AnalyzeMode:
15039       case AnalyzeFile:
15040         /* Skip if we know it isn't thinking */
15041         if (!cps->maybeThinking) return;
15042         if (appData.debugMode)
15043           fprintf(debugFP, "Interrupting %s\n", cps->which);
15044         InterruptChildProcess(cps->pr);
15045         cps->maybeThinking = FALSE;
15046         break;
15047       default:
15048         break;
15049     }
15050 #endif /*ATTENTION*/
15051 }
15052
15053 int
15054 CheckFlags()
15055 {
15056     if (whiteTimeRemaining <= 0) {
15057         if (!whiteFlag) {
15058             whiteFlag = TRUE;
15059             if (appData.icsActive) {
15060                 if (appData.autoCallFlag &&
15061                     gameMode == IcsPlayingBlack && !blackFlag) {
15062                   SendToICS(ics_prefix);
15063                   SendToICS("flag\n");
15064                 }
15065             } else {
15066                 if (blackFlag) {
15067                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15068                 } else {
15069                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15070                     if (appData.autoCallFlag) {
15071                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15072                         return TRUE;
15073                     }
15074                 }
15075             }
15076         }
15077     }
15078     if (blackTimeRemaining <= 0) {
15079         if (!blackFlag) {
15080             blackFlag = TRUE;
15081             if (appData.icsActive) {
15082                 if (appData.autoCallFlag &&
15083                     gameMode == IcsPlayingWhite && !whiteFlag) {
15084                   SendToICS(ics_prefix);
15085                   SendToICS("flag\n");
15086                 }
15087             } else {
15088                 if (whiteFlag) {
15089                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15090                 } else {
15091                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15092                     if (appData.autoCallFlag) {
15093                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15094                         return TRUE;
15095                     }
15096                 }
15097             }
15098         }
15099     }
15100     return FALSE;
15101 }
15102
15103 void
15104 CheckTimeControl()
15105 {
15106     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15107         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15108
15109     /*
15110      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15111      */
15112     if ( !WhiteOnMove(forwardMostMove) ) {
15113         /* White made time control */
15114         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15115         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15116         /* [HGM] time odds: correct new time quota for time odds! */
15117                                             / WhitePlayer()->timeOdds;
15118         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15119     } else {
15120         lastBlack -= blackTimeRemaining;
15121         /* Black made time control */
15122         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15123                                             / WhitePlayer()->other->timeOdds;
15124         lastWhite = whiteTimeRemaining;
15125     }
15126 }
15127
15128 void
15129 DisplayBothClocks()
15130 {
15131     int wom = gameMode == EditPosition ?
15132       !blackPlaysFirst : WhiteOnMove(currentMove);
15133     DisplayWhiteClock(whiteTimeRemaining, wom);
15134     DisplayBlackClock(blackTimeRemaining, !wom);
15135 }
15136
15137
15138 /* Timekeeping seems to be a portability nightmare.  I think everyone
15139    has ftime(), but I'm really not sure, so I'm including some ifdefs
15140    to use other calls if you don't.  Clocks will be less accurate if
15141    you have neither ftime nor gettimeofday.
15142 */
15143
15144 /* VS 2008 requires the #include outside of the function */
15145 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15146 #include <sys/timeb.h>
15147 #endif
15148
15149 /* Get the current time as a TimeMark */
15150 void
15151 GetTimeMark(tm)
15152      TimeMark *tm;
15153 {
15154 #if HAVE_GETTIMEOFDAY
15155
15156     struct timeval timeVal;
15157     struct timezone timeZone;
15158
15159     gettimeofday(&timeVal, &timeZone);
15160     tm->sec = (long) timeVal.tv_sec;
15161     tm->ms = (int) (timeVal.tv_usec / 1000L);
15162
15163 #else /*!HAVE_GETTIMEOFDAY*/
15164 #if HAVE_FTIME
15165
15166 // include <sys/timeb.h> / moved to just above start of function
15167     struct timeb timeB;
15168
15169     ftime(&timeB);
15170     tm->sec = (long) timeB.time;
15171     tm->ms = (int) timeB.millitm;
15172
15173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15174     tm->sec = (long) time(NULL);
15175     tm->ms = 0;
15176 #endif
15177 #endif
15178 }
15179
15180 /* Return the difference in milliseconds between two
15181    time marks.  We assume the difference will fit in a long!
15182 */
15183 long
15184 SubtractTimeMarks(tm2, tm1)
15185      TimeMark *tm2, *tm1;
15186 {
15187     return 1000L*(tm2->sec - tm1->sec) +
15188            (long) (tm2->ms - tm1->ms);
15189 }
15190
15191
15192 /*
15193  * Code to manage the game clocks.
15194  *
15195  * In tournament play, black starts the clock and then white makes a move.
15196  * We give the human user a slight advantage if he is playing white---the
15197  * clocks don't run until he makes his first move, so it takes zero time.
15198  * Also, we don't account for network lag, so we could get out of sync
15199  * with GNU Chess's clock -- but then, referees are always right.
15200  */
15201
15202 static TimeMark tickStartTM;
15203 static long intendedTickLength;
15204
15205 long
15206 NextTickLength(timeRemaining)
15207      long timeRemaining;
15208 {
15209     long nominalTickLength, nextTickLength;
15210
15211     if (timeRemaining > 0L && timeRemaining <= 10000L)
15212       nominalTickLength = 100L;
15213     else
15214       nominalTickLength = 1000L;
15215     nextTickLength = timeRemaining % nominalTickLength;
15216     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15217
15218     return nextTickLength;
15219 }
15220
15221 /* Adjust clock one minute up or down */
15222 void
15223 AdjustClock(Boolean which, int dir)
15224 {
15225     if(which) blackTimeRemaining += 60000*dir;
15226     else      whiteTimeRemaining += 60000*dir;
15227     DisplayBothClocks();
15228 }
15229
15230 /* Stop clocks and reset to a fresh time control */
15231 void
15232 ResetClocks()
15233 {
15234     (void) StopClockTimer();
15235     if (appData.icsActive) {
15236         whiteTimeRemaining = blackTimeRemaining = 0;
15237     } else if (searchTime) {
15238         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15239         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15240     } else { /* [HGM] correct new time quote for time odds */
15241         whiteTC = blackTC = fullTimeControlString;
15242         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15243         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15244     }
15245     if (whiteFlag || blackFlag) {
15246         DisplayTitle("");
15247         whiteFlag = blackFlag = FALSE;
15248     }
15249     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15250     DisplayBothClocks();
15251 }
15252
15253 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15254
15255 /* Decrement running clock by amount of time that has passed */
15256 void
15257 DecrementClocks()
15258 {
15259     long timeRemaining;
15260     long lastTickLength, fudge;
15261     TimeMark now;
15262
15263     if (!appData.clockMode) return;
15264     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15265
15266     GetTimeMark(&now);
15267
15268     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15269
15270     /* Fudge if we woke up a little too soon */
15271     fudge = intendedTickLength - lastTickLength;
15272     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15273
15274     if (WhiteOnMove(forwardMostMove)) {
15275         if(whiteNPS >= 0) lastTickLength = 0;
15276         timeRemaining = whiteTimeRemaining -= lastTickLength;
15277         if(timeRemaining < 0 && !appData.icsActive) {
15278             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15279             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15280                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15281                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15282             }
15283         }
15284         DisplayWhiteClock(whiteTimeRemaining - fudge,
15285                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15286     } else {
15287         if(blackNPS >= 0) lastTickLength = 0;
15288         timeRemaining = blackTimeRemaining -= lastTickLength;
15289         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15290             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15291             if(suddenDeath) {
15292                 blackStartMove = forwardMostMove;
15293                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15294             }
15295         }
15296         DisplayBlackClock(blackTimeRemaining - fudge,
15297                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15298     }
15299     if (CheckFlags()) return;
15300
15301     tickStartTM = now;
15302     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15303     StartClockTimer(intendedTickLength);
15304
15305     /* if the time remaining has fallen below the alarm threshold, sound the
15306      * alarm. if the alarm has sounded and (due to a takeback or time control
15307      * with increment) the time remaining has increased to a level above the
15308      * threshold, reset the alarm so it can sound again.
15309      */
15310
15311     if (appData.icsActive && appData.icsAlarm) {
15312
15313         /* make sure we are dealing with the user's clock */
15314         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15315                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15316            )) return;
15317
15318         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15319             alarmSounded = FALSE;
15320         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15321             PlayAlarmSound();
15322             alarmSounded = TRUE;
15323         }
15324     }
15325 }
15326
15327
15328 /* A player has just moved, so stop the previously running
15329    clock and (if in clock mode) start the other one.
15330    We redisplay both clocks in case we're in ICS mode, because
15331    ICS gives us an update to both clocks after every move.
15332    Note that this routine is called *after* forwardMostMove
15333    is updated, so the last fractional tick must be subtracted
15334    from the color that is *not* on move now.
15335 */
15336 void
15337 SwitchClocks(int newMoveNr)
15338 {
15339     long lastTickLength;
15340     TimeMark now;
15341     int flagged = FALSE;
15342
15343     GetTimeMark(&now);
15344
15345     if (StopClockTimer() && appData.clockMode) {
15346         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15347         if (!WhiteOnMove(forwardMostMove)) {
15348             if(blackNPS >= 0) lastTickLength = 0;
15349             blackTimeRemaining -= lastTickLength;
15350            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15351 //         if(pvInfoList[forwardMostMove].time == -1)
15352                  pvInfoList[forwardMostMove].time =               // use GUI time
15353                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15354         } else {
15355            if(whiteNPS >= 0) lastTickLength = 0;
15356            whiteTimeRemaining -= lastTickLength;
15357            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15358 //         if(pvInfoList[forwardMostMove].time == -1)
15359                  pvInfoList[forwardMostMove].time =
15360                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15361         }
15362         flagged = CheckFlags();
15363     }
15364     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15365     CheckTimeControl();
15366
15367     if (flagged || !appData.clockMode) return;
15368
15369     switch (gameMode) {
15370       case MachinePlaysBlack:
15371       case MachinePlaysWhite:
15372       case BeginningOfGame:
15373         if (pausing) return;
15374         break;
15375
15376       case EditGame:
15377       case PlayFromGameFile:
15378       case IcsExamining:
15379         return;
15380
15381       default:
15382         break;
15383     }
15384
15385     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15386         if(WhiteOnMove(forwardMostMove))
15387              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15388         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15389     }
15390
15391     tickStartTM = now;
15392     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15393       whiteTimeRemaining : blackTimeRemaining);
15394     StartClockTimer(intendedTickLength);
15395 }
15396
15397
15398 /* Stop both clocks */
15399 void
15400 StopClocks()
15401 {
15402     long lastTickLength;
15403     TimeMark now;
15404
15405     if (!StopClockTimer()) return;
15406     if (!appData.clockMode) return;
15407
15408     GetTimeMark(&now);
15409
15410     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15411     if (WhiteOnMove(forwardMostMove)) {
15412         if(whiteNPS >= 0) lastTickLength = 0;
15413         whiteTimeRemaining -= lastTickLength;
15414         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15415     } else {
15416         if(blackNPS >= 0) lastTickLength = 0;
15417         blackTimeRemaining -= lastTickLength;
15418         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15419     }
15420     CheckFlags();
15421 }
15422
15423 /* Start clock of player on move.  Time may have been reset, so
15424    if clock is already running, stop and restart it. */
15425 void
15426 StartClocks()
15427 {
15428     (void) StopClockTimer(); /* in case it was running already */
15429     DisplayBothClocks();
15430     if (CheckFlags()) return;
15431
15432     if (!appData.clockMode) return;
15433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15434
15435     GetTimeMark(&tickStartTM);
15436     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15437       whiteTimeRemaining : blackTimeRemaining);
15438
15439    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15440     whiteNPS = blackNPS = -1;
15441     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15442        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15443         whiteNPS = first.nps;
15444     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15445        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15446         blackNPS = first.nps;
15447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15448         whiteNPS = second.nps;
15449     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15450         blackNPS = second.nps;
15451     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15452
15453     StartClockTimer(intendedTickLength);
15454 }
15455
15456 char *
15457 TimeString(ms)
15458      long ms;
15459 {
15460     long second, minute, hour, day;
15461     char *sign = "";
15462     static char buf[32];
15463
15464     if (ms > 0 && ms <= 9900) {
15465       /* convert milliseconds to tenths, rounding up */
15466       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15467
15468       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15469       return buf;
15470     }
15471
15472     /* convert milliseconds to seconds, rounding up */
15473     /* use floating point to avoid strangeness of integer division
15474        with negative dividends on many machines */
15475     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15476
15477     if (second < 0) {
15478         sign = "-";
15479         second = -second;
15480     }
15481
15482     day = second / (60 * 60 * 24);
15483     second = second % (60 * 60 * 24);
15484     hour = second / (60 * 60);
15485     second = second % (60 * 60);
15486     minute = second / 60;
15487     second = second % 60;
15488
15489     if (day > 0)
15490       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15491               sign, day, hour, minute, second);
15492     else if (hour > 0)
15493       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15494     else
15495       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15496
15497     return buf;
15498 }
15499
15500
15501 /*
15502  * This is necessary because some C libraries aren't ANSI C compliant yet.
15503  */
15504 char *
15505 StrStr(string, match)
15506      char *string, *match;
15507 {
15508     int i, length;
15509
15510     length = strlen(match);
15511
15512     for (i = strlen(string) - length; i >= 0; i--, string++)
15513       if (!strncmp(match, string, length))
15514         return string;
15515
15516     return NULL;
15517 }
15518
15519 char *
15520 StrCaseStr(string, match)
15521      char *string, *match;
15522 {
15523     int i, j, length;
15524
15525     length = strlen(match);
15526
15527     for (i = strlen(string) - length; i >= 0; i--, string++) {
15528         for (j = 0; j < length; j++) {
15529             if (ToLower(match[j]) != ToLower(string[j]))
15530               break;
15531         }
15532         if (j == length) return string;
15533     }
15534
15535     return NULL;
15536 }
15537
15538 #ifndef _amigados
15539 int
15540 StrCaseCmp(s1, s2)
15541      char *s1, *s2;
15542 {
15543     char c1, c2;
15544
15545     for (;;) {
15546         c1 = ToLower(*s1++);
15547         c2 = ToLower(*s2++);
15548         if (c1 > c2) return 1;
15549         if (c1 < c2) return -1;
15550         if (c1 == NULLCHAR) return 0;
15551     }
15552 }
15553
15554
15555 int
15556 ToLower(c)
15557      int c;
15558 {
15559     return isupper(c) ? tolower(c) : c;
15560 }
15561
15562
15563 int
15564 ToUpper(c)
15565      int c;
15566 {
15567     return islower(c) ? toupper(c) : c;
15568 }
15569 #endif /* !_amigados    */
15570
15571 char *
15572 StrSave(s)
15573      char *s;
15574 {
15575   char *ret;
15576
15577   if ((ret = (char *) malloc(strlen(s) + 1)))
15578     {
15579       safeStrCpy(ret, s, strlen(s)+1);
15580     }
15581   return ret;
15582 }
15583
15584 char *
15585 StrSavePtr(s, savePtr)
15586      char *s, **savePtr;
15587 {
15588     if (*savePtr) {
15589         free(*savePtr);
15590     }
15591     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15592       safeStrCpy(*savePtr, s, strlen(s)+1);
15593     }
15594     return(*savePtr);
15595 }
15596
15597 char *
15598 PGNDate()
15599 {
15600     time_t clock;
15601     struct tm *tm;
15602     char buf[MSG_SIZ];
15603
15604     clock = time((time_t *)NULL);
15605     tm = localtime(&clock);
15606     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15607             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15608     return StrSave(buf);
15609 }
15610
15611
15612 char *
15613 PositionToFEN(move, overrideCastling)
15614      int move;
15615      char *overrideCastling;
15616 {
15617     int i, j, fromX, fromY, toX, toY;
15618     int whiteToPlay;
15619     char buf[128];
15620     char *p, *q;
15621     int emptycount;
15622     ChessSquare piece;
15623
15624     whiteToPlay = (gameMode == EditPosition) ?
15625       !blackPlaysFirst : (move % 2 == 0);
15626     p = buf;
15627
15628     /* Piece placement data */
15629     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15630         emptycount = 0;
15631         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15632             if (boards[move][i][j] == EmptySquare) {
15633                 emptycount++;
15634             } else { ChessSquare piece = boards[move][i][j];
15635                 if (emptycount > 0) {
15636                     if(emptycount<10) /* [HGM] can be >= 10 */
15637                         *p++ = '0' + emptycount;
15638                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15639                     emptycount = 0;
15640                 }
15641                 if(PieceToChar(piece) == '+') {
15642                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15643                     *p++ = '+';
15644                     piece = (ChessSquare)(DEMOTED piece);
15645                 }
15646                 *p++ = PieceToChar(piece);
15647                 if(p[-1] == '~') {
15648                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15649                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15650                     *p++ = '~';
15651                 }
15652             }
15653         }
15654         if (emptycount > 0) {
15655             if(emptycount<10) /* [HGM] can be >= 10 */
15656                 *p++ = '0' + emptycount;
15657             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15658             emptycount = 0;
15659         }
15660         *p++ = '/';
15661     }
15662     *(p - 1) = ' ';
15663
15664     /* [HGM] print Crazyhouse or Shogi holdings */
15665     if( gameInfo.holdingsWidth ) {
15666         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15667         q = p;
15668         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15669             piece = boards[move][i][BOARD_WIDTH-1];
15670             if( piece != EmptySquare )
15671               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15672                   *p++ = PieceToChar(piece);
15673         }
15674         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15675             piece = boards[move][BOARD_HEIGHT-i-1][0];
15676             if( piece != EmptySquare )
15677               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15678                   *p++ = PieceToChar(piece);
15679         }
15680
15681         if( q == p ) *p++ = '-';
15682         *p++ = ']';
15683         *p++ = ' ';
15684     }
15685
15686     /* Active color */
15687     *p++ = whiteToPlay ? 'w' : 'b';
15688     *p++ = ' ';
15689
15690   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15691     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15692   } else {
15693   if(nrCastlingRights) {
15694      q = p;
15695      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15696        /* [HGM] write directly from rights */
15697            if(boards[move][CASTLING][2] != NoRights &&
15698               boards[move][CASTLING][0] != NoRights   )
15699                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15700            if(boards[move][CASTLING][2] != NoRights &&
15701               boards[move][CASTLING][1] != NoRights   )
15702                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15703            if(boards[move][CASTLING][5] != NoRights &&
15704               boards[move][CASTLING][3] != NoRights   )
15705                 *p++ = boards[move][CASTLING][3] + AAA;
15706            if(boards[move][CASTLING][5] != NoRights &&
15707               boards[move][CASTLING][4] != NoRights   )
15708                 *p++ = boards[move][CASTLING][4] + AAA;
15709      } else {
15710
15711         /* [HGM] write true castling rights */
15712         if( nrCastlingRights == 6 ) {
15713             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15714                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15715             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15716                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15717             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15718                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15719             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15720                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15721         }
15722      }
15723      if (q == p) *p++ = '-'; /* No castling rights */
15724      *p++ = ' ';
15725   }
15726
15727   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15728      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15729     /* En passant target square */
15730     if (move > backwardMostMove) {
15731         fromX = moveList[move - 1][0] - AAA;
15732         fromY = moveList[move - 1][1] - ONE;
15733         toX = moveList[move - 1][2] - AAA;
15734         toY = moveList[move - 1][3] - ONE;
15735         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15736             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15737             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15738             fromX == toX) {
15739             /* 2-square pawn move just happened */
15740             *p++ = toX + AAA;
15741             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15742         } else {
15743             *p++ = '-';
15744         }
15745     } else if(move == backwardMostMove) {
15746         // [HGM] perhaps we should always do it like this, and forget the above?
15747         if((signed char)boards[move][EP_STATUS] >= 0) {
15748             *p++ = boards[move][EP_STATUS] + AAA;
15749             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15750         } else {
15751             *p++ = '-';
15752         }
15753     } else {
15754         *p++ = '-';
15755     }
15756     *p++ = ' ';
15757   }
15758   }
15759
15760     /* [HGM] find reversible plies */
15761     {   int i = 0, j=move;
15762
15763         if (appData.debugMode) { int k;
15764             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15765             for(k=backwardMostMove; k<=forwardMostMove; k++)
15766                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15767
15768         }
15769
15770         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15771         if( j == backwardMostMove ) i += initialRulePlies;
15772         sprintf(p, "%d ", i);
15773         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15774     }
15775     /* Fullmove number */
15776     sprintf(p, "%d", (move / 2) + 1);
15777
15778     return StrSave(buf);
15779 }
15780
15781 Boolean
15782 ParseFEN(board, blackPlaysFirst, fen)
15783     Board board;
15784      int *blackPlaysFirst;
15785      char *fen;
15786 {
15787     int i, j;
15788     char *p, c;
15789     int emptycount;
15790     ChessSquare piece;
15791
15792     p = fen;
15793
15794     /* [HGM] by default clear Crazyhouse holdings, if present */
15795     if(gameInfo.holdingsWidth) {
15796        for(i=0; i<BOARD_HEIGHT; i++) {
15797            board[i][0]             = EmptySquare; /* black holdings */
15798            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15799            board[i][1]             = (ChessSquare) 0; /* black counts */
15800            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15801        }
15802     }
15803
15804     /* Piece placement data */
15805     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15806         j = 0;
15807         for (;;) {
15808             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15809                 if (*p == '/') p++;
15810                 emptycount = gameInfo.boardWidth - j;
15811                 while (emptycount--)
15812                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15813                 break;
15814 #if(BOARD_FILES >= 10)
15815             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15816                 p++; emptycount=10;
15817                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15818                 while (emptycount--)
15819                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15820 #endif
15821             } else if (isdigit(*p)) {
15822                 emptycount = *p++ - '0';
15823                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15824                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15825                 while (emptycount--)
15826                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15827             } else if (*p == '+' || isalpha(*p)) {
15828                 if (j >= gameInfo.boardWidth) return FALSE;
15829                 if(*p=='+') {
15830                     piece = CharToPiece(*++p);
15831                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15832                     piece = (ChessSquare) (PROMOTED piece ); p++;
15833                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15834                 } else piece = CharToPiece(*p++);
15835
15836                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15837                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15838                     piece = (ChessSquare) (PROMOTED piece);
15839                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15840                     p++;
15841                 }
15842                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15843             } else {
15844                 return FALSE;
15845             }
15846         }
15847     }
15848     while (*p == '/' || *p == ' ') p++;
15849
15850     /* [HGM] look for Crazyhouse holdings here */
15851     while(*p==' ') p++;
15852     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15853         if(*p == '[') p++;
15854         if(*p == '-' ) p++; /* empty holdings */ else {
15855             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15856             /* if we would allow FEN reading to set board size, we would   */
15857             /* have to add holdings and shift the board read so far here   */
15858             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15859                 p++;
15860                 if((int) piece >= (int) BlackPawn ) {
15861                     i = (int)piece - (int)BlackPawn;
15862                     i = PieceToNumber((ChessSquare)i);
15863                     if( i >= gameInfo.holdingsSize ) return FALSE;
15864                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15865                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15866                 } else {
15867                     i = (int)piece - (int)WhitePawn;
15868                     i = PieceToNumber((ChessSquare)i);
15869                     if( i >= gameInfo.holdingsSize ) return FALSE;
15870                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15871                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15872                 }
15873             }
15874         }
15875         if(*p == ']') p++;
15876     }
15877
15878     while(*p == ' ') p++;
15879
15880     /* Active color */
15881     c = *p++;
15882     if(appData.colorNickNames) {
15883       if( c == appData.colorNickNames[0] ) c = 'w'; else
15884       if( c == appData.colorNickNames[1] ) c = 'b';
15885     }
15886     switch (c) {
15887       case 'w':
15888         *blackPlaysFirst = FALSE;
15889         break;
15890       case 'b':
15891         *blackPlaysFirst = TRUE;
15892         break;
15893       default:
15894         return FALSE;
15895     }
15896
15897     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15898     /* return the extra info in global variiables             */
15899
15900     /* set defaults in case FEN is incomplete */
15901     board[EP_STATUS] = EP_UNKNOWN;
15902     for(i=0; i<nrCastlingRights; i++ ) {
15903         board[CASTLING][i] =
15904             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15905     }   /* assume possible unless obviously impossible */
15906     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15907     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15908     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15909                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15910     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15911     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15912     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15913                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15914     FENrulePlies = 0;
15915
15916     while(*p==' ') p++;
15917     if(nrCastlingRights) {
15918       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15919           /* castling indicator present, so default becomes no castlings */
15920           for(i=0; i<nrCastlingRights; i++ ) {
15921                  board[CASTLING][i] = NoRights;
15922           }
15923       }
15924       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15925              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15926              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15927              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15928         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15929
15930         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15931             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15932             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15933         }
15934         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15935             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15936         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15937                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15938         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15939                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15940         switch(c) {
15941           case'K':
15942               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15943               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15944               board[CASTLING][2] = whiteKingFile;
15945               break;
15946           case'Q':
15947               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15948               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15949               board[CASTLING][2] = whiteKingFile;
15950               break;
15951           case'k':
15952               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15953               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15954               board[CASTLING][5] = blackKingFile;
15955               break;
15956           case'q':
15957               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15958               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15959               board[CASTLING][5] = blackKingFile;
15960           case '-':
15961               break;
15962           default: /* FRC castlings */
15963               if(c >= 'a') { /* black rights */
15964                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15965                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15966                   if(i == BOARD_RGHT) break;
15967                   board[CASTLING][5] = i;
15968                   c -= AAA;
15969                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15970                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15971                   if(c > i)
15972                       board[CASTLING][3] = c;
15973                   else
15974                       board[CASTLING][4] = c;
15975               } else { /* white rights */
15976                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15977                     if(board[0][i] == WhiteKing) break;
15978                   if(i == BOARD_RGHT) break;
15979                   board[CASTLING][2] = i;
15980                   c -= AAA - 'a' + 'A';
15981                   if(board[0][c] >= WhiteKing) break;
15982                   if(c > i)
15983                       board[CASTLING][0] = c;
15984                   else
15985                       board[CASTLING][1] = c;
15986               }
15987         }
15988       }
15989       for(i=0; i<nrCastlingRights; i++)
15990         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15991     if (appData.debugMode) {
15992         fprintf(debugFP, "FEN castling rights:");
15993         for(i=0; i<nrCastlingRights; i++)
15994         fprintf(debugFP, " %d", board[CASTLING][i]);
15995         fprintf(debugFP, "\n");
15996     }
15997
15998       while(*p==' ') p++;
15999     }
16000
16001     /* read e.p. field in games that know e.p. capture */
16002     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16003        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16004       if(*p=='-') {
16005         p++; board[EP_STATUS] = EP_NONE;
16006       } else {
16007          char c = *p++ - AAA;
16008
16009          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16010          if(*p >= '0' && *p <='9') p++;
16011          board[EP_STATUS] = c;
16012       }
16013     }
16014
16015
16016     if(sscanf(p, "%d", &i) == 1) {
16017         FENrulePlies = i; /* 50-move ply counter */
16018         /* (The move number is still ignored)    */
16019     }
16020
16021     return TRUE;
16022 }
16023
16024 void
16025 EditPositionPasteFEN(char *fen)
16026 {
16027   if (fen != NULL) {
16028     Board initial_position;
16029
16030     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16031       DisplayError(_("Bad FEN position in clipboard"), 0);
16032       return ;
16033     } else {
16034       int savedBlackPlaysFirst = blackPlaysFirst;
16035       EditPositionEvent();
16036       blackPlaysFirst = savedBlackPlaysFirst;
16037       CopyBoard(boards[0], initial_position);
16038       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16039       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16040       DisplayBothClocks();
16041       DrawPosition(FALSE, boards[currentMove]);
16042     }
16043   }
16044 }
16045
16046 static char cseq[12] = "\\   ";
16047
16048 Boolean set_cont_sequence(char *new_seq)
16049 {
16050     int len;
16051     Boolean ret;
16052
16053     // handle bad attempts to set the sequence
16054         if (!new_seq)
16055                 return 0; // acceptable error - no debug
16056
16057     len = strlen(new_seq);
16058     ret = (len > 0) && (len < sizeof(cseq));
16059     if (ret)
16060       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16061     else if (appData.debugMode)
16062       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16063     return ret;
16064 }
16065
16066 /*
16067     reformat a source message so words don't cross the width boundary.  internal
16068     newlines are not removed.  returns the wrapped size (no null character unless
16069     included in source message).  If dest is NULL, only calculate the size required
16070     for the dest buffer.  lp argument indicats line position upon entry, and it's
16071     passed back upon exit.
16072 */
16073 int wrap(char *dest, char *src, int count, int width, int *lp)
16074 {
16075     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16076
16077     cseq_len = strlen(cseq);
16078     old_line = line = *lp;
16079     ansi = len = clen = 0;
16080
16081     for (i=0; i < count; i++)
16082     {
16083         if (src[i] == '\033')
16084             ansi = 1;
16085
16086         // if we hit the width, back up
16087         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16088         {
16089             // store i & len in case the word is too long
16090             old_i = i, old_len = len;
16091
16092             // find the end of the last word
16093             while (i && src[i] != ' ' && src[i] != '\n')
16094             {
16095                 i--;
16096                 len--;
16097             }
16098
16099             // word too long?  restore i & len before splitting it
16100             if ((old_i-i+clen) >= width)
16101             {
16102                 i = old_i;
16103                 len = old_len;
16104             }
16105
16106             // extra space?
16107             if (i && src[i-1] == ' ')
16108                 len--;
16109
16110             if (src[i] != ' ' && src[i] != '\n')
16111             {
16112                 i--;
16113                 if (len)
16114                     len--;
16115             }
16116
16117             // now append the newline and continuation sequence
16118             if (dest)
16119                 dest[len] = '\n';
16120             len++;
16121             if (dest)
16122                 strncpy(dest+len, cseq, cseq_len);
16123             len += cseq_len;
16124             line = cseq_len;
16125             clen = cseq_len;
16126             continue;
16127         }
16128
16129         if (dest)
16130             dest[len] = src[i];
16131         len++;
16132         if (!ansi)
16133             line++;
16134         if (src[i] == '\n')
16135             line = 0;
16136         if (src[i] == 'm')
16137             ansi = 0;
16138     }
16139     if (dest && appData.debugMode)
16140     {
16141         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16142             count, width, line, len, *lp);
16143         show_bytes(debugFP, src, count);
16144         fprintf(debugFP, "\ndest: ");
16145         show_bytes(debugFP, dest, len);
16146         fprintf(debugFP, "\n");
16147     }
16148     *lp = dest ? line : old_line;
16149
16150     return len;
16151 }
16152
16153 // [HGM] vari: routines for shelving variations
16154
16155 void
16156 PushInner(int firstMove, int lastMove)
16157 {
16158         int i, j, nrMoves = lastMove - firstMove;
16159
16160         // push current tail of game on stack
16161         savedResult[storedGames] = gameInfo.result;
16162         savedDetails[storedGames] = gameInfo.resultDetails;
16163         gameInfo.resultDetails = NULL;
16164         savedFirst[storedGames] = firstMove;
16165         savedLast [storedGames] = lastMove;
16166         savedFramePtr[storedGames] = framePtr;
16167         framePtr -= nrMoves; // reserve space for the boards
16168         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16169             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16170             for(j=0; j<MOVE_LEN; j++)
16171                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16172             for(j=0; j<2*MOVE_LEN; j++)
16173                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16174             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16175             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16176             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16177             pvInfoList[firstMove+i-1].depth = 0;
16178             commentList[framePtr+i] = commentList[firstMove+i];
16179             commentList[firstMove+i] = NULL;
16180         }
16181
16182         storedGames++;
16183         forwardMostMove = firstMove; // truncate game so we can start variation
16184 }
16185
16186 void
16187 PushTail(int firstMove, int lastMove)
16188 {
16189         if(appData.icsActive) { // only in local mode
16190                 forwardMostMove = currentMove; // mimic old ICS behavior
16191                 return;
16192         }
16193         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16194
16195         PushInner(firstMove, lastMove);
16196         if(storedGames == 1) GreyRevert(FALSE);
16197 }
16198
16199 void
16200 PopInner(Boolean annotate)
16201 {
16202         int i, j, nrMoves;
16203         char buf[8000], moveBuf[20];
16204
16205         storedGames--;
16206         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16207         nrMoves = savedLast[storedGames] - currentMove;
16208         if(annotate) {
16209                 int cnt = 10;
16210                 if(!WhiteOnMove(currentMove))
16211                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16212                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16213                 for(i=currentMove; i<forwardMostMove; i++) {
16214                         if(WhiteOnMove(i))
16215                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16216                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16217                         strcat(buf, moveBuf);
16218                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16219                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16220                 }
16221                 strcat(buf, ")");
16222         }
16223         for(i=1; i<=nrMoves; i++) { // copy last variation back
16224             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16225             for(j=0; j<MOVE_LEN; j++)
16226                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16227             for(j=0; j<2*MOVE_LEN; j++)
16228                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16229             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16230             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16231             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16232             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16233             commentList[currentMove+i] = commentList[framePtr+i];
16234             commentList[framePtr+i] = NULL;
16235         }
16236         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16237         framePtr = savedFramePtr[storedGames];
16238         gameInfo.result = savedResult[storedGames];
16239         if(gameInfo.resultDetails != NULL) {
16240             free(gameInfo.resultDetails);
16241       }
16242         gameInfo.resultDetails = savedDetails[storedGames];
16243         forwardMostMove = currentMove + nrMoves;
16244 }
16245
16246 Boolean
16247 PopTail(Boolean annotate)
16248 {
16249         if(appData.icsActive) return FALSE; // only in local mode
16250         if(!storedGames) return FALSE; // sanity
16251         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16252
16253         PopInner(annotate);
16254
16255         if(storedGames == 0) GreyRevert(TRUE);
16256         return TRUE;
16257 }
16258
16259 void
16260 CleanupTail()
16261 {       // remove all shelved variations
16262         int i;
16263         for(i=0; i<storedGames; i++) {
16264             if(savedDetails[i])
16265                 free(savedDetails[i]);
16266             savedDetails[i] = NULL;
16267         }
16268         for(i=framePtr; i<MAX_MOVES; i++) {
16269                 if(commentList[i]) free(commentList[i]);
16270                 commentList[i] = NULL;
16271         }
16272         framePtr = MAX_MOVES-1;
16273         storedGames = 0;
16274 }
16275
16276 void
16277 LoadVariation(int index, char *text)
16278 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16279         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16280         int level = 0, move;
16281
16282         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16283         // first find outermost bracketing variation
16284         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16285             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16286                 if(*p == '{') wait = '}'; else
16287                 if(*p == '[') wait = ']'; else
16288                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16289                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16290             }
16291             if(*p == wait) wait = NULLCHAR; // closing ]} found
16292             p++;
16293         }
16294         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16295         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16296         end[1] = NULLCHAR; // clip off comment beyond variation
16297         ToNrEvent(currentMove-1);
16298         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16299         // kludge: use ParsePV() to append variation to game
16300         move = currentMove;
16301         ParsePV(start, TRUE, TRUE);
16302         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16303         ClearPremoveHighlights();
16304         CommentPopDown();
16305         ToNrEvent(currentMove+1);
16306 }
16307