a0bffa55cc7e24b23c903926a4ba7faa016f220c
[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     cps->supportsNPS = UNKNOWN;
800
801     /* [HGM] options */
802     cps->optionSettings  = appData.engOptions[n];
803
804     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805     cps->isUCI = appData.isUCI[n]; /* [AS] */
806     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807
808     if (appData.protocolVersion[n] > PROTOVER
809         || appData.protocolVersion[n] < 1)
810       {
811         char buf[MSG_SIZ];
812         int len;
813
814         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815                        appData.protocolVersion[n]);
816         if( (len > MSG_SIZ) && appData.debugMode )
817           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818
819         DisplayFatalError(buf, 0, 2);
820       }
821     else
822       {
823         cps->protocolVersion = appData.protocolVersion[n];
824       }
825
826     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
827 }
828
829 ChessProgramState *savCps;
830
831 void
832 LoadEngine()
833 {
834     int i;
835     if(WaitForEngine(savCps, LoadEngine)) return;
836     CommonEngineInit(); // recalculate time odds
837     if(gameInfo.variant != StringToVariant(appData.variant)) {
838         // we changed variant when loading the engine; this forces us to reset
839         Reset(TRUE, savCps != &first);
840         EditGameEvent(); // for consistency with other path, as Reset changes mode
841     }
842     InitChessProgram(savCps, FALSE);
843     SendToProgram("force\n", savCps);
844     DisplayMessage("", "");
845     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
847     ThawUI();
848     SetGNUMode();
849 }
850
851 void
852 ReplaceEngine(ChessProgramState *cps, int n)
853 {
854     EditGameEvent();
855     UnloadEngine(cps);
856     appData.noChessProgram = FALSE;
857     appData.clockMode = TRUE;
858     InitEngine(cps, n);
859     if(n) return; // only startup first engine immediately; second can wait
860     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861     LoadEngine();
862 }
863
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
866
867 void
868 Load(ChessProgramState *cps, int i)
869 {
870     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871     if(engineLine[0]) { // an engine was selected from the combo box
872         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -fn \"\"");
875         ParseArgsFromString(buf);
876         SwapEngines(i);
877         ReplaceEngine(cps, i);
878         return;
879     }
880     p = engineName;
881     while(q = strchr(p, SLASH)) p = q+1;
882     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883     if(engineDir[0] != NULLCHAR)
884         appData.directory[i] = engineDir;
885     else if(p != engineName) { // derive directory from engine path, when not given
886         p[-1] = 0;
887         appData.directory[i] = strdup(engineName);
888         p[-1] = SLASH;
889     } else appData.directory[i] = ".";
890     if(params[0]) {
891         snprintf(command, MSG_SIZ, "%s %s", p, params);
892         p = command;
893     }
894     appData.chessProgram[i] = strdup(p);
895     appData.isUCI[i] = isUCI;
896     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897     appData.hasOwnBookUCI[i] = hasBook;
898     if(!nickName[0]) useNick = FALSE;
899     if(useNick) ASSIGN(appData.pgnName[i], nickName);
900     if(addToList) {
901         int len;
902         q = firstChessProgramNames;
903         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
904         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
905                         useNick ? " -fn \"" : "",
906                         useNick ? nickName : "",
907                         useNick ? "\"" : "",
908                         v1 ? " -firstProtocolVersion 1" : "",
909                         hasBook ? "" : " -fNoOwnBookUCI",
910                         isUCI ? " -fUCI" : "",
911                         storeVariant ? " -variant " : "",
912                         storeVariant ? VariantName(gameInfo.variant) : "");
913         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
914         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
915         if(q)   free(q);
916     }
917     ReplaceEngine(cps, i);
918 }
919
920 void
921 InitTimeControls()
922 {
923     int matched, min, sec;
924     /*
925      * Parse timeControl resource
926      */
927     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
928                           appData.movesPerSession)) {
929         char buf[MSG_SIZ];
930         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
931         DisplayFatalError(buf, 0, 2);
932     }
933
934     /*
935      * Parse searchTime resource
936      */
937     if (*appData.searchTime != NULLCHAR) {
938         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
939         if (matched == 1) {
940             searchTime = min * 60;
941         } else if (matched == 2) {
942             searchTime = min * 60 + sec;
943         } else {
944             char buf[MSG_SIZ];
945             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
946             DisplayFatalError(buf, 0, 2);
947         }
948     }
949 }
950
951 void
952 InitBackEnd1()
953 {
954
955     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
956     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
957
958     GetTimeMark(&programStartTime);
959     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
960     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
961
962     ClearProgramStats();
963     programStats.ok_to_send = 1;
964     programStats.seen_stat = 0;
965
966     /*
967      * Initialize game list
968      */
969     ListNew(&gameList);
970
971
972     /*
973      * Internet chess server status
974      */
975     if (appData.icsActive) {
976         appData.matchMode = FALSE;
977         appData.matchGames = 0;
978 #if ZIPPY
979         appData.noChessProgram = !appData.zippyPlay;
980 #else
981         appData.zippyPlay = FALSE;
982         appData.zippyTalk = FALSE;
983         appData.noChessProgram = TRUE;
984 #endif
985         if (*appData.icsHelper != NULLCHAR) {
986             appData.useTelnet = TRUE;
987             appData.telnetProgram = appData.icsHelper;
988         }
989     } else {
990         appData.zippyTalk = appData.zippyPlay = FALSE;
991     }
992
993     /* [AS] Initialize pv info list [HGM] and game state */
994     {
995         int i, j;
996
997         for( i=0; i<=framePtr; i++ ) {
998             pvInfoList[i].depth = -1;
999             boards[i][EP_STATUS] = EP_NONE;
1000             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1001         }
1002     }
1003
1004     InitTimeControls();
1005
1006     /* [AS] Adjudication threshold */
1007     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1008
1009     InitEngine(&first, 0);
1010     InitEngine(&second, 1);
1011     CommonEngineInit();
1012
1013     if (appData.icsActive) {
1014         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1015     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1016         appData.clockMode = FALSE;
1017         first.sendTime = second.sendTime = 0;
1018     }
1019
1020 #if ZIPPY
1021     /* Override some settings from environment variables, for backward
1022        compatibility.  Unfortunately it's not feasible to have the env
1023        vars just set defaults, at least in xboard.  Ugh.
1024     */
1025     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1026       ZippyInit();
1027     }
1028 #endif
1029
1030     if (!appData.icsActive) {
1031       char buf[MSG_SIZ];
1032       int len;
1033
1034       /* Check for variants that are supported only in ICS mode,
1035          or not at all.  Some that are accepted here nevertheless
1036          have bugs; see comments below.
1037       */
1038       VariantClass variant = StringToVariant(appData.variant);
1039       switch (variant) {
1040       case VariantBughouse:     /* need four players and two boards */
1041       case VariantKriegspiel:   /* need to hide pieces and move details */
1042         /* case VariantFischeRandom: (Fabien: moved below) */
1043         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1044         if( (len > MSG_SIZ) && appData.debugMode )
1045           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1046
1047         DisplayFatalError(buf, 0, 2);
1048         return;
1049
1050       case VariantUnknown:
1051       case VariantLoadable:
1052       case Variant29:
1053       case Variant30:
1054       case Variant31:
1055       case Variant32:
1056       case Variant33:
1057       case Variant34:
1058       case Variant35:
1059       case Variant36:
1060       default:
1061         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1062         if( (len > MSG_SIZ) && appData.debugMode )
1063           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1064
1065         DisplayFatalError(buf, 0, 2);
1066         return;
1067
1068       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1069       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1070       case VariantGothic:     /* [HGM] should work */
1071       case VariantCapablanca: /* [HGM] should work */
1072       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1073       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1074       case VariantKnightmate: /* [HGM] should work */
1075       case VariantCylinder:   /* [HGM] untested */
1076       case VariantFalcon:     /* [HGM] untested */
1077       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1078                                  offboard interposition not understood */
1079       case VariantNormal:     /* definitely works! */
1080       case VariantWildCastle: /* pieces not automatically shuffled */
1081       case VariantNoCastle:   /* pieces not automatically shuffled */
1082       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1083       case VariantLosers:     /* should work except for win condition,
1084                                  and doesn't know captures are mandatory */
1085       case VariantSuicide:    /* should work except for win condition,
1086                                  and doesn't know captures are mandatory */
1087       case VariantGiveaway:   /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantTwoKings:   /* should work */
1090       case VariantAtomic:     /* should work except for win condition */
1091       case Variant3Check:     /* should work except for win condition */
1092       case VariantShatranj:   /* should work except for all win conditions */
1093       case VariantMakruk:     /* should work except for daw countdown */
1094       case VariantBerolina:   /* might work if TestLegality is off */
1095       case VariantCapaRandom: /* should work */
1096       case VariantJanus:      /* should work */
1097       case VariantSuper:      /* experimental */
1098       case VariantGreat:      /* experimental, requires legality testing to be off */
1099       case VariantSChess:     /* S-Chess, should work */
1100       case VariantSpartan:    /* should work */
1101         break;
1102       }
1103     }
1104
1105 }
1106
1107 int NextIntegerFromString( char ** str, long * value )
1108 {
1109     int result = -1;
1110     char * s = *str;
1111
1112     while( *s == ' ' || *s == '\t' ) {
1113         s++;
1114     }
1115
1116     *value = 0;
1117
1118     if( *s >= '0' && *s <= '9' ) {
1119         while( *s >= '0' && *s <= '9' ) {
1120             *value = *value * 10 + (*s - '0');
1121             s++;
1122         }
1123
1124         result = 0;
1125     }
1126
1127     *str = s;
1128
1129     return result;
1130 }
1131
1132 int NextTimeControlFromString( char ** str, long * value )
1133 {
1134     long temp;
1135     int result = NextIntegerFromString( str, &temp );
1136
1137     if( result == 0 ) {
1138         *value = temp * 60; /* Minutes */
1139         if( **str == ':' ) {
1140             (*str)++;
1141             result = NextIntegerFromString( str, &temp );
1142             *value += temp; /* Seconds */
1143         }
1144     }
1145
1146     return result;
1147 }
1148
1149 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1150 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1151     int result = -1, type = 0; long temp, temp2;
1152
1153     if(**str != ':') return -1; // old params remain in force!
1154     (*str)++;
1155     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1156     if( NextIntegerFromString( str, &temp ) ) return -1;
1157     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1158
1159     if(**str != '/') {
1160         /* time only: incremental or sudden-death time control */
1161         if(**str == '+') { /* increment follows; read it */
1162             (*str)++;
1163             if(**str == '!') type = *(*str)++; // Bronstein TC
1164             if(result = NextIntegerFromString( str, &temp2)) return -1;
1165             *inc = temp2 * 1000;
1166             if(**str == '.') { // read fraction of increment
1167                 char *start = ++(*str);
1168                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1169                 temp2 *= 1000;
1170                 while(start++ < *str) temp2 /= 10;
1171                 *inc += temp2;
1172             }
1173         } else *inc = 0;
1174         *moves = 0; *tc = temp * 1000; *incType = type;
1175         return 0;
1176     }
1177
1178     (*str)++; /* classical time control */
1179     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1180
1181     if(result == 0) {
1182         *moves = temp;
1183         *tc    = temp2 * 1000;
1184         *inc   = 0;
1185         *incType = type;
1186     }
1187     return result;
1188 }
1189
1190 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1191 {   /* [HGM] get time to add from the multi-session time-control string */
1192     int incType, moves=1; /* kludge to force reading of first session */
1193     long time, increment;
1194     char *s = tcString;
1195
1196     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1197     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1198     do {
1199         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1200         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1201         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1202         if(movenr == -1) return time;    /* last move before new session     */
1203         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1204         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1205         if(!moves) return increment;     /* current session is incremental   */
1206         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1207     } while(movenr >= -1);               /* try again for next session       */
1208
1209     return 0; // no new time quota on this move
1210 }
1211
1212 int
1213 ParseTimeControl(tc, ti, mps)
1214      char *tc;
1215      float ti;
1216      int mps;
1217 {
1218   long tc1;
1219   long tc2;
1220   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1221   int min, sec=0;
1222
1223   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1224   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1225       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1226   if(ti > 0) {
1227
1228     if(mps)
1229       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1230     else 
1231       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1232   } else {
1233     if(mps)
1234       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1235     else 
1236       snprintf(buf, MSG_SIZ, ":%s", mytc);
1237   }
1238   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1239   
1240   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1241     return FALSE;
1242   }
1243
1244   if( *tc == '/' ) {
1245     /* Parse second time control */
1246     tc++;
1247
1248     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1249       return FALSE;
1250     }
1251
1252     if( tc2 == 0 ) {
1253       return FALSE;
1254     }
1255
1256     timeControl_2 = tc2 * 1000;
1257   }
1258   else {
1259     timeControl_2 = 0;
1260   }
1261
1262   if( tc1 == 0 ) {
1263     return FALSE;
1264   }
1265
1266   timeControl = tc1 * 1000;
1267
1268   if (ti >= 0) {
1269     timeIncrement = ti * 1000;  /* convert to ms */
1270     movesPerSession = 0;
1271   } else {
1272     timeIncrement = 0;
1273     movesPerSession = mps;
1274   }
1275   return TRUE;
1276 }
1277
1278 void
1279 InitBackEnd2()
1280 {
1281     if (appData.debugMode) {
1282         fprintf(debugFP, "%s\n", programVersion);
1283     }
1284
1285     set_cont_sequence(appData.wrapContSeq);
1286     if (appData.matchGames > 0) {
1287         appData.matchMode = TRUE;
1288     } else if (appData.matchMode) {
1289         appData.matchGames = 1;
1290     }
1291     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1292         appData.matchGames = appData.sameColorGames;
1293     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1294         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1295         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1296     }
1297     Reset(TRUE, FALSE);
1298     if (appData.noChessProgram || first.protocolVersion == 1) {
1299       InitBackEnd3();
1300     } else {
1301       /* kludge: allow timeout for initial "feature" commands */
1302       FreezeUI();
1303       DisplayMessage("", _("Starting chess program"));
1304       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1305     }
1306 }
1307
1308 int
1309 CalculateIndex(int index, int gameNr)
1310 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1311     int res;
1312     if(index > 0) return index; // fixed nmber
1313     if(index == 0) return 1;
1314     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1315     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1316     return res;
1317 }
1318
1319 int
1320 LoadGameOrPosition(int gameNr)
1321 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1322     if (*appData.loadGameFile != NULLCHAR) {
1323         if (!LoadGameFromFile(appData.loadGameFile,
1324                 CalculateIndex(appData.loadGameIndex, gameNr),
1325                               appData.loadGameFile, FALSE)) {
1326             DisplayFatalError(_("Bad game file"), 0, 1);
1327             return 0;
1328         }
1329     } else if (*appData.loadPositionFile != NULLCHAR) {
1330         if (!LoadPositionFromFile(appData.loadPositionFile,
1331                 CalculateIndex(appData.loadPositionIndex, gameNr),
1332                                   appData.loadPositionFile)) {
1333             DisplayFatalError(_("Bad position file"), 0, 1);
1334             return 0;
1335         }
1336     }
1337     return 1;
1338 }
1339
1340 void
1341 ReserveGame(int gameNr, char resChar)
1342 {
1343     FILE *tf = fopen(appData.tourneyFile, "r+");
1344     char *p, *q, c, buf[MSG_SIZ];
1345     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1346     safeStrCpy(buf, lastMsg, MSG_SIZ);
1347     DisplayMessage(_("Pick new game"), "");
1348     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1349     ParseArgsFromFile(tf);
1350     p = q = appData.results;
1351     if(appData.debugMode) {
1352       char *r = appData.participants;
1353       fprintf(debugFP, "results = '%s'\n", p);
1354       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1355       fprintf(debugFP, "\n");
1356     }
1357     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1358     nextGame = q - p;
1359     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1360     safeStrCpy(q, p, strlen(p) + 2);
1361     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1362     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1363     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1364         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1365         q[nextGame] = '*';
1366     }
1367     fseek(tf, -(strlen(p)+4), SEEK_END);
1368     c = fgetc(tf);
1369     if(c != '"') // depending on DOS or Unix line endings we can be one off
1370          fseek(tf, -(strlen(p)+2), SEEK_END);
1371     else fseek(tf, -(strlen(p)+3), SEEK_END);
1372     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1373     DisplayMessage(buf, "");
1374     free(p); appData.results = q;
1375     if(nextGame <= appData.matchGames && resChar != ' ' &&
1376        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1377         UnloadEngine(&first);  // next game belongs to other pairing;
1378         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1379     }
1380 }
1381
1382 void
1383 MatchEvent(int mode)
1384 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1385         int dummy;
1386         if(matchMode) { // already in match mode: switch it off
1387             abortMatch = TRUE;
1388             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1389             ModeHighlight(); // kludgey way to remove checkmark...
1390             return;
1391         }
1392 //      if(gameMode != BeginningOfGame) {
1393 //          DisplayError(_("You can only start a match from the initial position."), 0);
1394 //          return;
1395 //      }
1396         abortMatch = FALSE;
1397         appData.matchGames = appData.defaultMatchGames;
1398         /* Set up machine vs. machine match */
1399         nextGame = 0;
1400         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1401         if(appData.tourneyFile[0]) {
1402             ReserveGame(-1, 0);
1403             if(nextGame > appData.matchGames) {
1404                 char buf[MSG_SIZ];
1405                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1406                 DisplayError(buf, 0);
1407                 appData.tourneyFile[0] = 0;
1408                 return;
1409             }
1410         } else
1411         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1412             DisplayFatalError(_("Can't have a match with no chess programs"),
1413                               0, 2);
1414             return;
1415         }
1416         matchMode = mode;
1417         matchGame = roundNr = 1;
1418         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1419         NextMatchGame();
1420 }
1421
1422 void
1423 InitBackEnd3 P((void))
1424 {
1425     GameMode initialMode;
1426     char buf[MSG_SIZ];
1427     int err, len;
1428
1429     InitChessProgram(&first, startedFromSetupPosition);
1430
1431     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1432         free(programVersion);
1433         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1434         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1435     }
1436
1437     if (appData.icsActive) {
1438 #ifdef WIN32
1439         /* [DM] Make a console window if needed [HGM] merged ifs */
1440         ConsoleCreate();
1441 #endif
1442         err = establish();
1443         if (err != 0)
1444           {
1445             if (*appData.icsCommPort != NULLCHAR)
1446               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1447                              appData.icsCommPort);
1448             else
1449               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1450                         appData.icsHost, appData.icsPort);
1451
1452             if( (len > MSG_SIZ) && appData.debugMode )
1453               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1454
1455             DisplayFatalError(buf, err, 1);
1456             return;
1457         }
1458         SetICSMode();
1459         telnetISR =
1460           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1461         fromUserISR =
1462           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1463         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1464             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1465     } else if (appData.noChessProgram) {
1466         SetNCPMode();
1467     } else {
1468         SetGNUMode();
1469     }
1470
1471     if (*appData.cmailGameName != NULLCHAR) {
1472         SetCmailMode();
1473         OpenLoopback(&cmailPR);
1474         cmailISR =
1475           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1476     }
1477
1478     ThawUI();
1479     DisplayMessage("", "");
1480     if (StrCaseCmp(appData.initialMode, "") == 0) {
1481       initialMode = BeginningOfGame;
1482       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1483         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1484         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1485         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1486         ModeHighlight();
1487       }
1488     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1489       initialMode = TwoMachinesPlay;
1490     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1491       initialMode = AnalyzeFile;
1492     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1493       initialMode = AnalyzeMode;
1494     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1495       initialMode = MachinePlaysWhite;
1496     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1497       initialMode = MachinePlaysBlack;
1498     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1499       initialMode = EditGame;
1500     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1501       initialMode = EditPosition;
1502     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1503       initialMode = Training;
1504     } else {
1505       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1506       if( (len > MSG_SIZ) && appData.debugMode )
1507         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1508
1509       DisplayFatalError(buf, 0, 2);
1510       return;
1511     }
1512
1513     if (appData.matchMode) {
1514         if(appData.tourneyFile[0]) { // start tourney from command line
1515             FILE *f;
1516             if(f = fopen(appData.tourneyFile, "r")) {
1517                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1518                 fclose(f);
1519             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1520         }
1521         MatchEvent(TRUE);
1522     } else if (*appData.cmailGameName != NULLCHAR) {
1523         /* Set up cmail mode */
1524         ReloadCmailMsgEvent(TRUE);
1525     } else {
1526         /* Set up other modes */
1527         if (initialMode == AnalyzeFile) {
1528           if (*appData.loadGameFile == NULLCHAR) {
1529             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1530             return;
1531           }
1532         }
1533         if (*appData.loadGameFile != NULLCHAR) {
1534             (void) LoadGameFromFile(appData.loadGameFile,
1535                                     appData.loadGameIndex,
1536                                     appData.loadGameFile, TRUE);
1537         } else if (*appData.loadPositionFile != NULLCHAR) {
1538             (void) LoadPositionFromFile(appData.loadPositionFile,
1539                                         appData.loadPositionIndex,
1540                                         appData.loadPositionFile);
1541             /* [HGM] try to make self-starting even after FEN load */
1542             /* to allow automatic setup of fairy variants with wtm */
1543             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1544                 gameMode = BeginningOfGame;
1545                 setboardSpoiledMachineBlack = 1;
1546             }
1547             /* [HGM] loadPos: make that every new game uses the setup */
1548             /* from file as long as we do not switch variant          */
1549             if(!blackPlaysFirst) {
1550                 startedFromPositionFile = TRUE;
1551                 CopyBoard(filePosition, boards[0]);
1552             }
1553         }
1554         if (initialMode == AnalyzeMode) {
1555           if (appData.noChessProgram) {
1556             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1557             return;
1558           }
1559           if (appData.icsActive) {
1560             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1561             return;
1562           }
1563           AnalyzeModeEvent();
1564         } else if (initialMode == AnalyzeFile) {
1565           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1566           ShowThinkingEvent();
1567           AnalyzeFileEvent();
1568           AnalysisPeriodicEvent(1);
1569         } else if (initialMode == MachinePlaysWhite) {
1570           if (appData.noChessProgram) {
1571             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1572                               0, 2);
1573             return;
1574           }
1575           if (appData.icsActive) {
1576             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1577                               0, 2);
1578             return;
1579           }
1580           MachineWhiteEvent();
1581         } else if (initialMode == MachinePlaysBlack) {
1582           if (appData.noChessProgram) {
1583             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1584                               0, 2);
1585             return;
1586           }
1587           if (appData.icsActive) {
1588             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1589                               0, 2);
1590             return;
1591           }
1592           MachineBlackEvent();
1593         } else if (initialMode == TwoMachinesPlay) {
1594           if (appData.noChessProgram) {
1595             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1596                               0, 2);
1597             return;
1598           }
1599           if (appData.icsActive) {
1600             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1601                               0, 2);
1602             return;
1603           }
1604           TwoMachinesEvent();
1605         } else if (initialMode == EditGame) {
1606           EditGameEvent();
1607         } else if (initialMode == EditPosition) {
1608           EditPositionEvent();
1609         } else if (initialMode == Training) {
1610           if (*appData.loadGameFile == NULLCHAR) {
1611             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1612             return;
1613           }
1614           TrainingEvent();
1615         }
1616     }
1617 }
1618
1619 /*
1620  * Establish will establish a contact to a remote host.port.
1621  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1622  *  used to talk to the host.
1623  * Returns 0 if okay, error code if not.
1624  */
1625 int
1626 establish()
1627 {
1628     char buf[MSG_SIZ];
1629
1630     if (*appData.icsCommPort != NULLCHAR) {
1631         /* Talk to the host through a serial comm port */
1632         return OpenCommPort(appData.icsCommPort, &icsPR);
1633
1634     } else if (*appData.gateway != NULLCHAR) {
1635         if (*appData.remoteShell == NULLCHAR) {
1636             /* Use the rcmd protocol to run telnet program on a gateway host */
1637             snprintf(buf, sizeof(buf), "%s %s %s",
1638                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1639             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1640
1641         } else {
1642             /* Use the rsh program to run telnet program on a gateway host */
1643             if (*appData.remoteUser == NULLCHAR) {
1644                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1645                         appData.gateway, appData.telnetProgram,
1646                         appData.icsHost, appData.icsPort);
1647             } else {
1648                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1649                         appData.remoteShell, appData.gateway,
1650                         appData.remoteUser, appData.telnetProgram,
1651                         appData.icsHost, appData.icsPort);
1652             }
1653             return StartChildProcess(buf, "", &icsPR);
1654
1655         }
1656     } else if (appData.useTelnet) {
1657         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1658
1659     } else {
1660         /* TCP socket interface differs somewhat between
1661            Unix and NT; handle details in the front end.
1662            */
1663         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1664     }
1665 }
1666
1667 void EscapeExpand(char *p, char *q)
1668 {       // [HGM] initstring: routine to shape up string arguments
1669         while(*p++ = *q++) if(p[-1] == '\\')
1670             switch(*q++) {
1671                 case 'n': p[-1] = '\n'; break;
1672                 case 'r': p[-1] = '\r'; break;
1673                 case 't': p[-1] = '\t'; break;
1674                 case '\\': p[-1] = '\\'; break;
1675                 case 0: *p = 0; return;
1676                 default: p[-1] = q[-1]; break;
1677             }
1678 }
1679
1680 void
1681 show_bytes(fp, buf, count)
1682      FILE *fp;
1683      char *buf;
1684      int count;
1685 {
1686     while (count--) {
1687         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1688             fprintf(fp, "\\%03o", *buf & 0xff);
1689         } else {
1690             putc(*buf, fp);
1691         }
1692         buf++;
1693     }
1694     fflush(fp);
1695 }
1696
1697 /* Returns an errno value */
1698 int
1699 OutputMaybeTelnet(pr, message, count, outError)
1700      ProcRef pr;
1701      char *message;
1702      int count;
1703      int *outError;
1704 {
1705     char buf[8192], *p, *q, *buflim;
1706     int left, newcount, outcount;
1707
1708     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1709         *appData.gateway != NULLCHAR) {
1710         if (appData.debugMode) {
1711             fprintf(debugFP, ">ICS: ");
1712             show_bytes(debugFP, message, count);
1713             fprintf(debugFP, "\n");
1714         }
1715         return OutputToProcess(pr, message, count, outError);
1716     }
1717
1718     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1719     p = message;
1720     q = buf;
1721     left = count;
1722     newcount = 0;
1723     while (left) {
1724         if (q >= buflim) {
1725             if (appData.debugMode) {
1726                 fprintf(debugFP, ">ICS: ");
1727                 show_bytes(debugFP, buf, newcount);
1728                 fprintf(debugFP, "\n");
1729             }
1730             outcount = OutputToProcess(pr, buf, newcount, outError);
1731             if (outcount < newcount) return -1; /* to be sure */
1732             q = buf;
1733             newcount = 0;
1734         }
1735         if (*p == '\n') {
1736             *q++ = '\r';
1737             newcount++;
1738         } else if (((unsigned char) *p) == TN_IAC) {
1739             *q++ = (char) TN_IAC;
1740             newcount ++;
1741         }
1742         *q++ = *p++;
1743         newcount++;
1744         left--;
1745     }
1746     if (appData.debugMode) {
1747         fprintf(debugFP, ">ICS: ");
1748         show_bytes(debugFP, buf, newcount);
1749         fprintf(debugFP, "\n");
1750     }
1751     outcount = OutputToProcess(pr, buf, newcount, outError);
1752     if (outcount < newcount) return -1; /* to be sure */
1753     return count;
1754 }
1755
1756 void
1757 read_from_player(isr, closure, message, count, error)
1758      InputSourceRef isr;
1759      VOIDSTAR closure;
1760      char *message;
1761      int count;
1762      int error;
1763 {
1764     int outError, outCount;
1765     static int gotEof = 0;
1766
1767     /* Pass data read from player on to ICS */
1768     if (count > 0) {
1769         gotEof = 0;
1770         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1771         if (outCount < count) {
1772             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1773         }
1774     } else if (count < 0) {
1775         RemoveInputSource(isr);
1776         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1777     } else if (gotEof++ > 0) {
1778         RemoveInputSource(isr);
1779         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1780     }
1781 }
1782
1783 void
1784 KeepAlive()
1785 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1786     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1787     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1788     SendToICS("date\n");
1789     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1790 }
1791
1792 /* added routine for printf style output to ics */
1793 void ics_printf(char *format, ...)
1794 {
1795     char buffer[MSG_SIZ];
1796     va_list args;
1797
1798     va_start(args, format);
1799     vsnprintf(buffer, sizeof(buffer), format, args);
1800     buffer[sizeof(buffer)-1] = '\0';
1801     SendToICS(buffer);
1802     va_end(args);
1803 }
1804
1805 void
1806 SendToICS(s)
1807      char *s;
1808 {
1809     int count, outCount, outError;
1810
1811     if (icsPR == NULL) return;
1812
1813     count = strlen(s);
1814     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1815     if (outCount < count) {
1816         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1817     }
1818 }
1819
1820 /* This is used for sending logon scripts to the ICS. Sending
1821    without a delay causes problems when using timestamp on ICC
1822    (at least on my machine). */
1823 void
1824 SendToICSDelayed(s,msdelay)
1825      char *s;
1826      long msdelay;
1827 {
1828     int count, outCount, outError;
1829
1830     if (icsPR == NULL) return;
1831
1832     count = strlen(s);
1833     if (appData.debugMode) {
1834         fprintf(debugFP, ">ICS: ");
1835         show_bytes(debugFP, s, count);
1836         fprintf(debugFP, "\n");
1837     }
1838     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1839                                       msdelay);
1840     if (outCount < count) {
1841         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1842     }
1843 }
1844
1845
1846 /* Remove all highlighting escape sequences in s
1847    Also deletes any suffix starting with '('
1848    */
1849 char *
1850 StripHighlightAndTitle(s)
1851      char *s;
1852 {
1853     static char retbuf[MSG_SIZ];
1854     char *p = retbuf;
1855
1856     while (*s != NULLCHAR) {
1857         while (*s == '\033') {
1858             while (*s != NULLCHAR && !isalpha(*s)) s++;
1859             if (*s != NULLCHAR) s++;
1860         }
1861         while (*s != NULLCHAR && *s != '\033') {
1862             if (*s == '(' || *s == '[') {
1863                 *p = NULLCHAR;
1864                 return retbuf;
1865             }
1866             *p++ = *s++;
1867         }
1868     }
1869     *p = NULLCHAR;
1870     return retbuf;
1871 }
1872
1873 /* Remove all highlighting escape sequences in s */
1874 char *
1875 StripHighlight(s)
1876      char *s;
1877 {
1878     static char retbuf[MSG_SIZ];
1879     char *p = retbuf;
1880
1881     while (*s != NULLCHAR) {
1882         while (*s == '\033') {
1883             while (*s != NULLCHAR && !isalpha(*s)) s++;
1884             if (*s != NULLCHAR) s++;
1885         }
1886         while (*s != NULLCHAR && *s != '\033') {
1887             *p++ = *s++;
1888         }
1889     }
1890     *p = NULLCHAR;
1891     return retbuf;
1892 }
1893
1894 char *variantNames[] = VARIANT_NAMES;
1895 char *
1896 VariantName(v)
1897      VariantClass v;
1898 {
1899     return variantNames[v];
1900 }
1901
1902
1903 /* Identify a variant from the strings the chess servers use or the
1904    PGN Variant tag names we use. */
1905 VariantClass
1906 StringToVariant(e)
1907      char *e;
1908 {
1909     char *p;
1910     int wnum = -1;
1911     VariantClass v = VariantNormal;
1912     int i, found = FALSE;
1913     char buf[MSG_SIZ];
1914     int len;
1915
1916     if (!e) return v;
1917
1918     /* [HGM] skip over optional board-size prefixes */
1919     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1920         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1921         while( *e++ != '_');
1922     }
1923
1924     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1925         v = VariantNormal;
1926         found = TRUE;
1927     } else
1928     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1929       if (StrCaseStr(e, variantNames[i])) {
1930         v = (VariantClass) i;
1931         found = TRUE;
1932         break;
1933       }
1934     }
1935
1936     if (!found) {
1937       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1938           || StrCaseStr(e, "wild/fr")
1939           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1940         v = VariantFischeRandom;
1941       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1942                  (i = 1, p = StrCaseStr(e, "w"))) {
1943         p += i;
1944         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1945         if (isdigit(*p)) {
1946           wnum = atoi(p);
1947         } else {
1948           wnum = -1;
1949         }
1950         switch (wnum) {
1951         case 0: /* FICS only, actually */
1952         case 1:
1953           /* Castling legal even if K starts on d-file */
1954           v = VariantWildCastle;
1955           break;
1956         case 2:
1957         case 3:
1958         case 4:
1959           /* Castling illegal even if K & R happen to start in
1960              normal positions. */
1961           v = VariantNoCastle;
1962           break;
1963         case 5:
1964         case 7:
1965         case 8:
1966         case 10:
1967         case 11:
1968         case 12:
1969         case 13:
1970         case 14:
1971         case 15:
1972         case 18:
1973         case 19:
1974           /* Castling legal iff K & R start in normal positions */
1975           v = VariantNormal;
1976           break;
1977         case 6:
1978         case 20:
1979         case 21:
1980           /* Special wilds for position setup; unclear what to do here */
1981           v = VariantLoadable;
1982           break;
1983         case 9:
1984           /* Bizarre ICC game */
1985           v = VariantTwoKings;
1986           break;
1987         case 16:
1988           v = VariantKriegspiel;
1989           break;
1990         case 17:
1991           v = VariantLosers;
1992           break;
1993         case 22:
1994           v = VariantFischeRandom;
1995           break;
1996         case 23:
1997           v = VariantCrazyhouse;
1998           break;
1999         case 24:
2000           v = VariantBughouse;
2001           break;
2002         case 25:
2003           v = Variant3Check;
2004           break;
2005         case 26:
2006           /* Not quite the same as FICS suicide! */
2007           v = VariantGiveaway;
2008           break;
2009         case 27:
2010           v = VariantAtomic;
2011           break;
2012         case 28:
2013           v = VariantShatranj;
2014           break;
2015
2016         /* Temporary names for future ICC types.  The name *will* change in
2017            the next xboard/WinBoard release after ICC defines it. */
2018         case 29:
2019           v = Variant29;
2020           break;
2021         case 30:
2022           v = Variant30;
2023           break;
2024         case 31:
2025           v = Variant31;
2026           break;
2027         case 32:
2028           v = Variant32;
2029           break;
2030         case 33:
2031           v = Variant33;
2032           break;
2033         case 34:
2034           v = Variant34;
2035           break;
2036         case 35:
2037           v = Variant35;
2038           break;
2039         case 36:
2040           v = Variant36;
2041           break;
2042         case 37:
2043           v = VariantShogi;
2044           break;
2045         case 38:
2046           v = VariantXiangqi;
2047           break;
2048         case 39:
2049           v = VariantCourier;
2050           break;
2051         case 40:
2052           v = VariantGothic;
2053           break;
2054         case 41:
2055           v = VariantCapablanca;
2056           break;
2057         case 42:
2058           v = VariantKnightmate;
2059           break;
2060         case 43:
2061           v = VariantFairy;
2062           break;
2063         case 44:
2064           v = VariantCylinder;
2065           break;
2066         case 45:
2067           v = VariantFalcon;
2068           break;
2069         case 46:
2070           v = VariantCapaRandom;
2071           break;
2072         case 47:
2073           v = VariantBerolina;
2074           break;
2075         case 48:
2076           v = VariantJanus;
2077           break;
2078         case 49:
2079           v = VariantSuper;
2080           break;
2081         case 50:
2082           v = VariantGreat;
2083           break;
2084         case -1:
2085           /* Found "wild" or "w" in the string but no number;
2086              must assume it's normal chess. */
2087           v = VariantNormal;
2088           break;
2089         default:
2090           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2091           if( (len > MSG_SIZ) && appData.debugMode )
2092             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2093
2094           DisplayError(buf, 0);
2095           v = VariantUnknown;
2096           break;
2097         }
2098       }
2099     }
2100     if (appData.debugMode) {
2101       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2102               e, wnum, VariantName(v));
2103     }
2104     return v;
2105 }
2106
2107 static int leftover_start = 0, leftover_len = 0;
2108 char star_match[STAR_MATCH_N][MSG_SIZ];
2109
2110 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2111    advance *index beyond it, and set leftover_start to the new value of
2112    *index; else return FALSE.  If pattern contains the character '*', it
2113    matches any sequence of characters not containing '\r', '\n', or the
2114    character following the '*' (if any), and the matched sequence(s) are
2115    copied into star_match.
2116    */
2117 int
2118 looking_at(buf, index, pattern)
2119      char *buf;
2120      int *index;
2121      char *pattern;
2122 {
2123     char *bufp = &buf[*index], *patternp = pattern;
2124     int star_count = 0;
2125     char *matchp = star_match[0];
2126
2127     for (;;) {
2128         if (*patternp == NULLCHAR) {
2129             *index = leftover_start = bufp - buf;
2130             *matchp = NULLCHAR;
2131             return TRUE;
2132         }
2133         if (*bufp == NULLCHAR) return FALSE;
2134         if (*patternp == '*') {
2135             if (*bufp == *(patternp + 1)) {
2136                 *matchp = NULLCHAR;
2137                 matchp = star_match[++star_count];
2138                 patternp += 2;
2139                 bufp++;
2140                 continue;
2141             } else if (*bufp == '\n' || *bufp == '\r') {
2142                 patternp++;
2143                 if (*patternp == NULLCHAR)
2144                   continue;
2145                 else
2146                   return FALSE;
2147             } else {
2148                 *matchp++ = *bufp++;
2149                 continue;
2150             }
2151         }
2152         if (*patternp != *bufp) return FALSE;
2153         patternp++;
2154         bufp++;
2155     }
2156 }
2157
2158 void
2159 SendToPlayer(data, length)
2160      char *data;
2161      int length;
2162 {
2163     int error, outCount;
2164     outCount = OutputToProcess(NoProc, data, length, &error);
2165     if (outCount < length) {
2166         DisplayFatalError(_("Error writing to display"), error, 1);
2167     }
2168 }
2169
2170 void
2171 PackHolding(packed, holding)
2172      char packed[];
2173      char *holding;
2174 {
2175     char *p = holding;
2176     char *q = packed;
2177     int runlength = 0;
2178     int curr = 9999;
2179     do {
2180         if (*p == curr) {
2181             runlength++;
2182         } else {
2183             switch (runlength) {
2184               case 0:
2185                 break;
2186               case 1:
2187                 *q++ = curr;
2188                 break;
2189               case 2:
2190                 *q++ = curr;
2191                 *q++ = curr;
2192                 break;
2193               default:
2194                 sprintf(q, "%d", runlength);
2195                 while (*q) q++;
2196                 *q++ = curr;
2197                 break;
2198             }
2199             runlength = 1;
2200             curr = *p;
2201         }
2202     } while (*p++);
2203     *q = NULLCHAR;
2204 }
2205
2206 /* Telnet protocol requests from the front end */
2207 void
2208 TelnetRequest(ddww, option)
2209      unsigned char ddww, option;
2210 {
2211     unsigned char msg[3];
2212     int outCount, outError;
2213
2214     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2215
2216     if (appData.debugMode) {
2217         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2218         switch (ddww) {
2219           case TN_DO:
2220             ddwwStr = "DO";
2221             break;
2222           case TN_DONT:
2223             ddwwStr = "DONT";
2224             break;
2225           case TN_WILL:
2226             ddwwStr = "WILL";
2227             break;
2228           case TN_WONT:
2229             ddwwStr = "WONT";
2230             break;
2231           default:
2232             ddwwStr = buf1;
2233             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2234             break;
2235         }
2236         switch (option) {
2237           case TN_ECHO:
2238             optionStr = "ECHO";
2239             break;
2240           default:
2241             optionStr = buf2;
2242             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2243             break;
2244         }
2245         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2246     }
2247     msg[0] = TN_IAC;
2248     msg[1] = ddww;
2249     msg[2] = option;
2250     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2251     if (outCount < 3) {
2252         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2253     }
2254 }
2255
2256 void
2257 DoEcho()
2258 {
2259     if (!appData.icsActive) return;
2260     TelnetRequest(TN_DO, TN_ECHO);
2261 }
2262
2263 void
2264 DontEcho()
2265 {
2266     if (!appData.icsActive) return;
2267     TelnetRequest(TN_DONT, TN_ECHO);
2268 }
2269
2270 void
2271 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2272 {
2273     /* put the holdings sent to us by the server on the board holdings area */
2274     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2275     char p;
2276     ChessSquare piece;
2277
2278     if(gameInfo.holdingsWidth < 2)  return;
2279     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2280         return; // prevent overwriting by pre-board holdings
2281
2282     if( (int)lowestPiece >= BlackPawn ) {
2283         holdingsColumn = 0;
2284         countsColumn = 1;
2285         holdingsStartRow = BOARD_HEIGHT-1;
2286         direction = -1;
2287     } else {
2288         holdingsColumn = BOARD_WIDTH-1;
2289         countsColumn = BOARD_WIDTH-2;
2290         holdingsStartRow = 0;
2291         direction = 1;
2292     }
2293
2294     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2295         board[i][holdingsColumn] = EmptySquare;
2296         board[i][countsColumn]   = (ChessSquare) 0;
2297     }
2298     while( (p=*holdings++) != NULLCHAR ) {
2299         piece = CharToPiece( ToUpper(p) );
2300         if(piece == EmptySquare) continue;
2301         /*j = (int) piece - (int) WhitePawn;*/
2302         j = PieceToNumber(piece);
2303         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2304         if(j < 0) continue;               /* should not happen */
2305         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2306         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2307         board[holdingsStartRow+j*direction][countsColumn]++;
2308     }
2309 }
2310
2311
2312 void
2313 VariantSwitch(Board board, VariantClass newVariant)
2314 {
2315    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2316    static Board oldBoard;
2317
2318    startedFromPositionFile = FALSE;
2319    if(gameInfo.variant == newVariant) return;
2320
2321    /* [HGM] This routine is called each time an assignment is made to
2322     * gameInfo.variant during a game, to make sure the board sizes
2323     * are set to match the new variant. If that means adding or deleting
2324     * holdings, we shift the playing board accordingly
2325     * This kludge is needed because in ICS observe mode, we get boards
2326     * of an ongoing game without knowing the variant, and learn about the
2327     * latter only later. This can be because of the move list we requested,
2328     * in which case the game history is refilled from the beginning anyway,
2329     * but also when receiving holdings of a crazyhouse game. In the latter
2330     * case we want to add those holdings to the already received position.
2331     */
2332
2333
2334    if (appData.debugMode) {
2335      fprintf(debugFP, "Switch board from %s to %s\n",
2336              VariantName(gameInfo.variant), VariantName(newVariant));
2337      setbuf(debugFP, NULL);
2338    }
2339    shuffleOpenings = 0;       /* [HGM] shuffle */
2340    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2341    switch(newVariant)
2342      {
2343      case VariantShogi:
2344        newWidth = 9;  newHeight = 9;
2345        gameInfo.holdingsSize = 7;
2346      case VariantBughouse:
2347      case VariantCrazyhouse:
2348        newHoldingsWidth = 2; break;
2349      case VariantGreat:
2350        newWidth = 10;
2351      case VariantSuper:
2352        newHoldingsWidth = 2;
2353        gameInfo.holdingsSize = 8;
2354        break;
2355      case VariantGothic:
2356      case VariantCapablanca:
2357      case VariantCapaRandom:
2358        newWidth = 10;
2359      default:
2360        newHoldingsWidth = gameInfo.holdingsSize = 0;
2361      };
2362
2363    if(newWidth  != gameInfo.boardWidth  ||
2364       newHeight != gameInfo.boardHeight ||
2365       newHoldingsWidth != gameInfo.holdingsWidth ) {
2366
2367      /* shift position to new playing area, if needed */
2368      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2369        for(i=0; i<BOARD_HEIGHT; i++)
2370          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2371            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2372              board[i][j];
2373        for(i=0; i<newHeight; i++) {
2374          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2375          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2376        }
2377      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2378        for(i=0; i<BOARD_HEIGHT; i++)
2379          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2380            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2381              board[i][j];
2382      }
2383      gameInfo.boardWidth  = newWidth;
2384      gameInfo.boardHeight = newHeight;
2385      gameInfo.holdingsWidth = newHoldingsWidth;
2386      gameInfo.variant = newVariant;
2387      InitDrawingSizes(-2, 0);
2388    } else gameInfo.variant = newVariant;
2389    CopyBoard(oldBoard, board);   // remember correctly formatted board
2390      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2391    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2392 }
2393
2394 static int loggedOn = FALSE;
2395
2396 /*-- Game start info cache: --*/
2397 int gs_gamenum;
2398 char gs_kind[MSG_SIZ];
2399 static char player1Name[128] = "";
2400 static char player2Name[128] = "";
2401 static char cont_seq[] = "\n\\   ";
2402 static int player1Rating = -1;
2403 static int player2Rating = -1;
2404 /*----------------------------*/
2405
2406 ColorClass curColor = ColorNormal;
2407 int suppressKibitz = 0;
2408
2409 // [HGM] seekgraph
2410 Boolean soughtPending = FALSE;
2411 Boolean seekGraphUp;
2412 #define MAX_SEEK_ADS 200
2413 #define SQUARE 0x80
2414 char *seekAdList[MAX_SEEK_ADS];
2415 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2416 float tcList[MAX_SEEK_ADS];
2417 char colorList[MAX_SEEK_ADS];
2418 int nrOfSeekAds = 0;
2419 int minRating = 1010, maxRating = 2800;
2420 int hMargin = 10, vMargin = 20, h, w;
2421 extern int squareSize, lineGap;
2422
2423 void
2424 PlotSeekAd(int i)
2425 {
2426         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2427         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2428         if(r < minRating+100 && r >=0 ) r = minRating+100;
2429         if(r > maxRating) r = maxRating;
2430         if(tc < 1.) tc = 1.;
2431         if(tc > 95.) tc = 95.;
2432         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2433         y = ((double)r - minRating)/(maxRating - minRating)
2434             * (h-vMargin-squareSize/8-1) + vMargin;
2435         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2436         if(strstr(seekAdList[i], " u ")) color = 1;
2437         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2438            !strstr(seekAdList[i], "bullet") &&
2439            !strstr(seekAdList[i], "blitz") &&
2440            !strstr(seekAdList[i], "standard") ) color = 2;
2441         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2442         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2443 }
2444
2445 void
2446 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2447 {
2448         char buf[MSG_SIZ], *ext = "";
2449         VariantClass v = StringToVariant(type);
2450         if(strstr(type, "wild")) {
2451             ext = type + 4; // append wild number
2452             if(v == VariantFischeRandom) type = "chess960"; else
2453             if(v == VariantLoadable) type = "setup"; else
2454             type = VariantName(v);
2455         }
2456         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2457         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2458             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2459             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2460             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2461             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2462             seekNrList[nrOfSeekAds] = nr;
2463             zList[nrOfSeekAds] = 0;
2464             seekAdList[nrOfSeekAds++] = StrSave(buf);
2465             if(plot) PlotSeekAd(nrOfSeekAds-1);
2466         }
2467 }
2468
2469 void
2470 EraseSeekDot(int i)
2471 {
2472     int x = xList[i], y = yList[i], d=squareSize/4, k;
2473     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2474     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2475     // now replot every dot that overlapped
2476     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2477         int xx = xList[k], yy = yList[k];
2478         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2479             DrawSeekDot(xx, yy, colorList[k]);
2480     }
2481 }
2482
2483 void
2484 RemoveSeekAd(int nr)
2485 {
2486         int i;
2487         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2488             EraseSeekDot(i);
2489             if(seekAdList[i]) free(seekAdList[i]);
2490             seekAdList[i] = seekAdList[--nrOfSeekAds];
2491             seekNrList[i] = seekNrList[nrOfSeekAds];
2492             ratingList[i] = ratingList[nrOfSeekAds];
2493             colorList[i]  = colorList[nrOfSeekAds];
2494             tcList[i] = tcList[nrOfSeekAds];
2495             xList[i]  = xList[nrOfSeekAds];
2496             yList[i]  = yList[nrOfSeekAds];
2497             zList[i]  = zList[nrOfSeekAds];
2498             seekAdList[nrOfSeekAds] = NULL;
2499             break;
2500         }
2501 }
2502
2503 Boolean
2504 MatchSoughtLine(char *line)
2505 {
2506     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2507     int nr, base, inc, u=0; char dummy;
2508
2509     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2510        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2511        (u=1) &&
2512        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2513         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2514         // match: compact and save the line
2515         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2516         return TRUE;
2517     }
2518     return FALSE;
2519 }
2520
2521 int
2522 DrawSeekGraph()
2523 {
2524     int i;
2525     if(!seekGraphUp) return FALSE;
2526     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2527     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2528
2529     DrawSeekBackground(0, 0, w, h);
2530     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2531     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2532     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2533         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2534         yy = h-1-yy;
2535         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2536         if(i%500 == 0) {
2537             char buf[MSG_SIZ];
2538             snprintf(buf, MSG_SIZ, "%d", i);
2539             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2540         }
2541     }
2542     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2543     for(i=1; i<100; i+=(i<10?1:5)) {
2544         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2545         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2546         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2547             char buf[MSG_SIZ];
2548             snprintf(buf, MSG_SIZ, "%d", i);
2549             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2550         }
2551     }
2552     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2553     return TRUE;
2554 }
2555
2556 int SeekGraphClick(ClickType click, int x, int y, int moving)
2557 {
2558     static int lastDown = 0, displayed = 0, lastSecond;
2559     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2560         if(click == Release || moving) return FALSE;
2561         nrOfSeekAds = 0;
2562         soughtPending = TRUE;
2563         SendToICS(ics_prefix);
2564         SendToICS("sought\n"); // should this be "sought all"?
2565     } else { // issue challenge based on clicked ad
2566         int dist = 10000; int i, closest = 0, second = 0;
2567         for(i=0; i<nrOfSeekAds; i++) {
2568             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2569             if(d < dist) { dist = d; closest = i; }
2570             second += (d - zList[i] < 120); // count in-range ads
2571             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2572         }
2573         if(dist < 120) {
2574             char buf[MSG_SIZ];
2575             second = (second > 1);
2576             if(displayed != closest || second != lastSecond) {
2577                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2578                 lastSecond = second; displayed = closest;
2579             }
2580             if(click == Press) {
2581                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2582                 lastDown = closest;
2583                 return TRUE;
2584             } // on press 'hit', only show info
2585             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2586             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2587             SendToICS(ics_prefix);
2588             SendToICS(buf);
2589             return TRUE; // let incoming board of started game pop down the graph
2590         } else if(click == Release) { // release 'miss' is ignored
2591             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2592             if(moving == 2) { // right up-click
2593                 nrOfSeekAds = 0; // refresh graph
2594                 soughtPending = TRUE;
2595                 SendToICS(ics_prefix);
2596                 SendToICS("sought\n"); // should this be "sought all"?
2597             }
2598             return TRUE;
2599         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2600         // press miss or release hit 'pop down' seek graph
2601         seekGraphUp = FALSE;
2602         DrawPosition(TRUE, NULL);
2603     }
2604     return TRUE;
2605 }
2606
2607 void
2608 read_from_ics(isr, closure, data, count, error)
2609      InputSourceRef isr;
2610      VOIDSTAR closure;
2611      char *data;
2612      int count;
2613      int error;
2614 {
2615 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2616 #define STARTED_NONE 0
2617 #define STARTED_MOVES 1
2618 #define STARTED_BOARD 2
2619 #define STARTED_OBSERVE 3
2620 #define STARTED_HOLDINGS 4
2621 #define STARTED_CHATTER 5
2622 #define STARTED_COMMENT 6
2623 #define STARTED_MOVES_NOHIDE 7
2624
2625     static int started = STARTED_NONE;
2626     static char parse[20000];
2627     static int parse_pos = 0;
2628     static char buf[BUF_SIZE + 1];
2629     static int firstTime = TRUE, intfSet = FALSE;
2630     static ColorClass prevColor = ColorNormal;
2631     static int savingComment = FALSE;
2632     static int cmatch = 0; // continuation sequence match
2633     char *bp;
2634     char str[MSG_SIZ];
2635     int i, oldi;
2636     int buf_len;
2637     int next_out;
2638     int tkind;
2639     int backup;    /* [DM] For zippy color lines */
2640     char *p;
2641     char talker[MSG_SIZ]; // [HGM] chat
2642     int channel;
2643
2644     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2645
2646     if (appData.debugMode) {
2647       if (!error) {
2648         fprintf(debugFP, "<ICS: ");
2649         show_bytes(debugFP, data, count);
2650         fprintf(debugFP, "\n");
2651       }
2652     }
2653
2654     if (appData.debugMode) { int f = forwardMostMove;
2655         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2656                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2657                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2658     }
2659     if (count > 0) {
2660         /* If last read ended with a partial line that we couldn't parse,
2661            prepend it to the new read and try again. */
2662         if (leftover_len > 0) {
2663             for (i=0; i<leftover_len; i++)
2664               buf[i] = buf[leftover_start + i];
2665         }
2666
2667     /* copy new characters into the buffer */
2668     bp = buf + leftover_len;
2669     buf_len=leftover_len;
2670     for (i=0; i<count; i++)
2671     {
2672         // ignore these
2673         if (data[i] == '\r')
2674             continue;
2675
2676         // join lines split by ICS?
2677         if (!appData.noJoin)
2678         {
2679             /*
2680                 Joining just consists of finding matches against the
2681                 continuation sequence, and discarding that sequence
2682                 if found instead of copying it.  So, until a match
2683                 fails, there's nothing to do since it might be the
2684                 complete sequence, and thus, something we don't want
2685                 copied.
2686             */
2687             if (data[i] == cont_seq[cmatch])
2688             {
2689                 cmatch++;
2690                 if (cmatch == strlen(cont_seq))
2691                 {
2692                     cmatch = 0; // complete match.  just reset the counter
2693
2694                     /*
2695                         it's possible for the ICS to not include the space
2696                         at the end of the last word, making our [correct]
2697                         join operation fuse two separate words.  the server
2698                         does this when the space occurs at the width setting.
2699                     */
2700                     if (!buf_len || buf[buf_len-1] != ' ')
2701                     {
2702                         *bp++ = ' ';
2703                         buf_len++;
2704                     }
2705                 }
2706                 continue;
2707             }
2708             else if (cmatch)
2709             {
2710                 /*
2711                     match failed, so we have to copy what matched before
2712                     falling through and copying this character.  In reality,
2713                     this will only ever be just the newline character, but
2714                     it doesn't hurt to be precise.
2715                 */
2716                 strncpy(bp, cont_seq, cmatch);
2717                 bp += cmatch;
2718                 buf_len += cmatch;
2719                 cmatch = 0;
2720             }
2721         }
2722
2723         // copy this char
2724         *bp++ = data[i];
2725         buf_len++;
2726     }
2727
2728         buf[buf_len] = NULLCHAR;
2729 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2730         next_out = 0;
2731         leftover_start = 0;
2732
2733         i = 0;
2734         while (i < buf_len) {
2735             /* Deal with part of the TELNET option negotiation
2736                protocol.  We refuse to do anything beyond the
2737                defaults, except that we allow the WILL ECHO option,
2738                which ICS uses to turn off password echoing when we are
2739                directly connected to it.  We reject this option
2740                if localLineEditing mode is on (always on in xboard)
2741                and we are talking to port 23, which might be a real
2742                telnet server that will try to keep WILL ECHO on permanently.
2743              */
2744             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2745                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2746                 unsigned char option;
2747                 oldi = i;
2748                 switch ((unsigned char) buf[++i]) {
2749                   case TN_WILL:
2750                     if (appData.debugMode)
2751                       fprintf(debugFP, "\n<WILL ");
2752                     switch (option = (unsigned char) buf[++i]) {
2753                       case TN_ECHO:
2754                         if (appData.debugMode)
2755                           fprintf(debugFP, "ECHO ");
2756                         /* Reply only if this is a change, according
2757                            to the protocol rules. */
2758                         if (remoteEchoOption) break;
2759                         if (appData.localLineEditing &&
2760                             atoi(appData.icsPort) == TN_PORT) {
2761                             TelnetRequest(TN_DONT, TN_ECHO);
2762                         } else {
2763                             EchoOff();
2764                             TelnetRequest(TN_DO, TN_ECHO);
2765                             remoteEchoOption = TRUE;
2766                         }
2767                         break;
2768                       default:
2769                         if (appData.debugMode)
2770                           fprintf(debugFP, "%d ", option);
2771                         /* Whatever this is, we don't want it. */
2772                         TelnetRequest(TN_DONT, option);
2773                         break;
2774                     }
2775                     break;
2776                   case TN_WONT:
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, "\n<WONT ");
2779                     switch (option = (unsigned char) buf[++i]) {
2780                       case TN_ECHO:
2781                         if (appData.debugMode)
2782                           fprintf(debugFP, "ECHO ");
2783                         /* Reply only if this is a change, according
2784                            to the protocol rules. */
2785                         if (!remoteEchoOption) break;
2786                         EchoOn();
2787                         TelnetRequest(TN_DONT, TN_ECHO);
2788                         remoteEchoOption = FALSE;
2789                         break;
2790                       default:
2791                         if (appData.debugMode)
2792                           fprintf(debugFP, "%d ", (unsigned char) option);
2793                         /* Whatever this is, it must already be turned
2794                            off, because we never agree to turn on
2795                            anything non-default, so according to the
2796                            protocol rules, we don't reply. */
2797                         break;
2798                     }
2799                     break;
2800                   case TN_DO:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<DO ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       default:
2805                         /* Whatever this is, we refuse to do it. */
2806                         if (appData.debugMode)
2807                           fprintf(debugFP, "%d ", option);
2808                         TelnetRequest(TN_WONT, option);
2809                         break;
2810                     }
2811                     break;
2812                   case TN_DONT:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<DONT ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we are already not doing
2820                            it, because we never agree to do anything
2821                            non-default, so according to the protocol
2822                            rules, we don't reply. */
2823                         break;
2824                     }
2825                     break;
2826                   case TN_IAC:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<IAC ");
2829                     /* Doubled IAC; pass it through */
2830                     i--;
2831                     break;
2832                   default:
2833                     if (appData.debugMode)
2834                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2835                     /* Drop all other telnet commands on the floor */
2836                     break;
2837                 }
2838                 if (oldi > next_out)
2839                   SendToPlayer(&buf[next_out], oldi - next_out);
2840                 if (++i > next_out)
2841                   next_out = i;
2842                 continue;
2843             }
2844
2845             /* OK, this at least will *usually* work */
2846             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2847                 loggedOn = TRUE;
2848             }
2849
2850             if (loggedOn && !intfSet) {
2851                 if (ics_type == ICS_ICC) {
2852                   snprintf(str, MSG_SIZ,
2853                           "/set-quietly interface %s\n/set-quietly style 12\n",
2854                           programVersion);
2855                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2856                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2857                 } else if (ics_type == ICS_CHESSNET) {
2858                   snprintf(str, MSG_SIZ, "/style 12\n");
2859                 } else {
2860                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2861                   strcat(str, programVersion);
2862                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2863                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2865 #ifdef WIN32
2866                   strcat(str, "$iset nohighlight 1\n");
2867 #endif
2868                   strcat(str, "$iset lock 1\n$style 12\n");
2869                 }
2870                 SendToICS(str);
2871                 NotifyFrontendLogin();
2872                 intfSet = TRUE;
2873             }
2874
2875             if (started == STARTED_COMMENT) {
2876                 /* Accumulate characters in comment */
2877                 parse[parse_pos++] = buf[i];
2878                 if (buf[i] == '\n') {
2879                     parse[parse_pos] = NULLCHAR;
2880                     if(chattingPartner>=0) {
2881                         char mess[MSG_SIZ];
2882                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2883                         OutputChatMessage(chattingPartner, mess);
2884                         chattingPartner = -1;
2885                         next_out = i+1; // [HGM] suppress printing in ICS window
2886                     } else
2887                     if(!suppressKibitz) // [HGM] kibitz
2888                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2889                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2890                         int nrDigit = 0, nrAlph = 0, j;
2891                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2892                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2893                         parse[parse_pos] = NULLCHAR;
2894                         // try to be smart: if it does not look like search info, it should go to
2895                         // ICS interaction window after all, not to engine-output window.
2896                         for(j=0; j<parse_pos; j++) { // count letters and digits
2897                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2898                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2899                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2900                         }
2901                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2902                             int depth=0; float score;
2903                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2904                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2905                                 pvInfoList[forwardMostMove-1].depth = depth;
2906                                 pvInfoList[forwardMostMove-1].score = 100*score;
2907                             }
2908                             OutputKibitz(suppressKibitz, parse);
2909                         } else {
2910                             char tmp[MSG_SIZ];
2911                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2912                             SendToPlayer(tmp, strlen(tmp));
2913                         }
2914                         next_out = i+1; // [HGM] suppress printing in ICS window
2915                     }
2916                     started = STARTED_NONE;
2917                 } else {
2918                     /* Don't match patterns against characters in comment */
2919                     i++;
2920                     continue;
2921                 }
2922             }
2923             if (started == STARTED_CHATTER) {
2924                 if (buf[i] != '\n') {
2925                     /* Don't match patterns against characters in chatter */
2926                     i++;
2927                     continue;
2928                 }
2929                 started = STARTED_NONE;
2930                 if(suppressKibitz) next_out = i+1;
2931             }
2932
2933             /* Kludge to deal with rcmd protocol */
2934             if (firstTime && looking_at(buf, &i, "\001*")) {
2935                 DisplayFatalError(&buf[1], 0, 1);
2936                 continue;
2937             } else {
2938                 firstTime = FALSE;
2939             }
2940
2941             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2942                 ics_type = ICS_ICC;
2943                 ics_prefix = "/";
2944                 if (appData.debugMode)
2945                   fprintf(debugFP, "ics_type %d\n", ics_type);
2946                 continue;
2947             }
2948             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2949                 ics_type = ICS_FICS;
2950                 ics_prefix = "$";
2951                 if (appData.debugMode)
2952                   fprintf(debugFP, "ics_type %d\n", ics_type);
2953                 continue;
2954             }
2955             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2956                 ics_type = ICS_CHESSNET;
2957                 ics_prefix = "/";
2958                 if (appData.debugMode)
2959                   fprintf(debugFP, "ics_type %d\n", ics_type);
2960                 continue;
2961             }
2962
2963             if (!loggedOn &&
2964                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2965                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2966                  looking_at(buf, &i, "will be \"*\""))) {
2967               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2968               continue;
2969             }
2970
2971             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2972               char buf[MSG_SIZ];
2973               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2974               DisplayIcsInteractionTitle(buf);
2975               have_set_title = TRUE;
2976             }
2977
2978             /* skip finger notes */
2979             if (started == STARTED_NONE &&
2980                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2981                  (buf[i] == '1' && buf[i+1] == '0')) &&
2982                 buf[i+2] == ':' && buf[i+3] == ' ') {
2983               started = STARTED_CHATTER;
2984               i += 3;
2985               continue;
2986             }
2987
2988             oldi = i;
2989             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2990             if(appData.seekGraph) {
2991                 if(soughtPending && MatchSoughtLine(buf+i)) {
2992                     i = strstr(buf+i, "rated") - buf;
2993                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2994                     next_out = leftover_start = i;
2995                     started = STARTED_CHATTER;
2996                     suppressKibitz = TRUE;
2997                     continue;
2998                 }
2999                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3000                         && looking_at(buf, &i, "* ads displayed")) {
3001                     soughtPending = FALSE;
3002                     seekGraphUp = TRUE;
3003                     DrawSeekGraph();
3004                     continue;
3005                 }
3006                 if(appData.autoRefresh) {
3007                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3008                         int s = (ics_type == ICS_ICC); // ICC format differs
3009                         if(seekGraphUp)
3010                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3011                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3012                         looking_at(buf, &i, "*% "); // eat prompt
3013                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3014                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = i; // suppress
3016                         continue;
3017                     }
3018                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3019                         char *p = star_match[0];
3020                         while(*p) {
3021                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3022                             while(*p && *p++ != ' '); // next
3023                         }
3024                         looking_at(buf, &i, "*% "); // eat prompt
3025                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3026                         next_out = i;
3027                         continue;
3028                     }
3029                 }
3030             }
3031
3032             /* skip formula vars */
3033             if (started == STARTED_NONE &&
3034                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3035               started = STARTED_CHATTER;
3036               i += 3;
3037               continue;
3038             }
3039
3040             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3041             if (appData.autoKibitz && started == STARTED_NONE &&
3042                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3043                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3044                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3045                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3046                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3047                         suppressKibitz = TRUE;
3048                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3049                         next_out = i;
3050                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3051                                 && (gameMode == IcsPlayingWhite)) ||
3052                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3053                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3054                             started = STARTED_CHATTER; // own kibitz we simply discard
3055                         else {
3056                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3057                             parse_pos = 0; parse[0] = NULLCHAR;
3058                             savingComment = TRUE;
3059                             suppressKibitz = gameMode != IcsObserving ? 2 :
3060                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3061                         }
3062                         continue;
3063                 } else
3064                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3065                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3066                          && atoi(star_match[0])) {
3067                     // suppress the acknowledgements of our own autoKibitz
3068                     char *p;
3069                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3071                     SendToPlayer(star_match[0], strlen(star_match[0]));
3072                     if(looking_at(buf, &i, "*% ")) // eat prompt
3073                         suppressKibitz = FALSE;
3074                     next_out = i;
3075                     continue;
3076                 }
3077             } // [HGM] kibitz: end of patch
3078
3079             // [HGM] chat: intercept tells by users for which we have an open chat window
3080             channel = -1;
3081             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3082                                            looking_at(buf, &i, "* whispers:") ||
3083                                            looking_at(buf, &i, "* kibitzes:") ||
3084                                            looking_at(buf, &i, "* shouts:") ||
3085                                            looking_at(buf, &i, "* c-shouts:") ||
3086                                            looking_at(buf, &i, "--> * ") ||
3087                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3088                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3089                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3090                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3091                 int p;
3092                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3093                 chattingPartner = -1;
3094
3095                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3096                 for(p=0; p<MAX_CHAT; p++) {
3097                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3098                     talker[0] = '['; strcat(talker, "] ");
3099                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3100                     chattingPartner = p; break;
3101                     }
3102                 } else
3103                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3104                 for(p=0; p<MAX_CHAT; p++) {
3105                     if(!strcmp("kibitzes", chatPartner[p])) {
3106                         talker[0] = '['; strcat(talker, "] ");
3107                         chattingPartner = p; break;
3108                     }
3109                 } else
3110                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3111                 for(p=0; p<MAX_CHAT; p++) {
3112                     if(!strcmp("whispers", chatPartner[p])) {
3113                         talker[0] = '['; strcat(talker, "] ");
3114                         chattingPartner = p; break;
3115                     }
3116                 } else
3117                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3118                   if(buf[i-8] == '-' && buf[i-3] == 't')
3119                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3120                     if(!strcmp("c-shouts", chatPartner[p])) {
3121                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3122                         chattingPartner = p; break;
3123                     }
3124                   }
3125                   if(chattingPartner < 0)
3126                   for(p=0; p<MAX_CHAT; p++) {
3127                     if(!strcmp("shouts", chatPartner[p])) {
3128                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3129                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3130                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3131                         chattingPartner = p; break;
3132                     }
3133                   }
3134                 }
3135                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3136                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3137                     talker[0] = 0; Colorize(ColorTell, FALSE);
3138                     chattingPartner = p; break;
3139                 }
3140                 if(chattingPartner<0) i = oldi; else {
3141                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3142                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     started = STARTED_COMMENT;
3145                     parse_pos = 0; parse[0] = NULLCHAR;
3146                     savingComment = 3 + chattingPartner; // counts as TRUE
3147                     suppressKibitz = TRUE;
3148                     continue;
3149                 }
3150             } // [HGM] chat: end of patch
3151
3152           backup = i;
3153             if (appData.zippyTalk || appData.zippyPlay) {
3154                 /* [DM] Backup address for color zippy lines */
3155 #if ZIPPY
3156                if (loggedOn == TRUE)
3157                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3158                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3159 #endif
3160             } // [DM] 'else { ' deleted
3161                 if (
3162                     /* Regular tells and says */
3163                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3164                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3165                     looking_at(buf, &i, "* says: ") ||
3166                     /* Don't color "message" or "messages" output */
3167                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3168                     looking_at(buf, &i, "*. * at *:*: ") ||
3169                     looking_at(buf, &i, "--* (*:*): ") ||
3170                     /* Message notifications (same color as tells) */
3171                     looking_at(buf, &i, "* has left a message ") ||
3172                     looking_at(buf, &i, "* just sent you a message:\n") ||
3173                     /* Whispers and kibitzes */
3174                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3175                     looking_at(buf, &i, "* kibitzes: ") ||
3176                     /* Channel tells */
3177                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3178
3179                   if (tkind == 1 && strchr(star_match[0], ':')) {
3180                       /* Avoid "tells you:" spoofs in channels */
3181                      tkind = 3;
3182                   }
3183                   if (star_match[0][0] == NULLCHAR ||
3184                       strchr(star_match[0], ' ') ||
3185                       (tkind == 3 && strchr(star_match[1], ' '))) {
3186                     /* Reject bogus matches */
3187                     i = oldi;
3188                   } else {
3189                     if (appData.colorize) {
3190                       if (oldi > next_out) {
3191                         SendToPlayer(&buf[next_out], oldi - next_out);
3192                         next_out = oldi;
3193                       }
3194                       switch (tkind) {
3195                       case 1:
3196                         Colorize(ColorTell, FALSE);
3197                         curColor = ColorTell;
3198                         break;
3199                       case 2:
3200                         Colorize(ColorKibitz, FALSE);
3201                         curColor = ColorKibitz;
3202                         break;
3203                       case 3:
3204                         p = strrchr(star_match[1], '(');
3205                         if (p == NULL) {
3206                           p = star_match[1];
3207                         } else {
3208                           p++;
3209                         }
3210                         if (atoi(p) == 1) {
3211                           Colorize(ColorChannel1, FALSE);
3212                           curColor = ColorChannel1;
3213                         } else {
3214                           Colorize(ColorChannel, FALSE);
3215                           curColor = ColorChannel;
3216                         }
3217                         break;
3218                       case 5:
3219                         curColor = ColorNormal;
3220                         break;
3221                       }
3222                     }
3223                     if (started == STARTED_NONE && appData.autoComment &&
3224                         (gameMode == IcsObserving ||
3225                          gameMode == IcsPlayingWhite ||
3226                          gameMode == IcsPlayingBlack)) {
3227                       parse_pos = i - oldi;
3228                       memcpy(parse, &buf[oldi], parse_pos);
3229                       parse[parse_pos] = NULLCHAR;
3230                       started = STARTED_COMMENT;
3231                       savingComment = TRUE;
3232                     } else {
3233                       started = STARTED_CHATTER;
3234                       savingComment = FALSE;
3235                     }
3236                     loggedOn = TRUE;
3237                     continue;
3238                   }
3239                 }
3240
3241                 if (looking_at(buf, &i, "* s-shouts: ") ||
3242                     looking_at(buf, &i, "* c-shouts: ")) {
3243                     if (appData.colorize) {
3244                         if (oldi > next_out) {
3245                             SendToPlayer(&buf[next_out], oldi - next_out);
3246                             next_out = oldi;
3247                         }
3248                         Colorize(ColorSShout, FALSE);
3249                         curColor = ColorSShout;
3250                     }
3251                     loggedOn = TRUE;
3252                     started = STARTED_CHATTER;
3253                     continue;
3254                 }
3255
3256                 if (looking_at(buf, &i, "--->")) {
3257                     loggedOn = TRUE;
3258                     continue;
3259                 }
3260
3261                 if (looking_at(buf, &i, "* shouts: ") ||
3262                     looking_at(buf, &i, "--> ")) {
3263                     if (appData.colorize) {
3264                         if (oldi > next_out) {
3265                             SendToPlayer(&buf[next_out], oldi - next_out);
3266                             next_out = oldi;
3267                         }
3268                         Colorize(ColorShout, FALSE);
3269                         curColor = ColorShout;
3270                     }
3271                     loggedOn = TRUE;
3272                     started = STARTED_CHATTER;
3273                     continue;
3274                 }
3275
3276                 if (looking_at( buf, &i, "Challenge:")) {
3277                     if (appData.colorize) {
3278                         if (oldi > next_out) {
3279                             SendToPlayer(&buf[next_out], oldi - next_out);
3280                             next_out = oldi;
3281                         }
3282                         Colorize(ColorChallenge, FALSE);
3283                         curColor = ColorChallenge;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                 }
3288
3289                 if (looking_at(buf, &i, "* offers you") ||
3290                     looking_at(buf, &i, "* offers to be") ||
3291                     looking_at(buf, &i, "* would like to") ||
3292                     looking_at(buf, &i, "* requests to") ||
3293                     looking_at(buf, &i, "Your opponent offers") ||
3294                     looking_at(buf, &i, "Your opponent requests")) {
3295
3296                     if (appData.colorize) {
3297                         if (oldi > next_out) {
3298                             SendToPlayer(&buf[next_out], oldi - next_out);
3299                             next_out = oldi;
3300                         }
3301                         Colorize(ColorRequest, FALSE);
3302                         curColor = ColorRequest;
3303                     }
3304                     continue;
3305                 }
3306
3307                 if (looking_at(buf, &i, "* (*) seeking")) {
3308                     if (appData.colorize) {
3309                         if (oldi > next_out) {
3310                             SendToPlayer(&buf[next_out], oldi - next_out);
3311                             next_out = oldi;
3312                         }
3313                         Colorize(ColorSeek, FALSE);
3314                         curColor = ColorSeek;
3315                     }
3316                     continue;
3317             }
3318
3319           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3320
3321             if (looking_at(buf, &i, "\\   ")) {
3322                 if (prevColor != ColorNormal) {
3323                     if (oldi > next_out) {
3324                         SendToPlayer(&buf[next_out], oldi - next_out);
3325                         next_out = oldi;
3326                     }
3327                     Colorize(prevColor, TRUE);
3328                     curColor = prevColor;
3329                 }
3330                 if (savingComment) {
3331                     parse_pos = i - oldi;
3332                     memcpy(parse, &buf[oldi], parse_pos);
3333                     parse[parse_pos] = NULLCHAR;
3334                     started = STARTED_COMMENT;
3335                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3336                         chattingPartner = savingComment - 3; // kludge to remember the box
3337                 } else {
3338                     started = STARTED_CHATTER;
3339                 }
3340                 continue;
3341             }
3342
3343             if (looking_at(buf, &i, "Black Strength :") ||
3344                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3345                 looking_at(buf, &i, "<10>") ||
3346                 looking_at(buf, &i, "#@#")) {
3347                 /* Wrong board style */
3348                 loggedOn = TRUE;
3349                 SendToICS(ics_prefix);
3350                 SendToICS("set style 12\n");
3351                 SendToICS(ics_prefix);
3352                 SendToICS("refresh\n");
3353                 continue;
3354             }
3355
3356             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3357                 ICSInitScript();
3358                 have_sent_ICS_logon = 1;
3359                 continue;
3360             }
3361
3362             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3363                 (looking_at(buf, &i, "\n<12> ") ||
3364                  looking_at(buf, &i, "<12> "))) {
3365                 loggedOn = TRUE;
3366                 if (oldi > next_out) {
3367                     SendToPlayer(&buf[next_out], oldi - next_out);
3368                 }
3369                 next_out = i;
3370                 started = STARTED_BOARD;
3371                 parse_pos = 0;
3372                 continue;
3373             }
3374
3375             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3376                 looking_at(buf, &i, "<b1> ")) {
3377                 if (oldi > next_out) {
3378                     SendToPlayer(&buf[next_out], oldi - next_out);
3379                 }
3380                 next_out = i;
3381                 started = STARTED_HOLDINGS;
3382                 parse_pos = 0;
3383                 continue;
3384             }
3385
3386             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3387                 loggedOn = TRUE;
3388                 /* Header for a move list -- first line */
3389
3390                 switch (ics_getting_history) {
3391                   case H_FALSE:
3392                     switch (gameMode) {
3393                       case IcsIdle:
3394                       case BeginningOfGame:
3395                         /* User typed "moves" or "oldmoves" while we
3396                            were idle.  Pretend we asked for these
3397                            moves and soak them up so user can step
3398                            through them and/or save them.
3399                            */
3400                         Reset(FALSE, TRUE);
3401                         gameMode = IcsObserving;
3402                         ModeHighlight();
3403                         ics_gamenum = -1;
3404                         ics_getting_history = H_GOT_UNREQ_HEADER;
3405                         break;
3406                       case EditGame: /*?*/
3407                       case EditPosition: /*?*/
3408                         /* Should above feature work in these modes too? */
3409                         /* For now it doesn't */
3410                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3411                         break;
3412                       default:
3413                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3414                         break;
3415                     }
3416                     break;
3417                   case H_REQUESTED:
3418                     /* Is this the right one? */
3419                     if (gameInfo.white && gameInfo.black &&
3420                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3421                         strcmp(gameInfo.black, star_match[2]) == 0) {
3422                         /* All is well */
3423                         ics_getting_history = H_GOT_REQ_HEADER;
3424                     }
3425                     break;
3426                   case H_GOT_REQ_HEADER:
3427                   case H_GOT_UNREQ_HEADER:
3428                   case H_GOT_UNWANTED_HEADER:
3429                   case H_GETTING_MOVES:
3430                     /* Should not happen */
3431                     DisplayError(_("Error gathering move list: two headers"), 0);
3432                     ics_getting_history = H_FALSE;
3433                     break;
3434                 }
3435
3436                 /* Save player ratings into gameInfo if needed */
3437                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3438                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3439                     (gameInfo.whiteRating == -1 ||
3440                      gameInfo.blackRating == -1)) {
3441
3442                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3443                     gameInfo.blackRating = string_to_rating(star_match[3]);
3444                     if (appData.debugMode)
3445                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3446                               gameInfo.whiteRating, gameInfo.blackRating);
3447                 }
3448                 continue;
3449             }
3450
3451             if (looking_at(buf, &i,
3452               "* * match, initial time: * minute*, increment: * second")) {
3453                 /* Header for a move list -- second line */
3454                 /* Initial board will follow if this is a wild game */
3455                 if (gameInfo.event != NULL) free(gameInfo.event);
3456                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3457                 gameInfo.event = StrSave(str);
3458                 /* [HGM] we switched variant. Translate boards if needed. */
3459                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "Move  ")) {
3464                 /* Beginning of a move list */
3465                 switch (ics_getting_history) {
3466                   case H_FALSE:
3467                     /* Normally should not happen */
3468                     /* Maybe user hit reset while we were parsing */
3469                     break;
3470                   case H_REQUESTED:
3471                     /* Happens if we are ignoring a move list that is not
3472                      * the one we just requested.  Common if the user
3473                      * tries to observe two games without turning off
3474                      * getMoveList */
3475                     break;
3476                   case H_GETTING_MOVES:
3477                     /* Should not happen */
3478                     DisplayError(_("Error gathering move list: nested"), 0);
3479                     ics_getting_history = H_FALSE;
3480                     break;
3481                   case H_GOT_REQ_HEADER:
3482                     ics_getting_history = H_GETTING_MOVES;
3483                     started = STARTED_MOVES;
3484                     parse_pos = 0;
3485                     if (oldi > next_out) {
3486                         SendToPlayer(&buf[next_out], oldi - next_out);
3487                     }
3488                     break;
3489                   case H_GOT_UNREQ_HEADER:
3490                     ics_getting_history = H_GETTING_MOVES;
3491                     started = STARTED_MOVES_NOHIDE;
3492                     parse_pos = 0;
3493                     break;
3494                   case H_GOT_UNWANTED_HEADER:
3495                     ics_getting_history = H_FALSE;
3496                     break;
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i, "% ") ||
3502                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3503                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3504                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3505                     soughtPending = FALSE;
3506                     seekGraphUp = TRUE;
3507                     DrawSeekGraph();
3508                 }
3509                 if(suppressKibitz) next_out = i;
3510                 savingComment = FALSE;
3511                 suppressKibitz = 0;
3512                 switch (started) {
3513                   case STARTED_MOVES:
3514                   case STARTED_MOVES_NOHIDE:
3515                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3516                     parse[parse_pos + i - oldi] = NULLCHAR;
3517                     ParseGameHistory(parse);
3518 #if ZIPPY
3519                     if (appData.zippyPlay && first.initDone) {
3520                         FeedMovesToProgram(&first, forwardMostMove);
3521                         if (gameMode == IcsPlayingWhite) {
3522                             if (WhiteOnMove(forwardMostMove)) {
3523                                 if (first.sendTime) {
3524                                   if (first.useColors) {
3525                                     SendToProgram("black\n", &first);
3526                                   }
3527                                   SendTimeRemaining(&first, TRUE);
3528                                 }
3529                                 if (first.useColors) {
3530                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3531                                 }
3532                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3533                                 first.maybeThinking = TRUE;
3534                             } else {
3535                                 if (first.usePlayother) {
3536                                   if (first.sendTime) {
3537                                     SendTimeRemaining(&first, TRUE);
3538                                   }
3539                                   SendToProgram("playother\n", &first);
3540                                   firstMove = FALSE;
3541                                 } else {
3542                                   firstMove = TRUE;
3543                                 }
3544                             }
3545                         } else if (gameMode == IcsPlayingBlack) {
3546                             if (!WhiteOnMove(forwardMostMove)) {
3547                                 if (first.sendTime) {
3548                                   if (first.useColors) {
3549                                     SendToProgram("white\n", &first);
3550                                   }
3551                                   SendTimeRemaining(&first, FALSE);
3552                                 }
3553                                 if (first.useColors) {
3554                                   SendToProgram("black\n", &first);
3555                                 }
3556                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3557                                 first.maybeThinking = TRUE;
3558                             } else {
3559                                 if (first.usePlayother) {
3560                                   if (first.sendTime) {
3561                                     SendTimeRemaining(&first, FALSE);
3562                                   }
3563                                   SendToProgram("playother\n", &first);
3564                                   firstMove = FALSE;
3565                                 } else {
3566                                   firstMove = TRUE;
3567                                 }
3568                             }
3569                         }
3570                     }
3571 #endif
3572                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3573                         /* Moves came from oldmoves or moves command
3574                            while we weren't doing anything else.
3575                            */
3576                         currentMove = forwardMostMove;
3577                         ClearHighlights();/*!!could figure this out*/
3578                         flipView = appData.flipView;
3579                         DrawPosition(TRUE, boards[currentMove]);
3580                         DisplayBothClocks();
3581                         snprintf(str, MSG_SIZ, "%s vs. %s",
3582                                 gameInfo.white, gameInfo.black);
3583                         DisplayTitle(str);
3584                         gameMode = IcsIdle;
3585                     } else {
3586                         /* Moves were history of an active game */
3587                         if (gameInfo.resultDetails != NULL) {
3588                             free(gameInfo.resultDetails);
3589                             gameInfo.resultDetails = NULL;
3590                         }
3591                     }
3592                     HistorySet(parseList, backwardMostMove,
3593                                forwardMostMove, currentMove-1);
3594                     DisplayMove(currentMove - 1);
3595                     if (started == STARTED_MOVES) next_out = i;
3596                     started = STARTED_NONE;
3597                     ics_getting_history = H_FALSE;
3598                     break;
3599
3600                   case STARTED_OBSERVE:
3601                     started = STARTED_NONE;
3602                     SendToICS(ics_prefix);
3603                     SendToICS("refresh\n");
3604                     break;
3605
3606                   default:
3607                     break;
3608                 }
3609                 if(bookHit) { // [HGM] book: simulate book reply
3610                     static char bookMove[MSG_SIZ]; // a bit generous?
3611
3612                     programStats.nodes = programStats.depth = programStats.time =
3613                     programStats.score = programStats.got_only_move = 0;
3614                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3615
3616                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3617                     strcat(bookMove, bookHit);
3618                     HandleMachineMove(bookMove, &first);
3619                 }
3620                 continue;
3621             }
3622
3623             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3624                  started == STARTED_HOLDINGS ||
3625                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3626                 /* Accumulate characters in move list or board */
3627                 parse[parse_pos++] = buf[i];
3628             }
3629
3630             /* Start of game messages.  Mostly we detect start of game
3631                when the first board image arrives.  On some versions
3632                of the ICS, though, we need to do a "refresh" after starting
3633                to observe in order to get the current board right away. */
3634             if (looking_at(buf, &i, "Adding game * to observation list")) {
3635                 started = STARTED_OBSERVE;
3636                 continue;
3637             }
3638
3639             /* Handle auto-observe */
3640             if (appData.autoObserve &&
3641                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3642                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3643                 char *player;
3644                 /* Choose the player that was highlighted, if any. */
3645                 if (star_match[0][0] == '\033' ||
3646                     star_match[1][0] != '\033') {
3647                     player = star_match[0];
3648                 } else {
3649                     player = star_match[2];
3650                 }
3651                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3652                         ics_prefix, StripHighlightAndTitle(player));
3653                 SendToICS(str);
3654
3655                 /* Save ratings from notify string */
3656                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3657                 player1Rating = string_to_rating(star_match[1]);
3658                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3659                 player2Rating = string_to_rating(star_match[3]);
3660
3661                 if (appData.debugMode)
3662                   fprintf(debugFP,
3663                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3664                           player1Name, player1Rating,
3665                           player2Name, player2Rating);
3666
3667                 continue;
3668             }
3669
3670             /* Deal with automatic examine mode after a game,
3671                and with IcsObserving -> IcsExamining transition */
3672             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3673                 looking_at(buf, &i, "has made you an examiner of game *")) {
3674
3675                 int gamenum = atoi(star_match[0]);
3676                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3677                     gamenum == ics_gamenum) {
3678                     /* We were already playing or observing this game;
3679                        no need to refetch history */
3680                     gameMode = IcsExamining;
3681                     if (pausing) {
3682                         pauseExamForwardMostMove = forwardMostMove;
3683                     } else if (currentMove < forwardMostMove) {
3684                         ForwardInner(forwardMostMove);
3685                     }
3686                 } else {
3687                     /* I don't think this case really can happen */
3688                     SendToICS(ics_prefix);
3689                     SendToICS("refresh\n");
3690                 }
3691                 continue;
3692             }
3693
3694             /* Error messages */
3695 //          if (ics_user_moved) {
3696             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3697                 if (looking_at(buf, &i, "Illegal move") ||
3698                     looking_at(buf, &i, "Not a legal move") ||
3699                     looking_at(buf, &i, "Your king is in check") ||
3700                     looking_at(buf, &i, "It isn't your turn") ||
3701                     looking_at(buf, &i, "It is not your move")) {
3702                     /* Illegal move */
3703                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3704                         currentMove = forwardMostMove-1;
3705                         DisplayMove(currentMove - 1); /* before DMError */
3706                         DrawPosition(FALSE, boards[currentMove]);
3707                         SwitchClocks(forwardMostMove-1); // [HGM] race
3708                         DisplayBothClocks();
3709                     }
3710                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3711                     ics_user_moved = 0;
3712                     continue;
3713                 }
3714             }
3715
3716             if (looking_at(buf, &i, "still have time") ||
3717                 looking_at(buf, &i, "not out of time") ||
3718                 looking_at(buf, &i, "either player is out of time") ||
3719                 looking_at(buf, &i, "has timeseal; checking")) {
3720                 /* We must have called his flag a little too soon */
3721                 whiteFlag = blackFlag = FALSE;
3722                 continue;
3723             }
3724
3725             if (looking_at(buf, &i, "added * seconds to") ||
3726                 looking_at(buf, &i, "seconds were added to")) {
3727                 /* Update the clocks */
3728                 SendToICS(ics_prefix);
3729                 SendToICS("refresh\n");
3730                 continue;
3731             }
3732
3733             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3734                 ics_clock_paused = TRUE;
3735                 StopClocks();
3736                 continue;
3737             }
3738
3739             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3740                 ics_clock_paused = FALSE;
3741                 StartClocks();
3742                 continue;
3743             }
3744
3745             /* Grab player ratings from the Creating: message.
3746                Note we have to check for the special case when
3747                the ICS inserts things like [white] or [black]. */
3748             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3749                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3750                 /* star_matches:
3751                    0    player 1 name (not necessarily white)
3752                    1    player 1 rating
3753                    2    empty, white, or black (IGNORED)
3754                    3    player 2 name (not necessarily black)
3755                    4    player 2 rating
3756
3757                    The names/ratings are sorted out when the game
3758                    actually starts (below).
3759                 */
3760                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3761                 player1Rating = string_to_rating(star_match[1]);
3762                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3763                 player2Rating = string_to_rating(star_match[4]);
3764
3765                 if (appData.debugMode)
3766                   fprintf(debugFP,
3767                           "Ratings from 'Creating:' %s %d, %s %d\n",
3768                           player1Name, player1Rating,
3769                           player2Name, player2Rating);
3770
3771                 continue;
3772             }
3773
3774             /* Improved generic start/end-of-game messages */
3775             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3776                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3777                 /* If tkind == 0: */
3778                 /* star_match[0] is the game number */
3779                 /*           [1] is the white player's name */
3780                 /*           [2] is the black player's name */
3781                 /* For end-of-game: */
3782                 /*           [3] is the reason for the game end */
3783                 /*           [4] is a PGN end game-token, preceded by " " */
3784                 /* For start-of-game: */
3785                 /*           [3] begins with "Creating" or "Continuing" */
3786                 /*           [4] is " *" or empty (don't care). */
3787                 int gamenum = atoi(star_match[0]);
3788                 char *whitename, *blackname, *why, *endtoken;
3789                 ChessMove endtype = EndOfFile;
3790
3791                 if (tkind == 0) {
3792                   whitename = star_match[1];
3793                   blackname = star_match[2];
3794                   why = star_match[3];
3795                   endtoken = star_match[4];
3796                 } else {
3797                   whitename = star_match[1];
3798                   blackname = star_match[3];
3799                   why = star_match[5];
3800                   endtoken = star_match[6];
3801                 }
3802
3803                 /* Game start messages */
3804                 if (strncmp(why, "Creating ", 9) == 0 ||
3805                     strncmp(why, "Continuing ", 11) == 0) {
3806                     gs_gamenum = gamenum;
3807                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3808                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3809 #if ZIPPY
3810                     if (appData.zippyPlay) {
3811                         ZippyGameStart(whitename, blackname);
3812                     }
3813 #endif /*ZIPPY*/
3814                     partnerBoardValid = FALSE; // [HGM] bughouse
3815                     continue;
3816                 }
3817
3818                 /* Game end messages */
3819                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3820                     ics_gamenum != gamenum) {
3821                     continue;
3822                 }
3823                 while (endtoken[0] == ' ') endtoken++;
3824                 switch (endtoken[0]) {
3825                   case '*':
3826                   default:
3827                     endtype = GameUnfinished;
3828                     break;
3829                   case '0':
3830                     endtype = BlackWins;
3831                     break;
3832                   case '1':
3833                     if (endtoken[1] == '/')
3834                       endtype = GameIsDrawn;
3835                     else
3836                       endtype = WhiteWins;
3837                     break;
3838                 }
3839                 GameEnds(endtype, why, GE_ICS);
3840 #if ZIPPY
3841                 if (appData.zippyPlay && first.initDone) {
3842                     ZippyGameEnd(endtype, why);
3843                     if (first.pr == NULL) {
3844                       /* Start the next process early so that we'll
3845                          be ready for the next challenge */
3846                       StartChessProgram(&first);
3847                     }
3848                     /* Send "new" early, in case this command takes
3849                        a long time to finish, so that we'll be ready
3850                        for the next challenge. */
3851                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3852                     Reset(TRUE, TRUE);
3853                 }
3854 #endif /*ZIPPY*/
3855                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3856                 continue;
3857             }
3858
3859             if (looking_at(buf, &i, "Removing game * from observation") ||
3860                 looking_at(buf, &i, "no longer observing game *") ||
3861                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3862                 if (gameMode == IcsObserving &&
3863                     atoi(star_match[0]) == ics_gamenum)
3864                   {
3865                       /* icsEngineAnalyze */
3866                       if (appData.icsEngineAnalyze) {
3867                             ExitAnalyzeMode();
3868                             ModeHighlight();
3869                       }
3870                       StopClocks();
3871                       gameMode = IcsIdle;
3872                       ics_gamenum = -1;
3873                       ics_user_moved = FALSE;
3874                   }
3875                 continue;
3876             }
3877
3878             if (looking_at(buf, &i, "no longer examining game *")) {
3879                 if (gameMode == IcsExamining &&
3880                     atoi(star_match[0]) == ics_gamenum)
3881                   {
3882                       gameMode = IcsIdle;
3883                       ics_gamenum = -1;
3884                       ics_user_moved = FALSE;
3885                   }
3886                 continue;
3887             }
3888
3889             /* Advance leftover_start past any newlines we find,
3890                so only partial lines can get reparsed */
3891             if (looking_at(buf, &i, "\n")) {
3892                 prevColor = curColor;
3893                 if (curColor != ColorNormal) {
3894                     if (oldi > next_out) {
3895                         SendToPlayer(&buf[next_out], oldi - next_out);
3896                         next_out = oldi;
3897                     }
3898                     Colorize(ColorNormal, FALSE);
3899                     curColor = ColorNormal;
3900                 }
3901                 if (started == STARTED_BOARD) {
3902                     started = STARTED_NONE;
3903                     parse[parse_pos] = NULLCHAR;
3904                     ParseBoard12(parse);
3905                     ics_user_moved = 0;
3906
3907                     /* Send premove here */
3908                     if (appData.premove) {
3909                       char str[MSG_SIZ];
3910                       if (currentMove == 0 &&
3911                           gameMode == IcsPlayingWhite &&
3912                           appData.premoveWhite) {
3913                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3914                         if (appData.debugMode)
3915                           fprintf(debugFP, "Sending premove:\n");
3916                         SendToICS(str);
3917                       } else if (currentMove == 1 &&
3918                                  gameMode == IcsPlayingBlack &&
3919                                  appData.premoveBlack) {
3920                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3921                         if (appData.debugMode)
3922                           fprintf(debugFP, "Sending premove:\n");
3923                         SendToICS(str);
3924                       } else if (gotPremove) {
3925                         gotPremove = 0;
3926                         ClearPremoveHighlights();
3927                         if (appData.debugMode)
3928                           fprintf(debugFP, "Sending premove:\n");
3929                           UserMoveEvent(premoveFromX, premoveFromY,
3930                                         premoveToX, premoveToY,
3931                                         premovePromoChar);
3932                       }
3933                     }
3934
3935                     /* Usually suppress following prompt */
3936                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3937                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3938                         if (looking_at(buf, &i, "*% ")) {
3939                             savingComment = FALSE;
3940                             suppressKibitz = 0;
3941                         }
3942                     }
3943                     next_out = i;
3944                 } else if (started == STARTED_HOLDINGS) {
3945                     int gamenum;
3946                     char new_piece[MSG_SIZ];
3947                     started = STARTED_NONE;
3948                     parse[parse_pos] = NULLCHAR;
3949                     if (appData.debugMode)
3950                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3951                                                         parse, currentMove);
3952                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3953                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3954                         if (gameInfo.variant == VariantNormal) {
3955                           /* [HGM] We seem to switch variant during a game!
3956                            * Presumably no holdings were displayed, so we have
3957                            * to move the position two files to the right to
3958                            * create room for them!
3959                            */
3960                           VariantClass newVariant;
3961                           switch(gameInfo.boardWidth) { // base guess on board width
3962                                 case 9:  newVariant = VariantShogi; break;
3963                                 case 10: newVariant = VariantGreat; break;
3964                                 default: newVariant = VariantCrazyhouse; break;
3965                           }
3966                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3967                           /* Get a move list just to see the header, which
3968                              will tell us whether this is really bug or zh */
3969                           if (ics_getting_history == H_FALSE) {
3970                             ics_getting_history = H_REQUESTED;
3971                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3972                             SendToICS(str);
3973                           }
3974                         }
3975                         new_piece[0] = NULLCHAR;
3976                         sscanf(parse, "game %d white [%s black [%s <- %s",
3977                                &gamenum, white_holding, black_holding,
3978                                new_piece);
3979                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3980                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3981                         /* [HGM] copy holdings to board holdings area */
3982                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3983                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3984                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3985 #if ZIPPY
3986                         if (appData.zippyPlay && first.initDone) {
3987                             ZippyHoldings(white_holding, black_holding,
3988                                           new_piece);
3989                         }
3990 #endif /*ZIPPY*/
3991                         if (tinyLayout || smallLayout) {
3992                             char wh[16], bh[16];
3993                             PackHolding(wh, white_holding);
3994                             PackHolding(bh, black_holding);
3995                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3996                                     gameInfo.white, gameInfo.black);
3997                         } else {
3998                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3999                                     gameInfo.white, white_holding,
4000                                     gameInfo.black, black_holding);
4001                         }
4002                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4003                         DrawPosition(FALSE, boards[currentMove]);
4004                         DisplayTitle(str);
4005                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4006                         sscanf(parse, "game %d white [%s black [%s <- %s",
4007                                &gamenum, white_holding, black_holding,
4008                                new_piece);
4009                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4010                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4011                         /* [HGM] copy holdings to partner-board holdings area */
4012                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4013                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4014                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4015                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4016                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4017                       }
4018                     }
4019                     /* Suppress following prompt */
4020                     if (looking_at(buf, &i, "*% ")) {
4021                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4022                         savingComment = FALSE;
4023                         suppressKibitz = 0;
4024                     }
4025                     next_out = i;
4026                 }
4027                 continue;
4028             }
4029
4030             i++;                /* skip unparsed character and loop back */
4031         }
4032
4033         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4034 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4035 //          SendToPlayer(&buf[next_out], i - next_out);
4036             started != STARTED_HOLDINGS && leftover_start > next_out) {
4037             SendToPlayer(&buf[next_out], leftover_start - next_out);
4038             next_out = i;
4039         }
4040
4041         leftover_len = buf_len - leftover_start;
4042         /* if buffer ends with something we couldn't parse,
4043            reparse it after appending the next read */
4044
4045     } else if (count == 0) {
4046         RemoveInputSource(isr);
4047         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4048     } else {
4049         DisplayFatalError(_("Error reading from ICS"), error, 1);
4050     }
4051 }
4052
4053
4054 /* Board style 12 looks like this:
4055
4056    <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
4057
4058  * The "<12> " is stripped before it gets to this routine.  The two
4059  * trailing 0's (flip state and clock ticking) are later addition, and
4060  * some chess servers may not have them, or may have only the first.
4061  * Additional trailing fields may be added in the future.
4062  */
4063
4064 #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"
4065
4066 #define RELATION_OBSERVING_PLAYED    0
4067 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4068 #define RELATION_PLAYING_MYMOVE      1
4069 #define RELATION_PLAYING_NOTMYMOVE  -1
4070 #define RELATION_EXAMINING           2
4071 #define RELATION_ISOLATED_BOARD     -3
4072 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4073
4074 void
4075 ParseBoard12(string)
4076      char *string;
4077 {
4078     GameMode newGameMode;
4079     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4080     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4081     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4082     char to_play, board_chars[200];
4083     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4084     char black[32], white[32];
4085     Board board;
4086     int prevMove = currentMove;
4087     int ticking = 2;
4088     ChessMove moveType;
4089     int fromX, fromY, toX, toY;
4090     char promoChar;
4091     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4092     char *bookHit = NULL; // [HGM] book
4093     Boolean weird = FALSE, reqFlag = FALSE;
4094
4095     fromX = fromY = toX = toY = -1;
4096
4097     newGame = FALSE;
4098
4099     if (appData.debugMode)
4100       fprintf(debugFP, _("Parsing board: %s\n"), string);
4101
4102     move_str[0] = NULLCHAR;
4103     elapsed_time[0] = NULLCHAR;
4104     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4105         int  i = 0, j;
4106         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4107             if(string[i] == ' ') { ranks++; files = 0; }
4108             else files++;
4109             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4110             i++;
4111         }
4112         for(j = 0; j <i; j++) board_chars[j] = string[j];
4113         board_chars[i] = '\0';
4114         string += i + 1;
4115     }
4116     n = sscanf(string, PATTERN, &to_play, &double_push,
4117                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4118                &gamenum, white, black, &relation, &basetime, &increment,
4119                &white_stren, &black_stren, &white_time, &black_time,
4120                &moveNum, str, elapsed_time, move_str, &ics_flip,
4121                &ticking);
4122
4123     if (n < 21) {
4124         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4125         DisplayError(str, 0);
4126         return;
4127     }
4128
4129     /* Convert the move number to internal form */
4130     moveNum = (moveNum - 1) * 2;
4131     if (to_play == 'B') moveNum++;
4132     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4133       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4134                         0, 1);
4135       return;
4136     }
4137
4138     switch (relation) {
4139       case RELATION_OBSERVING_PLAYED:
4140       case RELATION_OBSERVING_STATIC:
4141         if (gamenum == -1) {
4142             /* Old ICC buglet */
4143             relation = RELATION_OBSERVING_STATIC;
4144         }
4145         newGameMode = IcsObserving;
4146         break;
4147       case RELATION_PLAYING_MYMOVE:
4148       case RELATION_PLAYING_NOTMYMOVE:
4149         newGameMode =
4150           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4151             IcsPlayingWhite : IcsPlayingBlack;
4152         break;
4153       case RELATION_EXAMINING:
4154         newGameMode = IcsExamining;
4155         break;
4156       case RELATION_ISOLATED_BOARD:
4157       default:
4158         /* Just display this board.  If user was doing something else,
4159            we will forget about it until the next board comes. */
4160         newGameMode = IcsIdle;
4161         break;
4162       case RELATION_STARTING_POSITION:
4163         newGameMode = gameMode;
4164         break;
4165     }
4166
4167     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4168          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4169       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4170       char *toSqr;
4171       for (k = 0; k < ranks; k++) {
4172         for (j = 0; j < files; j++)
4173           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4174         if(gameInfo.holdingsWidth > 1) {
4175              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4176              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4177         }
4178       }
4179       CopyBoard(partnerBoard, board);
4180       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4181         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4182         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4183       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4184       if(toSqr = strchr(str, '-')) {
4185         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4186         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4187       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4188       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4189       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4190       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4191       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4192       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4193                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4194       DisplayMessage(partnerStatus, "");
4195         partnerBoardValid = TRUE;
4196       return;
4197     }
4198
4199     /* Modify behavior for initial board display on move listing
4200        of wild games.
4201        */
4202     switch (ics_getting_history) {
4203       case H_FALSE:
4204       case H_REQUESTED:
4205         break;
4206       case H_GOT_REQ_HEADER:
4207       case H_GOT_UNREQ_HEADER:
4208         /* This is the initial position of the current game */
4209         gamenum = ics_gamenum;
4210         moveNum = 0;            /* old ICS bug workaround */
4211         if (to_play == 'B') {
4212           startedFromSetupPosition = TRUE;
4213           blackPlaysFirst = TRUE;
4214           moveNum = 1;
4215           if (forwardMostMove == 0) forwardMostMove = 1;
4216           if (backwardMostMove == 0) backwardMostMove = 1;
4217           if (currentMove == 0) currentMove = 1;
4218         }
4219         newGameMode = gameMode;
4220         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4221         break;
4222       case H_GOT_UNWANTED_HEADER:
4223         /* This is an initial board that we don't want */
4224         return;
4225       case H_GETTING_MOVES:
4226         /* Should not happen */
4227         DisplayError(_("Error gathering move list: extra board"), 0);
4228         ics_getting_history = H_FALSE;
4229         return;
4230     }
4231
4232    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4233                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4234      /* [HGM] We seem to have switched variant unexpectedly
4235       * Try to guess new variant from board size
4236       */
4237           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4238           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4239           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4240           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4241           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4242           if(!weird) newVariant = VariantNormal;
4243           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4244           /* Get a move list just to see the header, which
4245              will tell us whether this is really bug or zh */
4246           if (ics_getting_history == H_FALSE) {
4247             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4248             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4249             SendToICS(str);
4250           }
4251     }
4252
4253     /* Take action if this is the first board of a new game, or of a
4254        different game than is currently being displayed.  */
4255     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4256         relation == RELATION_ISOLATED_BOARD) {
4257
4258         /* Forget the old game and get the history (if any) of the new one */
4259         if (gameMode != BeginningOfGame) {
4260           Reset(TRUE, TRUE);
4261         }
4262         newGame = TRUE;
4263         if (appData.autoRaiseBoard) BoardToTop();
4264         prevMove = -3;
4265         if (gamenum == -1) {
4266             newGameMode = IcsIdle;
4267         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4268                    appData.getMoveList && !reqFlag) {
4269             /* Need to get game history */
4270             ics_getting_history = H_REQUESTED;
4271             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4272             SendToICS(str);
4273         }
4274
4275         /* Initially flip the board to have black on the bottom if playing
4276            black or if the ICS flip flag is set, but let the user change
4277            it with the Flip View button. */
4278         flipView = appData.autoFlipView ?
4279           (newGameMode == IcsPlayingBlack) || ics_flip :
4280           appData.flipView;
4281
4282         /* Done with values from previous mode; copy in new ones */
4283         gameMode = newGameMode;
4284         ModeHighlight();
4285         ics_gamenum = gamenum;
4286         if (gamenum == gs_gamenum) {
4287             int klen = strlen(gs_kind);
4288             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4289             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4290             gameInfo.event = StrSave(str);
4291         } else {
4292             gameInfo.event = StrSave("ICS game");
4293         }
4294         gameInfo.site = StrSave(appData.icsHost);
4295         gameInfo.date = PGNDate();
4296         gameInfo.round = StrSave("-");
4297         gameInfo.white = StrSave(white);
4298         gameInfo.black = StrSave(black);
4299         timeControl = basetime * 60 * 1000;
4300         timeControl_2 = 0;
4301         timeIncrement = increment * 1000;
4302         movesPerSession = 0;
4303         gameInfo.timeControl = TimeControlTagValue();
4304         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4307     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4308     setbuf(debugFP, NULL);
4309   }
4310
4311         gameInfo.outOfBook = NULL;
4312
4313         /* Do we have the ratings? */
4314         if (strcmp(player1Name, white) == 0 &&
4315             strcmp(player2Name, black) == 0) {
4316             if (appData.debugMode)
4317               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4318                       player1Rating, player2Rating);
4319             gameInfo.whiteRating = player1Rating;
4320             gameInfo.blackRating = player2Rating;
4321         } else if (strcmp(player2Name, white) == 0 &&
4322                    strcmp(player1Name, black) == 0) {
4323             if (appData.debugMode)
4324               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4325                       player2Rating, player1Rating);
4326             gameInfo.whiteRating = player2Rating;
4327             gameInfo.blackRating = player1Rating;
4328         }
4329         player1Name[0] = player2Name[0] = NULLCHAR;
4330
4331         /* Silence shouts if requested */
4332         if (appData.quietPlay &&
4333             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4334             SendToICS(ics_prefix);
4335             SendToICS("set shout 0\n");
4336         }
4337     }
4338
4339     /* Deal with midgame name changes */
4340     if (!newGame) {
4341         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4342             if (gameInfo.white) free(gameInfo.white);
4343             gameInfo.white = StrSave(white);
4344         }
4345         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4346             if (gameInfo.black) free(gameInfo.black);
4347             gameInfo.black = StrSave(black);
4348         }
4349     }
4350
4351     /* Throw away game result if anything actually changes in examine mode */
4352     if (gameMode == IcsExamining && !newGame) {
4353         gameInfo.result = GameUnfinished;
4354         if (gameInfo.resultDetails != NULL) {
4355             free(gameInfo.resultDetails);
4356             gameInfo.resultDetails = NULL;
4357         }
4358     }
4359
4360     /* In pausing && IcsExamining mode, we ignore boards coming
4361        in if they are in a different variation than we are. */
4362     if (pauseExamInvalid) return;
4363     if (pausing && gameMode == IcsExamining) {
4364         if (moveNum <= pauseExamForwardMostMove) {
4365             pauseExamInvalid = TRUE;
4366             forwardMostMove = pauseExamForwardMostMove;
4367             return;
4368         }
4369     }
4370
4371   if (appData.debugMode) {
4372     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4373   }
4374     /* Parse the board */
4375     for (k = 0; k < ranks; k++) {
4376       for (j = 0; j < files; j++)
4377         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4378       if(gameInfo.holdingsWidth > 1) {
4379            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4380            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4381       }
4382     }
4383     CopyBoard(boards[moveNum], board);
4384     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4385     if (moveNum == 0) {
4386         startedFromSetupPosition =
4387           !CompareBoards(board, initialPosition);
4388         if(startedFromSetupPosition)
4389             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4390     }
4391
4392     /* [HGM] Set castling rights. Take the outermost Rooks,
4393        to make it also work for FRC opening positions. Note that board12
4394        is really defective for later FRC positions, as it has no way to
4395        indicate which Rook can castle if they are on the same side of King.
4396        For the initial position we grant rights to the outermost Rooks,
4397        and remember thos rights, and we then copy them on positions
4398        later in an FRC game. This means WB might not recognize castlings with
4399        Rooks that have moved back to their original position as illegal,
4400        but in ICS mode that is not its job anyway.
4401     */
4402     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4403     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4404
4405         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4406             if(board[0][i] == WhiteRook) j = i;
4407         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4408         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4409             if(board[0][i] == WhiteRook) j = i;
4410         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4411         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4412             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4413         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4414         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4415             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4416         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4417
4418         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4419         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4420             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4421         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4422             if(board[BOARD_HEIGHT-1][k] == bKing)
4423                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4424         if(gameInfo.variant == VariantTwoKings) {
4425             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4426             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4427             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4428         }
4429     } else { int r;
4430         r = boards[moveNum][CASTLING][0] = initialRights[0];
4431         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4432         r = boards[moveNum][CASTLING][1] = initialRights[1];
4433         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4434         r = boards[moveNum][CASTLING][3] = initialRights[3];
4435         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4436         r = boards[moveNum][CASTLING][4] = initialRights[4];
4437         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4438         /* wildcastle kludge: always assume King has rights */
4439         r = boards[moveNum][CASTLING][2] = initialRights[2];
4440         r = boards[moveNum][CASTLING][5] = initialRights[5];
4441     }
4442     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4443     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4444
4445
4446     if (ics_getting_history == H_GOT_REQ_HEADER ||
4447         ics_getting_history == H_GOT_UNREQ_HEADER) {
4448         /* This was an initial position from a move list, not
4449            the current position */
4450         return;
4451     }
4452
4453     /* Update currentMove and known move number limits */
4454     newMove = newGame || moveNum > forwardMostMove;
4455
4456     if (newGame) {
4457         forwardMostMove = backwardMostMove = currentMove = moveNum;
4458         if (gameMode == IcsExamining && moveNum == 0) {
4459           /* Workaround for ICS limitation: we are not told the wild
4460              type when starting to examine a game.  But if we ask for
4461              the move list, the move list header will tell us */
4462             ics_getting_history = H_REQUESTED;
4463             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4464             SendToICS(str);
4465         }
4466     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4467                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4468 #if ZIPPY
4469         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4470         /* [HGM] applied this also to an engine that is silently watching        */
4471         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4472             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4473             gameInfo.variant == currentlyInitializedVariant) {
4474           takeback = forwardMostMove - moveNum;
4475           for (i = 0; i < takeback; i++) {
4476             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4477             SendToProgram("undo\n", &first);
4478           }
4479         }
4480 #endif
4481
4482         forwardMostMove = moveNum;
4483         if (!pausing || currentMove > forwardMostMove)
4484           currentMove = forwardMostMove;
4485     } else {
4486         /* New part of history that is not contiguous with old part */
4487         if (pausing && gameMode == IcsExamining) {
4488             pauseExamInvalid = TRUE;
4489             forwardMostMove = pauseExamForwardMostMove;
4490             return;
4491         }
4492         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4493 #if ZIPPY
4494             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4495                 // [HGM] when we will receive the move list we now request, it will be
4496                 // fed to the engine from the first move on. So if the engine is not
4497                 // in the initial position now, bring it there.
4498                 InitChessProgram(&first, 0);
4499             }
4500 #endif
4501             ics_getting_history = H_REQUESTED;
4502             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4503             SendToICS(str);
4504         }
4505         forwardMostMove = backwardMostMove = currentMove = moveNum;
4506     }
4507
4508     /* Update the clocks */
4509     if (strchr(elapsed_time, '.')) {
4510       /* Time is in ms */
4511       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4512       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4513     } else {
4514       /* Time is in seconds */
4515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4517     }
4518
4519
4520 #if ZIPPY
4521     if (appData.zippyPlay && newGame &&
4522         gameMode != IcsObserving && gameMode != IcsIdle &&
4523         gameMode != IcsExamining)
4524       ZippyFirstBoard(moveNum, basetime, increment);
4525 #endif
4526
4527     /* Put the move on the move list, first converting
4528        to canonical algebraic form. */
4529     if (moveNum > 0) {
4530   if (appData.debugMode) {
4531     if (appData.debugMode) { int f = forwardMostMove;
4532         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4533                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4534                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4535     }
4536     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4537     fprintf(debugFP, "moveNum = %d\n", moveNum);
4538     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4539     setbuf(debugFP, NULL);
4540   }
4541         if (moveNum <= backwardMostMove) {
4542             /* We don't know what the board looked like before
4543                this move.  Punt. */
4544           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4545             strcat(parseList[moveNum - 1], " ");
4546             strcat(parseList[moveNum - 1], elapsed_time);
4547             moveList[moveNum - 1][0] = NULLCHAR;
4548         } else if (strcmp(move_str, "none") == 0) {
4549             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4550             /* Again, we don't know what the board looked like;
4551                this is really the start of the game. */
4552             parseList[moveNum - 1][0] = NULLCHAR;
4553             moveList[moveNum - 1][0] = NULLCHAR;
4554             backwardMostMove = moveNum;
4555             startedFromSetupPosition = TRUE;
4556             fromX = fromY = toX = toY = -1;
4557         } else {
4558           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4559           //                 So we parse the long-algebraic move string in stead of the SAN move
4560           int valid; char buf[MSG_SIZ], *prom;
4561
4562           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4563                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4564           // str looks something like "Q/a1-a2"; kill the slash
4565           if(str[1] == '/')
4566             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4567           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4568           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4569                 strcat(buf, prom); // long move lacks promo specification!
4570           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4571                 if(appData.debugMode)
4572                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4573                 safeStrCpy(move_str, buf, MSG_SIZ);
4574           }
4575           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4576                                 &fromX, &fromY, &toX, &toY, &promoChar)
4577                || ParseOneMove(buf, moveNum - 1, &moveType,
4578                                 &fromX, &fromY, &toX, &toY, &promoChar);
4579           // end of long SAN patch
4580           if (valid) {
4581             (void) CoordsToAlgebraic(boards[moveNum - 1],
4582                                      PosFlags(moveNum - 1),
4583                                      fromY, fromX, toY, toX, promoChar,
4584                                      parseList[moveNum-1]);
4585             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4586               case MT_NONE:
4587               case MT_STALEMATE:
4588               default:
4589                 break;
4590               case MT_CHECK:
4591                 if(gameInfo.variant != VariantShogi)
4592                     strcat(parseList[moveNum - 1], "+");
4593                 break;
4594               case MT_CHECKMATE:
4595               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4596                 strcat(parseList[moveNum - 1], "#");
4597                 break;
4598             }
4599             strcat(parseList[moveNum - 1], " ");
4600             strcat(parseList[moveNum - 1], elapsed_time);
4601             /* currentMoveString is set as a side-effect of ParseOneMove */
4602             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4603             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4604             strcat(moveList[moveNum - 1], "\n");
4605
4606             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4607                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4608               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4609                 ChessSquare old, new = boards[moveNum][k][j];
4610                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4611                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4612                   if(old == new) continue;
4613                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4614                   else if(new == WhiteWazir || new == BlackWazir) {
4615                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4616                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4617                       else boards[moveNum][k][j] = old; // preserve type of Gold
4618                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4619                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4620               }
4621           } else {
4622             /* Move from ICS was illegal!?  Punt. */
4623             if (appData.debugMode) {
4624               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4625               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4626             }
4627             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4628             strcat(parseList[moveNum - 1], " ");
4629             strcat(parseList[moveNum - 1], elapsed_time);
4630             moveList[moveNum - 1][0] = NULLCHAR;
4631             fromX = fromY = toX = toY = -1;
4632           }
4633         }
4634   if (appData.debugMode) {
4635     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4636     setbuf(debugFP, NULL);
4637   }
4638
4639 #if ZIPPY
4640         /* Send move to chess program (BEFORE animating it). */
4641         if (appData.zippyPlay && !newGame && newMove &&
4642            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4643
4644             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4645                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4646                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4647                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4648                             move_str);
4649                     DisplayError(str, 0);
4650                 } else {
4651                     if (first.sendTime) {
4652                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4653                     }
4654                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4655                     if (firstMove && !bookHit) {
4656                         firstMove = FALSE;
4657                         if (first.useColors) {
4658                           SendToProgram(gameMode == IcsPlayingWhite ?
4659                                         "white\ngo\n" :
4660                                         "black\ngo\n", &first);
4661                         } else {
4662                           SendToProgram("go\n", &first);
4663                         }
4664                         first.maybeThinking = TRUE;
4665                     }
4666                 }
4667             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4668               if (moveList[moveNum - 1][0] == NULLCHAR) {
4669                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4670                 DisplayError(str, 0);
4671               } else {
4672                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4673                 SendMoveToProgram(moveNum - 1, &first);
4674               }
4675             }
4676         }
4677 #endif
4678     }
4679
4680     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4681         /* If move comes from a remote source, animate it.  If it
4682            isn't remote, it will have already been animated. */
4683         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4684             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4685         }
4686         if (!pausing && appData.highlightLastMove) {
4687             SetHighlights(fromX, fromY, toX, toY);
4688         }
4689     }
4690
4691     /* Start the clocks */
4692     whiteFlag = blackFlag = FALSE;
4693     appData.clockMode = !(basetime == 0 && increment == 0);
4694     if (ticking == 0) {
4695       ics_clock_paused = TRUE;
4696       StopClocks();
4697     } else if (ticking == 1) {
4698       ics_clock_paused = FALSE;
4699     }
4700     if (gameMode == IcsIdle ||
4701         relation == RELATION_OBSERVING_STATIC ||
4702         relation == RELATION_EXAMINING ||
4703         ics_clock_paused)
4704       DisplayBothClocks();
4705     else
4706       StartClocks();
4707
4708     /* Display opponents and material strengths */
4709     if (gameInfo.variant != VariantBughouse &&
4710         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4711         if (tinyLayout || smallLayout) {
4712             if(gameInfo.variant == VariantNormal)
4713               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4714                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4715                     basetime, increment);
4716             else
4717               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4718                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4719                     basetime, increment, (int) gameInfo.variant);
4720         } else {
4721             if(gameInfo.variant == VariantNormal)
4722               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4723                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4724                     basetime, increment);
4725             else
4726               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4727                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4728                     basetime, increment, VariantName(gameInfo.variant));
4729         }
4730         DisplayTitle(str);
4731   if (appData.debugMode) {
4732     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4733   }
4734     }
4735
4736
4737     /* Display the board */
4738     if (!pausing && !appData.noGUI) {
4739
4740       if (appData.premove)
4741           if (!gotPremove ||
4742              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4743              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4744               ClearPremoveHighlights();
4745
4746       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4747         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4748       DrawPosition(j, boards[currentMove]);
4749
4750       DisplayMove(moveNum - 1);
4751       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4752             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4753               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4754         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4755       }
4756     }
4757
4758     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4759 #if ZIPPY
4760     if(bookHit) { // [HGM] book: simulate book reply
4761         static char bookMove[MSG_SIZ]; // a bit generous?
4762
4763         programStats.nodes = programStats.depth = programStats.time =
4764         programStats.score = programStats.got_only_move = 0;
4765         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4766
4767         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4768         strcat(bookMove, bookHit);
4769         HandleMachineMove(bookMove, &first);
4770     }
4771 #endif
4772 }
4773
4774 void
4775 GetMoveListEvent()
4776 {
4777     char buf[MSG_SIZ];
4778     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4779         ics_getting_history = H_REQUESTED;
4780         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4781         SendToICS(buf);
4782     }
4783 }
4784
4785 void
4786 AnalysisPeriodicEvent(force)
4787      int force;
4788 {
4789     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4790          && !force) || !appData.periodicUpdates)
4791       return;
4792
4793     /* Send . command to Crafty to collect stats */
4794     SendToProgram(".\n", &first);
4795
4796     /* Don't send another until we get a response (this makes
4797        us stop sending to old Crafty's which don't understand
4798        the "." command (sending illegal cmds resets node count & time,
4799        which looks bad)) */
4800     programStats.ok_to_send = 0;
4801 }
4802
4803 void ics_update_width(new_width)
4804         int new_width;
4805 {
4806         ics_printf("set width %d\n", new_width);
4807 }
4808
4809 void
4810 SendMoveToProgram(moveNum, cps)
4811      int moveNum;
4812      ChessProgramState *cps;
4813 {
4814     char buf[MSG_SIZ];
4815
4816     if (cps->useUsermove) {
4817       SendToProgram("usermove ", cps);
4818     }
4819     if (cps->useSAN) {
4820       char *space;
4821       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4822         int len = space - parseList[moveNum];
4823         memcpy(buf, parseList[moveNum], len);
4824         buf[len++] = '\n';
4825         buf[len] = NULLCHAR;
4826       } else {
4827         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4828       }
4829       SendToProgram(buf, cps);
4830     } else {
4831       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4832         AlphaRank(moveList[moveNum], 4);
4833         SendToProgram(moveList[moveNum], cps);
4834         AlphaRank(moveList[moveNum], 4); // and back
4835       } else
4836       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4837        * the engine. It would be nice to have a better way to identify castle
4838        * moves here. */
4839       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4840                                                                          && cps->useOOCastle) {
4841         int fromX = moveList[moveNum][0] - AAA;
4842         int fromY = moveList[moveNum][1] - ONE;
4843         int toX = moveList[moveNum][2] - AAA;
4844         int toY = moveList[moveNum][3] - ONE;
4845         if((boards[moveNum][fromY][fromX] == WhiteKing
4846             && boards[moveNum][toY][toX] == WhiteRook)
4847            || (boards[moveNum][fromY][fromX] == BlackKing
4848                && boards[moveNum][toY][toX] == BlackRook)) {
4849           if(toX > fromX) SendToProgram("O-O\n", cps);
4850           else SendToProgram("O-O-O\n", cps);
4851         }
4852         else SendToProgram(moveList[moveNum], cps);
4853       }
4854       else SendToProgram(moveList[moveNum], cps);
4855       /* End of additions by Tord */
4856     }
4857
4858     /* [HGM] setting up the opening has brought engine in force mode! */
4859     /*       Send 'go' if we are in a mode where machine should play. */
4860     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4861         (gameMode == TwoMachinesPlay   ||
4862 #if ZIPPY
4863          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4864 #endif
4865          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4866         SendToProgram("go\n", cps);
4867   if (appData.debugMode) {
4868     fprintf(debugFP, "(extra)\n");
4869   }
4870     }
4871     setboardSpoiledMachineBlack = 0;
4872 }
4873
4874 void
4875 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4876      ChessMove moveType;
4877      int fromX, fromY, toX, toY;
4878      char promoChar;
4879 {
4880     char user_move[MSG_SIZ];
4881
4882     switch (moveType) {
4883       default:
4884         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4885                 (int)moveType, fromX, fromY, toX, toY);
4886         DisplayError(user_move + strlen("say "), 0);
4887         break;
4888       case WhiteKingSideCastle:
4889       case BlackKingSideCastle:
4890       case WhiteQueenSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* PUSH Fabien */
4893       case WhiteHSideCastleFR:
4894       case BlackHSideCastleFR:
4895       /* POP Fabien */
4896         snprintf(user_move, MSG_SIZ, "o-o\n");
4897         break;
4898       case WhiteQueenSideCastle:
4899       case BlackQueenSideCastle:
4900       case WhiteKingSideCastleWild:
4901       case BlackKingSideCastleWild:
4902       /* PUSH Fabien */
4903       case WhiteASideCastleFR:
4904       case BlackASideCastleFR:
4905       /* POP Fabien */
4906         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4907         break;
4908       case WhiteNonPromotion:
4909       case BlackNonPromotion:
4910         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4911         break;
4912       case WhitePromotion:
4913       case BlackPromotion:
4914         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4915           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4916                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4917                 PieceToChar(WhiteFerz));
4918         else if(gameInfo.variant == VariantGreat)
4919           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921                 PieceToChar(WhiteMan));
4922         else
4923           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 promoChar);
4926         break;
4927       case WhiteDrop:
4928       case BlackDrop:
4929       drop:
4930         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4931                  ToUpper(PieceToChar((ChessSquare) fromX)),
4932                  AAA + toX, ONE + toY);
4933         break;
4934       case IllegalMove:  /* could be a variant we don't quite understand */
4935         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4936       case NormalMove:
4937       case WhiteCapturesEnPassant:
4938       case BlackCapturesEnPassant:
4939         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4940                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4941         break;
4942     }
4943     SendToICS(user_move);
4944     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4945         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4946 }
4947
4948 void
4949 UploadGameEvent()
4950 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4951     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4952     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4953     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4954         DisplayError("You cannot do this while you are playing or observing", 0);
4955         return;
4956     }
4957     if(gameMode != IcsExamining) { // is this ever not the case?
4958         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4959
4960         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4961           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4962         } else { // on FICS we must first go to general examine mode
4963           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4964         }
4965         if(gameInfo.variant != VariantNormal) {
4966             // try figure out wild number, as xboard names are not always valid on ICS
4967             for(i=1; i<=36; i++) {
4968               snprintf(buf, MSG_SIZ, "wild/%d", i);
4969                 if(StringToVariant(buf) == gameInfo.variant) break;
4970             }
4971             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4972             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4973             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4974         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4975         SendToICS(ics_prefix);
4976         SendToICS(buf);
4977         if(startedFromSetupPosition || backwardMostMove != 0) {
4978           fen = PositionToFEN(backwardMostMove, NULL);
4979           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4980             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4981             SendToICS(buf);
4982           } else { // FICS: everything has to set by separate bsetup commands
4983             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4984             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4985             SendToICS(buf);
4986             if(!WhiteOnMove(backwardMostMove)) {
4987                 SendToICS("bsetup tomove black\n");
4988             }
4989             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4990             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4991             SendToICS(buf);
4992             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4993             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4994             SendToICS(buf);
4995             i = boards[backwardMostMove][EP_STATUS];
4996             if(i >= 0) { // set e.p.
4997               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4998                 SendToICS(buf);
4999             }
5000             bsetup++;
5001           }
5002         }
5003       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5004             SendToICS("bsetup done\n"); // switch to normal examining.
5005     }
5006     for(i = backwardMostMove; i<last; i++) {
5007         char buf[20];
5008         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5009         SendToICS(buf);
5010     }
5011     SendToICS(ics_prefix);
5012     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5013 }
5014
5015 void
5016 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5017      int rf, ff, rt, ft;
5018      char promoChar;
5019      char move[7];
5020 {
5021     if (rf == DROP_RANK) {
5022       sprintf(move, "%c@%c%c\n",
5023                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5024     } else {
5025         if (promoChar == 'x' || promoChar == NULLCHAR) {
5026           sprintf(move, "%c%c%c%c\n",
5027                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5028         } else {
5029             sprintf(move, "%c%c%c%c%c\n",
5030                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5031         }
5032     }
5033 }
5034
5035 void
5036 ProcessICSInitScript(f)
5037      FILE *f;
5038 {
5039     char buf[MSG_SIZ];
5040
5041     while (fgets(buf, MSG_SIZ, f)) {
5042         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5043     }
5044
5045     fclose(f);
5046 }
5047
5048
5049 static int lastX, lastY, selectFlag, dragging;
5050
5051 void
5052 Sweep(int step)
5053 {
5054     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5055     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5056     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5057     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5058     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5059     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5060     do {
5061         promoSweep -= step;
5062         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5063         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5064         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5065         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5066         if(!step) step = 1;
5067     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5068             appData.testLegality && (promoSweep == king ||
5069             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5070     ChangeDragPiece(promoSweep);
5071 }
5072
5073 int PromoScroll(int x, int y)
5074 {
5075   int step = 0;
5076
5077   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5078   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5079   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5080   if(!step) return FALSE;
5081   lastX = x; lastY = y;
5082   if((promoSweep < BlackPawn) == flipView) step = -step;
5083   if(step > 0) selectFlag = 1;
5084   if(!selectFlag) Sweep(step);
5085   return FALSE;
5086 }
5087
5088 void
5089 NextPiece(int step)
5090 {
5091     ChessSquare piece = boards[currentMove][toY][toX];
5092     do {
5093         pieceSweep -= step;
5094         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5095         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5096         if(!step) step = -1;
5097     } while(PieceToChar(pieceSweep) == '.');
5098     boards[currentMove][toY][toX] = pieceSweep;
5099     DrawPosition(FALSE, boards[currentMove]);
5100     boards[currentMove][toY][toX] = piece;
5101 }
5102 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5103 void
5104 AlphaRank(char *move, int n)
5105 {
5106 //    char *p = move, c; int x, y;
5107
5108     if (appData.debugMode) {
5109         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5110     }
5111
5112     if(move[1]=='*' &&
5113        move[2]>='0' && move[2]<='9' &&
5114        move[3]>='a' && move[3]<='x'    ) {
5115         move[1] = '@';
5116         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5117         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5118     } else
5119     if(move[0]>='0' && move[0]<='9' &&
5120        move[1]>='a' && move[1]<='x' &&
5121        move[2]>='0' && move[2]<='9' &&
5122        move[3]>='a' && move[3]<='x'    ) {
5123         /* input move, Shogi -> normal */
5124         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5125         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5126         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5127         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5128     } else
5129     if(move[1]=='@' &&
5130        move[3]>='0' && move[3]<='9' &&
5131        move[2]>='a' && move[2]<='x'    ) {
5132         move[1] = '*';
5133         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5134         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5135     } else
5136     if(
5137        move[0]>='a' && move[0]<='x' &&
5138        move[3]>='0' && move[3]<='9' &&
5139        move[2]>='a' && move[2]<='x'    ) {
5140          /* output move, normal -> Shogi */
5141         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5142         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5143         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5144         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5145         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5146     }
5147     if (appData.debugMode) {
5148         fprintf(debugFP, "   out = '%s'\n", move);
5149     }
5150 }
5151
5152 char yy_textstr[8000];
5153
5154 /* Parser for moves from gnuchess, ICS, or user typein box */
5155 Boolean
5156 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5157      char *move;
5158      int moveNum;
5159      ChessMove *moveType;
5160      int *fromX, *fromY, *toX, *toY;
5161      char *promoChar;
5162 {
5163     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5164
5165     switch (*moveType) {
5166       case WhitePromotion:
5167       case BlackPromotion:
5168       case WhiteNonPromotion:
5169       case BlackNonPromotion:
5170       case NormalMove:
5171       case WhiteCapturesEnPassant:
5172       case BlackCapturesEnPassant:
5173       case WhiteKingSideCastle:
5174       case WhiteQueenSideCastle:
5175       case BlackKingSideCastle:
5176       case BlackQueenSideCastle:
5177       case WhiteKingSideCastleWild:
5178       case WhiteQueenSideCastleWild:
5179       case BlackKingSideCastleWild:
5180       case BlackQueenSideCastleWild:
5181       /* Code added by Tord: */
5182       case WhiteHSideCastleFR:
5183       case WhiteASideCastleFR:
5184       case BlackHSideCastleFR:
5185       case BlackASideCastleFR:
5186       /* End of code added by Tord */
5187       case IllegalMove:         /* bug or odd chess variant */
5188         *fromX = currentMoveString[0] - AAA;
5189         *fromY = currentMoveString[1] - ONE;
5190         *toX = currentMoveString[2] - AAA;
5191         *toY = currentMoveString[3] - ONE;
5192         *promoChar = currentMoveString[4];
5193         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5194             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5195     if (appData.debugMode) {
5196         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5197     }
5198             *fromX = *fromY = *toX = *toY = 0;
5199             return FALSE;
5200         }
5201         if (appData.testLegality) {
5202           return (*moveType != IllegalMove);
5203         } else {
5204           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5205                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5206         }
5207
5208       case WhiteDrop:
5209       case BlackDrop:
5210         *fromX = *moveType == WhiteDrop ?
5211           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5212           (int) CharToPiece(ToLower(currentMoveString[0]));
5213         *fromY = DROP_RANK;
5214         *toX = currentMoveString[2] - AAA;
5215         *toY = currentMoveString[3] - ONE;
5216         *promoChar = NULLCHAR;
5217         return TRUE;
5218
5219       case AmbiguousMove:
5220       case ImpossibleMove:
5221       case EndOfFile:
5222       case ElapsedTime:
5223       case Comment:
5224       case PGNTag:
5225       case NAG:
5226       case WhiteWins:
5227       case BlackWins:
5228       case GameIsDrawn:
5229       default:
5230     if (appData.debugMode) {
5231         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5232     }
5233         /* bug? */
5234         *fromX = *fromY = *toX = *toY = 0;
5235         *promoChar = NULLCHAR;
5236         return FALSE;
5237     }
5238 }
5239
5240 Boolean pushed = FALSE;
5241
5242 void
5243 ParsePV(char *pv, Boolean storeComments)
5244 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5245   int fromX, fromY, toX, toY; char promoChar;
5246   ChessMove moveType;
5247   Boolean valid;
5248   int nr = 0;
5249
5250   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5251     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5252     pushed = TRUE;
5253   }
5254   endPV = forwardMostMove;
5255   do {
5256     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5257     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5258     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5259 if(appData.debugMode){
5260 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);
5261 }
5262     if(!valid && nr == 0 &&
5263        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5264         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5265         // Hande case where played move is different from leading PV move
5266         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5267         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5268         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5269         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5270           endPV += 2; // if position different, keep this
5271           moveList[endPV-1][0] = fromX + AAA;
5272           moveList[endPV-1][1] = fromY + ONE;
5273           moveList[endPV-1][2] = toX + AAA;
5274           moveList[endPV-1][3] = toY + ONE;
5275           parseList[endPV-1][0] = NULLCHAR;
5276           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5277         }
5278       }
5279     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5280     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5281     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5282     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5283         valid++; // allow comments in PV
5284         continue;
5285     }
5286     nr++;
5287     if(endPV+1 > framePtr) break; // no space, truncate
5288     if(!valid) break;
5289     endPV++;
5290     CopyBoard(boards[endPV], boards[endPV-1]);
5291     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5292     moveList[endPV-1][0] = fromX + AAA;
5293     moveList[endPV-1][1] = fromY + ONE;
5294     moveList[endPV-1][2] = toX + AAA;
5295     moveList[endPV-1][3] = toY + ONE;
5296     moveList[endPV-1][4] = promoChar;
5297     moveList[endPV-1][5] = NULLCHAR;
5298     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5299     if(storeComments)
5300         CoordsToAlgebraic(boards[endPV - 1],
5301                              PosFlags(endPV - 1),
5302                              fromY, fromX, toY, toX, promoChar,
5303                              parseList[endPV - 1]);
5304     else
5305         parseList[endPV-1][0] = NULLCHAR;
5306   } while(valid);
5307   currentMove = endPV;
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 Boolean
5315 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5316 {
5317         int startPV;
5318         char *p;
5319
5320         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5321         lastX = x; lastY = y;
5322         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5323         startPV = index;
5324         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5325         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5326         index = startPV;
5327         do{ while(buf[index] && buf[index] != '\n') index++;
5328         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5329         buf[index] = 0;
5330         ParsePV(buf+startPV, FALSE);
5331         *start = startPV; *end = index-1;
5332         return TRUE;
5333 }
5334
5335 Boolean
5336 LoadPV(int x, int y)
5337 { // called on right mouse click to load PV
5338   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5339   lastX = x; lastY = y;
5340   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5341   return TRUE;
5342 }
5343
5344 void
5345 UnLoadPV()
5346 {
5347   if(endPV < 0) return;
5348   endPV = -1;
5349   currentMove = forwardMostMove;
5350   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5351   ClearPremoveHighlights();
5352   DrawPosition(TRUE, boards[currentMove]);
5353 }
5354
5355 void
5356 MovePV(int x, int y, int h)
5357 { // step through PV based on mouse coordinates (called on mouse move)
5358   int margin = h>>3, step = 0;
5359
5360   // we must somehow check if right button is still down (might be released off board!)
5361   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5362   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5363   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5364   if(!step) return;
5365   lastX = x; lastY = y;
5366
5367   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5368   if(endPV < 0) return;
5369   if(y < margin) step = 1; else
5370   if(y > h - margin) step = -1;
5371   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5372   currentMove += step;
5373   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5374   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5375                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5376   DrawPosition(FALSE, boards[currentMove]);
5377 }
5378
5379
5380 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5381 // All positions will have equal probability, but the current method will not provide a unique
5382 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5383 #define DARK 1
5384 #define LITE 2
5385 #define ANY 3
5386
5387 int squaresLeft[4];
5388 int piecesLeft[(int)BlackPawn];
5389 int seed, nrOfShuffles;
5390
5391 void GetPositionNumber()
5392 {       // sets global variable seed
5393         int i;
5394
5395         seed = appData.defaultFrcPosition;
5396         if(seed < 0) { // randomize based on time for negative FRC position numbers
5397                 for(i=0; i<50; i++) seed += random();
5398                 seed = random() ^ random() >> 8 ^ random() << 8;
5399                 if(seed<0) seed = -seed;
5400         }
5401 }
5402
5403 int put(Board board, int pieceType, int rank, int n, int shade)
5404 // put the piece on the (n-1)-th empty squares of the given shade
5405 {
5406         int i;
5407
5408         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5409                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5410                         board[rank][i] = (ChessSquare) pieceType;
5411                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5412                         squaresLeft[ANY]--;
5413                         piecesLeft[pieceType]--;
5414                         return i;
5415                 }
5416         }
5417         return -1;
5418 }
5419
5420
5421 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5422 // calculate where the next piece goes, (any empty square), and put it there
5423 {
5424         int i;
5425
5426         i = seed % squaresLeft[shade];
5427         nrOfShuffles *= squaresLeft[shade];
5428         seed /= squaresLeft[shade];
5429         put(board, pieceType, rank, i, shade);
5430 }
5431
5432 void AddTwoPieces(Board board, int pieceType, int rank)
5433 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5434 {
5435         int i, n=squaresLeft[ANY], j=n-1, k;
5436
5437         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5438         i = seed % k;  // pick one
5439         nrOfShuffles *= k;
5440         seed /= k;
5441         while(i >= j) i -= j--;
5442         j = n - 1 - j; i += j;
5443         put(board, pieceType, rank, j, ANY);
5444         put(board, pieceType, rank, i, ANY);
5445 }
5446
5447 void SetUpShuffle(Board board, int number)
5448 {
5449         int i, p, first=1;
5450
5451         GetPositionNumber(); nrOfShuffles = 1;
5452
5453         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5454         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5455         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5456
5457         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5458
5459         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5460             p = (int) board[0][i];
5461             if(p < (int) BlackPawn) piecesLeft[p] ++;
5462             board[0][i] = EmptySquare;
5463         }
5464
5465         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5466             // shuffles restricted to allow normal castling put KRR first
5467             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5468                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5469             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5470                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5471             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5472                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5473             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5474                 put(board, WhiteRook, 0, 0, ANY);
5475             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5476         }
5477
5478         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5479             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5480             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5481                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5482                 while(piecesLeft[p] >= 2) {
5483                     AddOnePiece(board, p, 0, LITE);
5484                     AddOnePiece(board, p, 0, DARK);
5485                 }
5486                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5487             }
5488
5489         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5490             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5491             // but we leave King and Rooks for last, to possibly obey FRC restriction
5492             if(p == (int)WhiteRook) continue;
5493             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5494             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5495         }
5496
5497         // now everything is placed, except perhaps King (Unicorn) and Rooks
5498
5499         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5500             // Last King gets castling rights
5501             while(piecesLeft[(int)WhiteUnicorn]) {
5502                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5503                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5504             }
5505
5506             while(piecesLeft[(int)WhiteKing]) {
5507                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5508                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5509             }
5510
5511
5512         } else {
5513             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5514             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5515         }
5516
5517         // Only Rooks can be left; simply place them all
5518         while(piecesLeft[(int)WhiteRook]) {
5519                 i = put(board, WhiteRook, 0, 0, ANY);
5520                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5521                         if(first) {
5522                                 first=0;
5523                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5524                         }
5525                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5526                 }
5527         }
5528         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5529             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5530         }
5531
5532         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5533 }
5534
5535 int SetCharTable( char *table, const char * map )
5536 /* [HGM] moved here from winboard.c because of its general usefulness */
5537 /*       Basically a safe strcpy that uses the last character as King */
5538 {
5539     int result = FALSE; int NrPieces;
5540
5541     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5542                     && NrPieces >= 12 && !(NrPieces&1)) {
5543         int i; /* [HGM] Accept even length from 12 to 34 */
5544
5545         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5546         for( i=0; i<NrPieces/2-1; i++ ) {
5547             table[i] = map[i];
5548             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5549         }
5550         table[(int) WhiteKing]  = map[NrPieces/2-1];
5551         table[(int) BlackKing]  = map[NrPieces-1];
5552
5553         result = TRUE;
5554     }
5555
5556     return result;
5557 }
5558
5559 void Prelude(Board board)
5560 {       // [HGM] superchess: random selection of exo-pieces
5561         int i, j, k; ChessSquare p;
5562         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5563
5564         GetPositionNumber(); // use FRC position number
5565
5566         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5567             SetCharTable(pieceToChar, appData.pieceToCharTable);
5568             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5569                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5570         }
5571
5572         j = seed%4;                 seed /= 4;
5573         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5574         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5575         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5576         j = seed%3 + (seed%3 >= j); seed /= 3;
5577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5580         j = seed%3;                 seed /= 3;
5581         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5584         j = seed%2 + (seed%2 >= j); seed /= 2;
5585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5588         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5589         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5590         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5591         put(board, exoPieces[0],    0, 0, ANY);
5592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5593 }
5594
5595 void
5596 InitPosition(redraw)
5597      int redraw;
5598 {
5599     ChessSquare (* pieces)[BOARD_FILES];
5600     int i, j, pawnRow, overrule,
5601     oldx = gameInfo.boardWidth,
5602     oldy = gameInfo.boardHeight,
5603     oldh = gameInfo.holdingsWidth;
5604     static int oldv;
5605
5606     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5607
5608     /* [AS] Initialize pv info list [HGM] and game status */
5609     {
5610         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5611             pvInfoList[i].depth = 0;
5612             boards[i][EP_STATUS] = EP_NONE;
5613             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5614         }
5615
5616         initialRulePlies = 0; /* 50-move counter start */
5617
5618         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5619         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5620     }
5621
5622
5623     /* [HGM] logic here is completely changed. In stead of full positions */
5624     /* the initialized data only consist of the two backranks. The switch */
5625     /* selects which one we will use, which is than copied to the Board   */
5626     /* initialPosition, which for the rest is initialized by Pawns and    */
5627     /* empty squares. This initial position is then copied to boards[0],  */
5628     /* possibly after shuffling, so that it remains available.            */
5629
5630     gameInfo.holdingsWidth = 0; /* default board sizes */
5631     gameInfo.boardWidth    = 8;
5632     gameInfo.boardHeight   = 8;
5633     gameInfo.holdingsSize  = 0;
5634     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5635     for(i=0; i<BOARD_FILES-2; i++)
5636       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5637     initialPosition[EP_STATUS] = EP_NONE;
5638     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5639     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5640          SetCharTable(pieceNickName, appData.pieceNickNames);
5641     else SetCharTable(pieceNickName, "............");
5642     pieces = FIDEArray;
5643
5644     switch (gameInfo.variant) {
5645     case VariantFischeRandom:
5646       shuffleOpenings = TRUE;
5647     default:
5648       break;
5649     case VariantShatranj:
5650       pieces = ShatranjArray;
5651       nrCastlingRights = 0;
5652       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5653       break;
5654     case VariantMakruk:
5655       pieces = makrukArray;
5656       nrCastlingRights = 0;
5657       startedFromSetupPosition = TRUE;
5658       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5659       break;
5660     case VariantTwoKings:
5661       pieces = twoKingsArray;
5662       break;
5663     case VariantCapaRandom:
5664       shuffleOpenings = TRUE;
5665     case VariantCapablanca:
5666       pieces = CapablancaArray;
5667       gameInfo.boardWidth = 10;
5668       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5669       break;
5670     case VariantGothic:
5671       pieces = GothicArray;
5672       gameInfo.boardWidth = 10;
5673       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5674       break;
5675     case VariantSChess:
5676       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5677       gameInfo.holdingsSize = 7;
5678       break;
5679     case VariantJanus:
5680       pieces = JanusArray;
5681       gameInfo.boardWidth = 10;
5682       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5683       nrCastlingRights = 6;
5684         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5685         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5686         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5687         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5688         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5689         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5690       break;
5691     case VariantFalcon:
5692       pieces = FalconArray;
5693       gameInfo.boardWidth = 10;
5694       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5695       break;
5696     case VariantXiangqi:
5697       pieces = XiangqiArray;
5698       gameInfo.boardWidth  = 9;
5699       gameInfo.boardHeight = 10;
5700       nrCastlingRights = 0;
5701       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5702       break;
5703     case VariantShogi:
5704       pieces = ShogiArray;
5705       gameInfo.boardWidth  = 9;
5706       gameInfo.boardHeight = 9;
5707       gameInfo.holdingsSize = 7;
5708       nrCastlingRights = 0;
5709       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5710       break;
5711     case VariantCourier:
5712       pieces = CourierArray;
5713       gameInfo.boardWidth  = 12;
5714       nrCastlingRights = 0;
5715       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5716       break;
5717     case VariantKnightmate:
5718       pieces = KnightmateArray;
5719       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5720       break;
5721     case VariantSpartan:
5722       pieces = SpartanArray;
5723       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5724       break;
5725     case VariantFairy:
5726       pieces = fairyArray;
5727       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5728       break;
5729     case VariantGreat:
5730       pieces = GreatArray;
5731       gameInfo.boardWidth = 10;
5732       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5733       gameInfo.holdingsSize = 8;
5734       break;
5735     case VariantSuper:
5736       pieces = FIDEArray;
5737       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5738       gameInfo.holdingsSize = 8;
5739       startedFromSetupPosition = TRUE;
5740       break;
5741     case VariantCrazyhouse:
5742     case VariantBughouse:
5743       pieces = FIDEArray;
5744       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5745       gameInfo.holdingsSize = 5;
5746       break;
5747     case VariantWildCastle:
5748       pieces = FIDEArray;
5749       /* !!?shuffle with kings guaranteed to be on d or e file */
5750       shuffleOpenings = 1;
5751       break;
5752     case VariantNoCastle:
5753       pieces = FIDEArray;
5754       nrCastlingRights = 0;
5755       /* !!?unconstrained back-rank shuffle */
5756       shuffleOpenings = 1;
5757       break;
5758     }
5759
5760     overrule = 0;
5761     if(appData.NrFiles >= 0) {
5762         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5763         gameInfo.boardWidth = appData.NrFiles;
5764     }
5765     if(appData.NrRanks >= 0) {
5766         gameInfo.boardHeight = appData.NrRanks;
5767     }
5768     if(appData.holdingsSize >= 0) {
5769         i = appData.holdingsSize;
5770         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5771         gameInfo.holdingsSize = i;
5772     }
5773     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5774     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5775         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5776
5777     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5778     if(pawnRow < 1) pawnRow = 1;
5779     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5780
5781     /* User pieceToChar list overrules defaults */
5782     if(appData.pieceToCharTable != NULL)
5783         SetCharTable(pieceToChar, appData.pieceToCharTable);
5784
5785     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5786
5787         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5788             s = (ChessSquare) 0; /* account holding counts in guard band */
5789         for( i=0; i<BOARD_HEIGHT; i++ )
5790             initialPosition[i][j] = s;
5791
5792         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5793         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5794         initialPosition[pawnRow][j] = WhitePawn;
5795         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5796         if(gameInfo.variant == VariantXiangqi) {
5797             if(j&1) {
5798                 initialPosition[pawnRow][j] =
5799                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5800                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5801                    initialPosition[2][j] = WhiteCannon;
5802                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5803                 }
5804             }
5805         }
5806         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5807     }
5808     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5809
5810             j=BOARD_LEFT+1;
5811             initialPosition[1][j] = WhiteBishop;
5812             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5813             j=BOARD_RGHT-2;
5814             initialPosition[1][j] = WhiteRook;
5815             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5816     }
5817
5818     if( nrCastlingRights == -1) {
5819         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5820         /*       This sets default castling rights from none to normal corners   */
5821         /* Variants with other castling rights must set them themselves above    */
5822         nrCastlingRights = 6;
5823
5824         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5825         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5826         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5827         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5828         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5829         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5830      }
5831
5832      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5833      if(gameInfo.variant == VariantGreat) { // promotion commoners
5834         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5835         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5836         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5837         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5838      }
5839      if( gameInfo.variant == VariantSChess ) {
5840       initialPosition[1][0] = BlackMarshall;
5841       initialPosition[2][0] = BlackAngel;
5842       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5843       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5844       initialPosition[1][1] = initialPosition[2][1] = 
5845       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5846      }
5847   if (appData.debugMode) {
5848     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5849   }
5850     if(shuffleOpenings) {
5851         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5852         startedFromSetupPosition = TRUE;
5853     }
5854     if(startedFromPositionFile) {
5855       /* [HGM] loadPos: use PositionFile for every new game */
5856       CopyBoard(initialPosition, filePosition);
5857       for(i=0; i<nrCastlingRights; i++)
5858           initialRights[i] = filePosition[CASTLING][i];
5859       startedFromSetupPosition = TRUE;
5860     }
5861
5862     CopyBoard(boards[0], initialPosition);
5863
5864     if(oldx != gameInfo.boardWidth ||
5865        oldy != gameInfo.boardHeight ||
5866        oldv != gameInfo.variant ||
5867        oldh != gameInfo.holdingsWidth
5868                                          )
5869             InitDrawingSizes(-2 ,0);
5870
5871     oldv = gameInfo.variant;
5872     if (redraw)
5873       DrawPosition(TRUE, boards[currentMove]);
5874 }
5875
5876 void
5877 SendBoard(cps, moveNum)
5878      ChessProgramState *cps;
5879      int moveNum;
5880 {
5881     char message[MSG_SIZ];
5882
5883     if (cps->useSetboard) {
5884       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5885       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5886       SendToProgram(message, cps);
5887       free(fen);
5888
5889     } else {
5890       ChessSquare *bp;
5891       int i, j;
5892       /* Kludge to set black to move, avoiding the troublesome and now
5893        * deprecated "black" command.
5894        */
5895       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5896         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5897
5898       SendToProgram("edit\n", cps);
5899       SendToProgram("#\n", cps);
5900       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5901         bp = &boards[moveNum][i][BOARD_LEFT];
5902         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5903           if ((int) *bp < (int) BlackPawn) {
5904             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5905                     AAA + j, ONE + i);
5906             if(message[0] == '+' || message[0] == '~') {
5907               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5908                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5909                         AAA + j, ONE + i);
5910             }
5911             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5912                 message[1] = BOARD_RGHT   - 1 - j + '1';
5913                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5914             }
5915             SendToProgram(message, cps);
5916           }
5917         }
5918       }
5919
5920       SendToProgram("c\n", cps);
5921       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5922         bp = &boards[moveNum][i][BOARD_LEFT];
5923         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5924           if (((int) *bp != (int) EmptySquare)
5925               && ((int) *bp >= (int) BlackPawn)) {
5926             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5927                     AAA + j, ONE + i);
5928             if(message[0] == '+' || message[0] == '~') {
5929               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5930                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5931                         AAA + j, ONE + i);
5932             }
5933             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5934                 message[1] = BOARD_RGHT   - 1 - j + '1';
5935                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5936             }
5937             SendToProgram(message, cps);
5938           }
5939         }
5940       }
5941
5942       SendToProgram(".\n", cps);
5943     }
5944     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5945 }
5946
5947 ChessSquare
5948 DefaultPromoChoice(int white)
5949 {
5950     ChessSquare result;
5951     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5952         result = WhiteFerz; // no choice
5953     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5954         result= WhiteKing; // in Suicide Q is the last thing we want
5955     else if(gameInfo.variant == VariantSpartan)
5956         result = white ? WhiteQueen : WhiteAngel;
5957     else result = WhiteQueen;
5958     if(!white) result = WHITE_TO_BLACK result;
5959     return result;
5960 }
5961
5962 static int autoQueen; // [HGM] oneclick
5963
5964 int
5965 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5966 {
5967     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5968     /* [HGM] add Shogi promotions */
5969     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5970     ChessSquare piece;
5971     ChessMove moveType;
5972     Boolean premove;
5973
5974     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5975     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5976
5977     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5978       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5979         return FALSE;
5980
5981     piece = boards[currentMove][fromY][fromX];
5982     if(gameInfo.variant == VariantShogi) {
5983         promotionZoneSize = BOARD_HEIGHT/3;
5984         highestPromotingPiece = (int)WhiteFerz;
5985     } else if(gameInfo.variant == VariantMakruk) {
5986         promotionZoneSize = 3;
5987     }
5988
5989     // Treat Lance as Pawn when it is not representing Amazon
5990     if(gameInfo.variant != VariantSuper) {
5991         if(piece == WhiteLance) piece = WhitePawn; else
5992         if(piece == BlackLance) piece = BlackPawn;
5993     }
5994
5995     // next weed out all moves that do not touch the promotion zone at all
5996     if((int)piece >= BlackPawn) {
5997         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5998              return FALSE;
5999         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6000     } else {
6001         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6002            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6003     }
6004
6005     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6006
6007     // weed out mandatory Shogi promotions
6008     if(gameInfo.variant == VariantShogi) {
6009         if(piece >= BlackPawn) {
6010             if(toY == 0 && piece == BlackPawn ||
6011                toY == 0 && piece == BlackQueen ||
6012                toY <= 1 && piece == BlackKnight) {
6013                 *promoChoice = '+';
6014                 return FALSE;
6015             }
6016         } else {
6017             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6018                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6019                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6020                 *promoChoice = '+';
6021                 return FALSE;
6022             }
6023         }
6024     }
6025
6026     // weed out obviously illegal Pawn moves
6027     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6028         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6029         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6030         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6031         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6032         // note we are not allowed to test for valid (non-)capture, due to premove
6033     }
6034
6035     // we either have a choice what to promote to, or (in Shogi) whether to promote
6036     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6037         *promoChoice = PieceToChar(BlackFerz);  // no choice
6038         return FALSE;
6039     }
6040     // no sense asking what we must promote to if it is going to explode...
6041     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6042         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6043         return FALSE;
6044     }
6045     // give caller the default choice even if we will not make it
6046     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6047     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6048     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6049                            && gameInfo.variant != VariantShogi
6050                            && gameInfo.variant != VariantSuper) return FALSE;
6051     if(autoQueen) return FALSE; // predetermined
6052
6053     // suppress promotion popup on illegal moves that are not premoves
6054     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6055               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6056     if(appData.testLegality && !premove) {
6057         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6058                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6059         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6060             return FALSE;
6061     }
6062
6063     return TRUE;
6064 }
6065
6066 int
6067 InPalace(row, column)
6068      int row, column;
6069 {   /* [HGM] for Xiangqi */
6070     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6071          column < (BOARD_WIDTH + 4)/2 &&
6072          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6073     return FALSE;
6074 }
6075
6076 int
6077 PieceForSquare (x, y)
6078      int x;
6079      int y;
6080 {
6081   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6082      return -1;
6083   else
6084      return boards[currentMove][y][x];
6085 }
6086
6087 int
6088 OKToStartUserMove(x, y)
6089      int x, y;
6090 {
6091     ChessSquare from_piece;
6092     int white_piece;
6093
6094     if (matchMode) return FALSE;
6095     if (gameMode == EditPosition) return TRUE;
6096
6097     if (x >= 0 && y >= 0)
6098       from_piece = boards[currentMove][y][x];
6099     else
6100       from_piece = EmptySquare;
6101
6102     if (from_piece == EmptySquare) return FALSE;
6103
6104     white_piece = (int)from_piece >= (int)WhitePawn &&
6105       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6106
6107     switch (gameMode) {
6108       case PlayFromGameFile:
6109       case AnalyzeFile:
6110       case TwoMachinesPlay:
6111       case EndOfGame:
6112         return FALSE;
6113
6114       case IcsObserving:
6115       case IcsIdle:
6116         return FALSE;
6117
6118       case MachinePlaysWhite:
6119       case IcsPlayingBlack:
6120         if (appData.zippyPlay) return FALSE;
6121         if (white_piece) {
6122             DisplayMoveError(_("You are playing Black"));
6123             return FALSE;
6124         }
6125         break;
6126
6127       case MachinePlaysBlack:
6128       case IcsPlayingWhite:
6129         if (appData.zippyPlay) return FALSE;
6130         if (!white_piece) {
6131             DisplayMoveError(_("You are playing White"));
6132             return FALSE;
6133         }
6134         break;
6135
6136       case EditGame:
6137         if (!white_piece && WhiteOnMove(currentMove)) {
6138             DisplayMoveError(_("It is White's turn"));
6139             return FALSE;
6140         }
6141         if (white_piece && !WhiteOnMove(currentMove)) {
6142             DisplayMoveError(_("It is Black's turn"));
6143             return FALSE;
6144         }
6145         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6146             /* Editing correspondence game history */
6147             /* Could disallow this or prompt for confirmation */
6148             cmailOldMove = -1;
6149         }
6150         break;
6151
6152       case BeginningOfGame:
6153         if (appData.icsActive) return FALSE;
6154         if (!appData.noChessProgram) {
6155             if (!white_piece) {
6156                 DisplayMoveError(_("You are playing White"));
6157                 return FALSE;
6158             }
6159         }
6160         break;
6161
6162       case Training:
6163         if (!white_piece && WhiteOnMove(currentMove)) {
6164             DisplayMoveError(_("It is White's turn"));
6165             return FALSE;
6166         }
6167         if (white_piece && !WhiteOnMove(currentMove)) {
6168             DisplayMoveError(_("It is Black's turn"));
6169             return FALSE;
6170         }
6171         break;
6172
6173       default:
6174       case IcsExamining:
6175         break;
6176     }
6177     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6178         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6179         && gameMode != AnalyzeFile && gameMode != Training) {
6180         DisplayMoveError(_("Displayed position is not current"));
6181         return FALSE;
6182     }
6183     return TRUE;
6184 }
6185
6186 Boolean
6187 OnlyMove(int *x, int *y, Boolean captures) {
6188     DisambiguateClosure cl;
6189     if (appData.zippyPlay) return FALSE;
6190     switch(gameMode) {
6191       case MachinePlaysBlack:
6192       case IcsPlayingWhite:
6193       case BeginningOfGame:
6194         if(!WhiteOnMove(currentMove)) return FALSE;
6195         break;
6196       case MachinePlaysWhite:
6197       case IcsPlayingBlack:
6198         if(WhiteOnMove(currentMove)) return FALSE;
6199         break;
6200       case EditGame:
6201         break;
6202       default:
6203         return FALSE;
6204     }
6205     cl.pieceIn = EmptySquare;
6206     cl.rfIn = *y;
6207     cl.ffIn = *x;
6208     cl.rtIn = -1;
6209     cl.ftIn = -1;
6210     cl.promoCharIn = NULLCHAR;
6211     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6212     if( cl.kind == NormalMove ||
6213         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6214         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6215         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6216       fromX = cl.ff;
6217       fromY = cl.rf;
6218       *x = cl.ft;
6219       *y = cl.rt;
6220       return TRUE;
6221     }
6222     if(cl.kind != ImpossibleMove) return FALSE;
6223     cl.pieceIn = EmptySquare;
6224     cl.rfIn = -1;
6225     cl.ffIn = -1;
6226     cl.rtIn = *y;
6227     cl.ftIn = *x;
6228     cl.promoCharIn = NULLCHAR;
6229     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6230     if( cl.kind == NormalMove ||
6231         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6232         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6233         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6234       fromX = cl.ff;
6235       fromY = cl.rf;
6236       *x = cl.ft;
6237       *y = cl.rt;
6238       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6239       return TRUE;
6240     }
6241     return FALSE;
6242 }
6243
6244 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6245 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6246 int lastLoadGameUseList = FALSE;
6247 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6248 ChessMove lastLoadGameStart = EndOfFile;
6249
6250 void
6251 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6252      int fromX, fromY, toX, toY;
6253      int promoChar;
6254 {
6255     ChessMove moveType;
6256     ChessSquare pdown, pup;
6257
6258     /* Check if the user is playing in turn.  This is complicated because we
6259        let the user "pick up" a piece before it is his turn.  So the piece he
6260        tried to pick up may have been captured by the time he puts it down!
6261        Therefore we use the color the user is supposed to be playing in this
6262        test, not the color of the piece that is currently on the starting
6263        square---except in EditGame mode, where the user is playing both
6264        sides; fortunately there the capture race can't happen.  (It can
6265        now happen in IcsExamining mode, but that's just too bad.  The user
6266        will get a somewhat confusing message in that case.)
6267        */
6268
6269     switch (gameMode) {
6270       case PlayFromGameFile:
6271       case AnalyzeFile:
6272       case TwoMachinesPlay:
6273       case EndOfGame:
6274       case IcsObserving:
6275       case IcsIdle:
6276         /* We switched into a game mode where moves are not accepted,
6277            perhaps while the mouse button was down. */
6278         return;
6279
6280       case MachinePlaysWhite:
6281         /* User is moving for Black */
6282         if (WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is White's turn"));
6284             return;
6285         }
6286         break;
6287
6288       case MachinePlaysBlack:
6289         /* User is moving for White */
6290         if (!WhiteOnMove(currentMove)) {
6291             DisplayMoveError(_("It is Black's turn"));
6292             return;
6293         }
6294         break;
6295
6296       case EditGame:
6297       case IcsExamining:
6298       case BeginningOfGame:
6299       case AnalyzeMode:
6300       case Training:
6301         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6302         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6303             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6304             /* User is moving for Black */
6305             if (WhiteOnMove(currentMove)) {
6306                 DisplayMoveError(_("It is White's turn"));
6307                 return;
6308             }
6309         } else {
6310             /* User is moving for White */
6311             if (!WhiteOnMove(currentMove)) {
6312                 DisplayMoveError(_("It is Black's turn"));
6313                 return;
6314             }
6315         }
6316         break;
6317
6318       case IcsPlayingBlack:
6319         /* User is moving for Black */
6320         if (WhiteOnMove(currentMove)) {
6321             if (!appData.premove) {
6322                 DisplayMoveError(_("It is White's turn"));
6323             } else if (toX >= 0 && toY >= 0) {
6324                 premoveToX = toX;
6325                 premoveToY = toY;
6326                 premoveFromX = fromX;
6327                 premoveFromY = fromY;
6328                 premovePromoChar = promoChar;
6329                 gotPremove = 1;
6330                 if (appData.debugMode)
6331                     fprintf(debugFP, "Got premove: fromX %d,"
6332                             "fromY %d, toX %d, toY %d\n",
6333                             fromX, fromY, toX, toY);
6334             }
6335             return;
6336         }
6337         break;
6338
6339       case IcsPlayingWhite:
6340         /* User is moving for White */
6341         if (!WhiteOnMove(currentMove)) {
6342             if (!appData.premove) {
6343                 DisplayMoveError(_("It is Black's turn"));
6344             } else if (toX >= 0 && toY >= 0) {
6345                 premoveToX = toX;
6346                 premoveToY = toY;
6347                 premoveFromX = fromX;
6348                 premoveFromY = fromY;
6349                 premovePromoChar = promoChar;
6350                 gotPremove = 1;
6351                 if (appData.debugMode)
6352                     fprintf(debugFP, "Got premove: fromX %d,"
6353                             "fromY %d, toX %d, toY %d\n",
6354                             fromX, fromY, toX, toY);
6355             }
6356             return;
6357         }
6358         break;
6359
6360       default:
6361         break;
6362
6363       case EditPosition:
6364         /* EditPosition, empty square, or different color piece;
6365            click-click move is possible */
6366         if (toX == -2 || toY == -2) {
6367             boards[0][fromY][fromX] = EmptySquare;
6368             DrawPosition(FALSE, boards[currentMove]);
6369             return;
6370         } else if (toX >= 0 && toY >= 0) {
6371             boards[0][toY][toX] = boards[0][fromY][fromX];
6372             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6373                 if(boards[0][fromY][0] != EmptySquare) {
6374                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6375                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6376                 }
6377             } else
6378             if(fromX == BOARD_RGHT+1) {
6379                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6380                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6381                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6382                 }
6383             } else
6384             boards[0][fromY][fromX] = EmptySquare;
6385             DrawPosition(FALSE, boards[currentMove]);
6386             return;
6387         }
6388         return;
6389     }
6390
6391     if(toX < 0 || toY < 0) return;
6392     pdown = boards[currentMove][fromY][fromX];
6393     pup = boards[currentMove][toY][toX];
6394
6395     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6396     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6397          if( pup != EmptySquare ) return;
6398          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6399            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6400                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6401            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6402            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6403            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6404            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6405          fromY = DROP_RANK;
6406     }
6407
6408     /* [HGM] always test for legality, to get promotion info */
6409     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6410                                          fromY, fromX, toY, toX, promoChar);
6411     /* [HGM] but possibly ignore an IllegalMove result */
6412     if (appData.testLegality) {
6413         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6414             DisplayMoveError(_("Illegal move"));
6415             return;
6416         }
6417     }
6418
6419     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6420 }
6421
6422 /* Common tail of UserMoveEvent and DropMenuEvent */
6423 int
6424 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6425      ChessMove moveType;
6426      int fromX, fromY, toX, toY;
6427      /*char*/int promoChar;
6428 {
6429     char *bookHit = 0;
6430
6431     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6432         // [HGM] superchess: suppress promotions to non-available piece
6433         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6434         if(WhiteOnMove(currentMove)) {
6435             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6436         } else {
6437             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6438         }
6439     }
6440
6441     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6442        move type in caller when we know the move is a legal promotion */
6443     if(moveType == NormalMove && promoChar)
6444         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6445
6446     /* [HGM] <popupFix> The following if has been moved here from
6447        UserMoveEvent(). Because it seemed to belong here (why not allow
6448        piece drops in training games?), and because it can only be
6449        performed after it is known to what we promote. */
6450     if (gameMode == Training) {
6451       /* compare the move played on the board to the next move in the
6452        * game. If they match, display the move and the opponent's response.
6453        * If they don't match, display an error message.
6454        */
6455       int saveAnimate;
6456       Board testBoard;
6457       CopyBoard(testBoard, boards[currentMove]);
6458       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6459
6460       if (CompareBoards(testBoard, boards[currentMove+1])) {
6461         ForwardInner(currentMove+1);
6462
6463         /* Autoplay the opponent's response.
6464          * if appData.animate was TRUE when Training mode was entered,
6465          * the response will be animated.
6466          */
6467         saveAnimate = appData.animate;
6468         appData.animate = animateTraining;
6469         ForwardInner(currentMove+1);
6470         appData.animate = saveAnimate;
6471
6472         /* check for the end of the game */
6473         if (currentMove >= forwardMostMove) {
6474           gameMode = PlayFromGameFile;
6475           ModeHighlight();
6476           SetTrainingModeOff();
6477           DisplayInformation(_("End of game"));
6478         }
6479       } else {
6480         DisplayError(_("Incorrect move"), 0);
6481       }
6482       return 1;
6483     }
6484
6485   /* Ok, now we know that the move is good, so we can kill
6486      the previous line in Analysis Mode */
6487   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6488                                 && currentMove < forwardMostMove) {
6489     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6490     else forwardMostMove = currentMove;
6491   }
6492
6493   /* If we need the chess program but it's dead, restart it */
6494   ResurrectChessProgram();
6495
6496   /* A user move restarts a paused game*/
6497   if (pausing)
6498     PauseEvent();
6499
6500   thinkOutput[0] = NULLCHAR;
6501
6502   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6503
6504   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6505     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6506     return 1;
6507   }
6508
6509   if (gameMode == BeginningOfGame) {
6510     if (appData.noChessProgram) {
6511       gameMode = EditGame;
6512       SetGameInfo();
6513     } else {
6514       char buf[MSG_SIZ];
6515       gameMode = MachinePlaysBlack;
6516       StartClocks();
6517       SetGameInfo();
6518       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6519       DisplayTitle(buf);
6520       if (first.sendName) {
6521         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6522         SendToProgram(buf, &first);
6523       }
6524       StartClocks();
6525     }
6526     ModeHighlight();
6527   }
6528
6529   /* Relay move to ICS or chess engine */
6530   if (appData.icsActive) {
6531     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6532         gameMode == IcsExamining) {
6533       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6534         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6535         SendToICS("draw ");
6536         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6537       }
6538       // also send plain move, in case ICS does not understand atomic claims
6539       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6540       ics_user_moved = 1;
6541     }
6542   } else {
6543     if (first.sendTime && (gameMode == BeginningOfGame ||
6544                            gameMode == MachinePlaysWhite ||
6545                            gameMode == MachinePlaysBlack)) {
6546       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6547     }
6548     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6549          // [HGM] book: if program might be playing, let it use book
6550         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6551         first.maybeThinking = TRUE;
6552     } else SendMoveToProgram(forwardMostMove-1, &first);
6553     if (currentMove == cmailOldMove + 1) {
6554       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6555     }
6556   }
6557
6558   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6559
6560   switch (gameMode) {
6561   case EditGame:
6562     if(appData.testLegality)
6563     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6564     case MT_NONE:
6565     case MT_CHECK:
6566       break;
6567     case MT_CHECKMATE:
6568     case MT_STAINMATE:
6569       if (WhiteOnMove(currentMove)) {
6570         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6571       } else {
6572         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6573       }
6574       break;
6575     case MT_STALEMATE:
6576       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6577       break;
6578     }
6579     break;
6580
6581   case MachinePlaysBlack:
6582   case MachinePlaysWhite:
6583     /* disable certain menu options while machine is thinking */
6584     SetMachineThinkingEnables();
6585     break;
6586
6587   default:
6588     break;
6589   }
6590
6591   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6592   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6593
6594   if(bookHit) { // [HGM] book: simulate book reply
6595         static char bookMove[MSG_SIZ]; // a bit generous?
6596
6597         programStats.nodes = programStats.depth = programStats.time =
6598         programStats.score = programStats.got_only_move = 0;
6599         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6600
6601         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6602         strcat(bookMove, bookHit);
6603         HandleMachineMove(bookMove, &first);
6604   }
6605   return 1;
6606 }
6607
6608 void
6609 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6610      Board board;
6611      int flags;
6612      ChessMove kind;
6613      int rf, ff, rt, ft;
6614      VOIDSTAR closure;
6615 {
6616     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6617     Markers *m = (Markers *) closure;
6618     if(rf == fromY && ff == fromX)
6619         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6620                          || kind == WhiteCapturesEnPassant
6621                          || kind == BlackCapturesEnPassant);
6622     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6623 }
6624
6625 void
6626 MarkTargetSquares(int clear)
6627 {
6628   int x, y;
6629   if(!appData.markers || !appData.highlightDragging ||
6630      !appData.testLegality || gameMode == EditPosition) return;
6631   if(clear) {
6632     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6633   } else {
6634     int capt = 0;
6635     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6636     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6637       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6638       if(capt)
6639       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6640     }
6641   }
6642   DrawPosition(TRUE, NULL);
6643 }
6644
6645 int
6646 Explode(Board board, int fromX, int fromY, int toX, int toY)
6647 {
6648     if(gameInfo.variant == VariantAtomic &&
6649        (board[toY][toX] != EmptySquare ||                     // capture?
6650         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6651                          board[fromY][fromX] == BlackPawn   )
6652       )) {
6653         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6654         return TRUE;
6655     }
6656     return FALSE;
6657 }
6658
6659 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6660
6661 int CanPromote(ChessSquare piece, int y)
6662 {
6663         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6664         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6665         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6666            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6667            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6668                                                   gameInfo.variant == VariantMakruk) return FALSE;
6669         return (piece == BlackPawn && y == 1 ||
6670                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6671                 piece == BlackLance && y == 1 ||
6672                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6673 }
6674
6675 void LeftClick(ClickType clickType, int xPix, int yPix)
6676 {
6677     int x, y;
6678     Boolean saveAnimate;
6679     static int second = 0, promotionChoice = 0, clearFlag = 0;
6680     char promoChoice = NULLCHAR;
6681     ChessSquare piece;
6682
6683     if(appData.seekGraph && appData.icsActive && loggedOn &&
6684         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6685         SeekGraphClick(clickType, xPix, yPix, 0);
6686         return;
6687     }
6688
6689     if (clickType == Press) ErrorPopDown();
6690     MarkTargetSquares(1);
6691
6692     x = EventToSquare(xPix, BOARD_WIDTH);
6693     y = EventToSquare(yPix, BOARD_HEIGHT);
6694     if (!flipView && y >= 0) {
6695         y = BOARD_HEIGHT - 1 - y;
6696     }
6697     if (flipView && x >= 0) {
6698         x = BOARD_WIDTH - 1 - x;
6699     }
6700
6701     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6702         defaultPromoChoice = promoSweep;
6703         promoSweep = EmptySquare;   // terminate sweep
6704         promoDefaultAltered = TRUE;
6705         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6706     }
6707
6708     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6709         if(clickType == Release) return; // ignore upclick of click-click destination
6710         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6711         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6712         if(gameInfo.holdingsWidth &&
6713                 (WhiteOnMove(currentMove)
6714                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6715                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6716             // click in right holdings, for determining promotion piece
6717             ChessSquare p = boards[currentMove][y][x];
6718             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6719             if(p != EmptySquare) {
6720                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6721                 fromX = fromY = -1;
6722                 return;
6723             }
6724         }
6725         DrawPosition(FALSE, boards[currentMove]);
6726         return;
6727     }
6728
6729     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6730     if(clickType == Press
6731             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6732               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6733               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6734         return;
6735
6736     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6737         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6738
6739     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6740         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6741                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6742         defaultPromoChoice = DefaultPromoChoice(side);
6743     }
6744
6745     autoQueen = appData.alwaysPromoteToQueen;
6746
6747     if (fromX == -1) {
6748       int originalY = y;
6749       gatingPiece = EmptySquare;
6750       if (clickType != Press) {
6751         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6752             DragPieceEnd(xPix, yPix); dragging = 0;
6753             DrawPosition(FALSE, NULL);
6754         }
6755         return;
6756       }
6757       fromX = x; fromY = y;
6758       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6759          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6760          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6761             /* First square */
6762             if (OKToStartUserMove(fromX, fromY)) {
6763                 second = 0;
6764                 MarkTargetSquares(0);
6765                 DragPieceBegin(xPix, yPix); dragging = 1;
6766                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6767                     promoSweep = defaultPromoChoice;
6768                     selectFlag = 0; lastX = xPix; lastY = yPix;
6769                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6770                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6771                 }
6772                 if (appData.highlightDragging) {
6773                     SetHighlights(fromX, fromY, -1, -1);
6774                 }
6775             } else fromX = fromY = -1;
6776             return;
6777         }
6778     }
6779
6780     /* fromX != -1 */
6781     if (clickType == Press && gameMode != EditPosition) {
6782         ChessSquare fromP;
6783         ChessSquare toP;
6784         int frc;
6785
6786         // ignore off-board to clicks
6787         if(y < 0 || x < 0) return;
6788
6789         /* Check if clicking again on the same color piece */
6790         fromP = boards[currentMove][fromY][fromX];
6791         toP = boards[currentMove][y][x];
6792         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6793         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6794              WhitePawn <= toP && toP <= WhiteKing &&
6795              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6796              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6797             (BlackPawn <= fromP && fromP <= BlackKing &&
6798              BlackPawn <= toP && toP <= BlackKing &&
6799              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6800              !(fromP == BlackKing && toP == BlackRook && frc))) {
6801             /* Clicked again on same color piece -- changed his mind */
6802             second = (x == fromX && y == fromY);
6803             promoDefaultAltered = FALSE;
6804            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6805             if (appData.highlightDragging) {
6806                 SetHighlights(x, y, -1, -1);
6807             } else {
6808                 ClearHighlights();
6809             }
6810             if (OKToStartUserMove(x, y)) {
6811                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6812                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6813                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6814                  gatingPiece = boards[currentMove][fromY][fromX];
6815                 else gatingPiece = EmptySquare;
6816                 fromX = x;
6817                 fromY = y; dragging = 1;
6818                 MarkTargetSquares(0);
6819                 DragPieceBegin(xPix, yPix);
6820                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6821                     promoSweep = defaultPromoChoice;
6822                     selectFlag = 0; lastX = xPix; lastY = yPix;
6823                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6824                 }
6825             }
6826            }
6827            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6828            second = FALSE; 
6829         }
6830         // ignore clicks on holdings
6831         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6832     }
6833
6834     if (clickType == Release && x == fromX && y == fromY) {
6835         DragPieceEnd(xPix, yPix); dragging = 0;
6836         if(clearFlag) {
6837             // a deferred attempt to click-click move an empty square on top of a piece
6838             boards[currentMove][y][x] = EmptySquare;
6839             ClearHighlights();
6840             DrawPosition(FALSE, boards[currentMove]);
6841             fromX = fromY = -1; clearFlag = 0;
6842             return;
6843         }
6844         if (appData.animateDragging) {
6845             /* Undo animation damage if any */
6846             DrawPosition(FALSE, NULL);
6847         }
6848         if (second) {
6849             /* Second up/down in same square; just abort move */
6850             second = 0;
6851             fromX = fromY = -1;
6852             gatingPiece = EmptySquare;
6853             ClearHighlights();
6854             gotPremove = 0;
6855             ClearPremoveHighlights();
6856         } else {
6857             /* First upclick in same square; start click-click mode */
6858             SetHighlights(x, y, -1, -1);
6859         }
6860         return;
6861     }
6862
6863     clearFlag = 0;
6864
6865     /* we now have a different from- and (possibly off-board) to-square */
6866     /* Completed move */
6867     toX = x;
6868     toY = y;
6869     saveAnimate = appData.animate;
6870     if (clickType == Press) {
6871         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6872             // must be Edit Position mode with empty-square selected
6873             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6874             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6875             return;
6876         }
6877         /* Finish clickclick move */
6878         if (appData.animate || appData.highlightLastMove) {
6879             SetHighlights(fromX, fromY, toX, toY);
6880         } else {
6881             ClearHighlights();
6882         }
6883     } else {
6884         /* Finish drag move */
6885         if (appData.highlightLastMove) {
6886             SetHighlights(fromX, fromY, toX, toY);
6887         } else {
6888             ClearHighlights();
6889         }
6890         DragPieceEnd(xPix, yPix); dragging = 0;
6891         /* Don't animate move and drag both */
6892         appData.animate = FALSE;
6893     }
6894
6895     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6896     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6897         ChessSquare piece = boards[currentMove][fromY][fromX];
6898         if(gameMode == EditPosition && piece != EmptySquare &&
6899            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6900             int n;
6901
6902             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6903                 n = PieceToNumber(piece - (int)BlackPawn);
6904                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6905                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6906                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6907             } else
6908             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6909                 n = PieceToNumber(piece);
6910                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6911                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6912                 boards[currentMove][n][BOARD_WIDTH-2]++;
6913             }
6914             boards[currentMove][fromY][fromX] = EmptySquare;
6915         }
6916         ClearHighlights();
6917         fromX = fromY = -1;
6918         DrawPosition(TRUE, boards[currentMove]);
6919         return;
6920     }
6921
6922     // off-board moves should not be highlighted
6923     if(x < 0 || y < 0) ClearHighlights();
6924
6925     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6926
6927     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6928         SetHighlights(fromX, fromY, toX, toY);
6929         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6930             // [HGM] super: promotion to captured piece selected from holdings
6931             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6932             promotionChoice = TRUE;
6933             // kludge follows to temporarily execute move on display, without promoting yet
6934             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6935             boards[currentMove][toY][toX] = p;
6936             DrawPosition(FALSE, boards[currentMove]);
6937             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6938             boards[currentMove][toY][toX] = q;
6939             DisplayMessage("Click in holdings to choose piece", "");
6940             return;
6941         }
6942         PromotionPopUp();
6943     } else {
6944         int oldMove = currentMove;
6945         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6946         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6947         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6948         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6949            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6950             DrawPosition(TRUE, boards[currentMove]);
6951         fromX = fromY = -1;
6952     }
6953     appData.animate = saveAnimate;
6954     if (appData.animate || appData.animateDragging) {
6955         /* Undo animation damage if needed */
6956         DrawPosition(FALSE, NULL);
6957     }
6958 }
6959
6960 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6961 {   // front-end-free part taken out of PieceMenuPopup
6962     int whichMenu; int xSqr, ySqr;
6963
6964     if(seekGraphUp) { // [HGM] seekgraph
6965         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6966         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6967         return -2;
6968     }
6969
6970     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6971          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6972         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6973         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6974         if(action == Press)   {
6975             originalFlip = flipView;
6976             flipView = !flipView; // temporarily flip board to see game from partners perspective
6977             DrawPosition(TRUE, partnerBoard);
6978             DisplayMessage(partnerStatus, "");
6979             partnerUp = TRUE;
6980         } else if(action == Release) {
6981             flipView = originalFlip;
6982             DrawPosition(TRUE, boards[currentMove]);
6983             partnerUp = FALSE;
6984         }
6985         return -2;
6986     }
6987
6988     xSqr = EventToSquare(x, BOARD_WIDTH);
6989     ySqr = EventToSquare(y, BOARD_HEIGHT);
6990     if (action == Release) {
6991         if(pieceSweep != EmptySquare) {
6992             EditPositionMenuEvent(pieceSweep, toX, toY);
6993             pieceSweep = EmptySquare;
6994         } else UnLoadPV(); // [HGM] pv
6995     }
6996     if (action != Press) return -2; // return code to be ignored
6997     switch (gameMode) {
6998       case IcsExamining:
6999         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7000       case EditPosition:
7001         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7002         if (xSqr < 0 || ySqr < 0) return -1;
7003         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7004         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7005         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7006         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7007         NextPiece(0);
7008         return -2;\r
7009       case IcsObserving:
7010         if(!appData.icsEngineAnalyze) return -1;
7011       case IcsPlayingWhite:
7012       case IcsPlayingBlack:
7013         if(!appData.zippyPlay) goto noZip;
7014       case AnalyzeMode:
7015       case AnalyzeFile:
7016       case MachinePlaysWhite:
7017       case MachinePlaysBlack:
7018       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7019         if (!appData.dropMenu) {
7020           LoadPV(x, y);
7021           return 2; // flag front-end to grab mouse events
7022         }
7023         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7024            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7025       case EditGame:
7026       noZip:
7027         if (xSqr < 0 || ySqr < 0) return -1;
7028         if (!appData.dropMenu || appData.testLegality &&
7029             gameInfo.variant != VariantBughouse &&
7030             gameInfo.variant != VariantCrazyhouse) return -1;
7031         whichMenu = 1; // drop menu
7032         break;
7033       default:
7034         return -1;
7035     }
7036
7037     if (((*fromX = xSqr) < 0) ||
7038         ((*fromY = ySqr) < 0)) {
7039         *fromX = *fromY = -1;
7040         return -1;
7041     }
7042     if (flipView)
7043       *fromX = BOARD_WIDTH - 1 - *fromX;
7044     else
7045       *fromY = BOARD_HEIGHT - 1 - *fromY;
7046
7047     return whichMenu;
7048 }
7049
7050 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7051 {
7052 //    char * hint = lastHint;
7053     FrontEndProgramStats stats;
7054
7055     stats.which = cps == &first ? 0 : 1;
7056     stats.depth = cpstats->depth;
7057     stats.nodes = cpstats->nodes;
7058     stats.score = cpstats->score;
7059     stats.time = cpstats->time;
7060     stats.pv = cpstats->movelist;
7061     stats.hint = lastHint;
7062     stats.an_move_index = 0;
7063     stats.an_move_count = 0;
7064
7065     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7066         stats.hint = cpstats->move_name;
7067         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7068         stats.an_move_count = cpstats->nr_moves;
7069     }
7070
7071     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
7072
7073     SetProgramStats( &stats );
7074 }
7075
7076 #define MAXPLAYERS 500
7077
7078 char *
7079 TourneyStandings(int display)
7080 {
7081     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7082     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7083     char result, *p, *names[MAXPLAYERS];
7084
7085     names[0] = p = strdup(appData.participants);
7086     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7087
7088     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7089
7090     while(result = appData.results[nr]) {
7091         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7092         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7093         wScore = bScore = 0;
7094         switch(result) {
7095           case '+': wScore = 2; break;
7096           case '-': bScore = 2; break;
7097           case '=': wScore = bScore = 1; break;
7098           case ' ':
7099           case '*': return strdup("busy"); // tourney not finished
7100         }
7101         score[w] += wScore;
7102         score[b] += bScore;
7103         games[w]++;
7104         games[b]++;
7105         nr++;
7106     }
7107     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7108     for(w=0; w<nPlayers; w++) {
7109         bScore = -1;
7110         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7111         ranking[w] = b; points[w] = bScore; score[b] = -2;
7112     }
7113     p = malloc(nPlayers*34+1);
7114     for(w=0; w<nPlayers && w<display; w++)
7115         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7116     free(names[0]);
7117     return p;
7118 }
7119
7120 void
7121 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7122 {       // count all piece types
7123         int p, f, r;
7124         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7125         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7126         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7127                 p = board[r][f];
7128                 pCnt[p]++;
7129                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7130                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7131                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7132                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7133                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7134                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7135         }
7136 }
7137
7138 int
7139 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7140 {
7141         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7142         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7143
7144         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7145         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7146         if(myPawns == 2 && nMine == 3) // KPP
7147             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7148         if(myPawns == 1 && nMine == 2) // KP
7149             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7150         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7151             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7152         if(myPawns) return FALSE;
7153         if(pCnt[WhiteRook+side])
7154             return pCnt[BlackRook-side] ||
7155                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7156                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7157                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7158         if(pCnt[WhiteCannon+side]) {
7159             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7160             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7161         }
7162         if(pCnt[WhiteKnight+side])
7163             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7164         return FALSE;
7165 }
7166
7167 int
7168 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7169 {
7170         VariantClass v = gameInfo.variant;
7171
7172         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7173         if(v == VariantShatranj) return TRUE; // always winnable through baring
7174         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7175         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7176
7177         if(v == VariantXiangqi) {
7178                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7179
7180                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7181                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7182                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7183                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7184                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7185                 if(stale) // we have at least one last-rank P plus perhaps C
7186                     return majors // KPKX
7187                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7188                 else // KCA*E*
7189                     return pCnt[WhiteFerz+side] // KCAK
7190                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7191                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7192                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7193
7194         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7195                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7196
7197                 if(nMine == 1) return FALSE; // bare King
7198                 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
7199                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7200                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7201                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7202                 if(pCnt[WhiteKnight+side])
7203                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7204                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7205                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7206                 if(nBishops)
7207                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7208                 if(pCnt[WhiteAlfil+side])
7209                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7210                 if(pCnt[WhiteWazir+side])
7211                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7212         }
7213
7214         return TRUE;
7215 }
7216
7217 int
7218 Adjudicate(ChessProgramState *cps)
7219 {       // [HGM] some adjudications useful with buggy engines
7220         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7221         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7222         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7223         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7224         int k, count = 0; static int bare = 1;
7225         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7226         Boolean canAdjudicate = !appData.icsActive;
7227
7228         // most tests only when we understand the game, i.e. legality-checking on
7229             if( appData.testLegality )
7230             {   /* [HGM] Some more adjudications for obstinate engines */
7231                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7232                 static int moveCount = 6;
7233                 ChessMove result;
7234                 char *reason = NULL;
7235
7236                 /* Count what is on board. */
7237                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7238
7239                 /* Some material-based adjudications that have to be made before stalemate test */
7240                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7241                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7242                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7243                      if(canAdjudicate && appData.checkMates) {
7244                          if(engineOpponent)
7245                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7246                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7247                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7248                          return 1;
7249                      }
7250                 }
7251
7252                 /* Bare King in Shatranj (loses) or Losers (wins) */
7253                 if( nrW == 1 || nrB == 1) {
7254                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7255                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7256                      if(canAdjudicate && appData.checkMates) {
7257                          if(engineOpponent)
7258                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7259                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7260                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7261                          return 1;
7262                      }
7263                   } else
7264                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7265                   {    /* bare King */
7266                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7267                         if(canAdjudicate && appData.checkMates) {
7268                             /* but only adjudicate if adjudication enabled */
7269                             if(engineOpponent)
7270                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7271                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7272                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7273                             return 1;
7274                         }
7275                   }
7276                 } else bare = 1;
7277
7278
7279             // don't wait for engine to announce game end if we can judge ourselves
7280             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7281               case MT_CHECK:
7282                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7283                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7284                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7285                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7286                             checkCnt++;
7287                         if(checkCnt >= 2) {
7288                             reason = "Xboard adjudication: 3rd check";
7289                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7290                             break;
7291                         }
7292                     }
7293                 }
7294               case MT_NONE:
7295               default:
7296                 break;
7297               case MT_STALEMATE:
7298               case MT_STAINMATE:
7299                 reason = "Xboard adjudication: Stalemate";
7300                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7301                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7302                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7303                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7304                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7305                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7306                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7307                                                                         EP_CHECKMATE : EP_WINS);
7308                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7309                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7310                 }
7311                 break;
7312               case MT_CHECKMATE:
7313                 reason = "Xboard adjudication: Checkmate";
7314                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7315                 break;
7316             }
7317
7318                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7319                     case EP_STALEMATE:
7320                         result = GameIsDrawn; break;
7321                     case EP_CHECKMATE:
7322                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7323                     case EP_WINS:
7324                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7325                     default:
7326                         result = EndOfFile;
7327                 }
7328                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7329                     if(engineOpponent)
7330                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7331                     GameEnds( result, reason, GE_XBOARD );
7332                     return 1;
7333                 }
7334
7335                 /* Next absolutely insufficient mating material. */
7336                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7337                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7338                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7339
7340                      /* always flag draws, for judging claims */
7341                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7342
7343                      if(canAdjudicate && appData.materialDraws) {
7344                          /* but only adjudicate them if adjudication enabled */
7345                          if(engineOpponent) {
7346                            SendToProgram("force\n", engineOpponent); // suppress reply
7347                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7348                          }
7349                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7350                          return 1;
7351                      }
7352                 }
7353
7354                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7355                 if(gameInfo.variant == VariantXiangqi ?
7356                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7357                  : nrW + nrB == 4 &&
7358                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7359                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7360                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7361                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7362                    ) ) {
7363                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7364                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7365                           if(engineOpponent) {
7366                             SendToProgram("force\n", engineOpponent); // suppress reply
7367                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7368                           }
7369                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7370                           return 1;
7371                      }
7372                 } else moveCount = 6;
7373             }
7374         if (appData.debugMode) { int i;
7375             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7376                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7377                     appData.drawRepeats);
7378             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7379               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7380
7381         }
7382
7383         // Repetition draws and 50-move rule can be applied independently of legality testing
7384
7385                 /* Check for rep-draws */
7386                 count = 0;
7387                 for(k = forwardMostMove-2;
7388                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7389                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7390                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7391                     k-=2)
7392                 {   int rights=0;
7393                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7394                         /* compare castling rights */
7395                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7396                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7397                                 rights++; /* King lost rights, while rook still had them */
7398                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7399                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7400                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7401                                    rights++; /* but at least one rook lost them */
7402                         }
7403                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7404                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7405                                 rights++;
7406                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7407                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7408                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7409                                    rights++;
7410                         }
7411                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7412                             && appData.drawRepeats > 1) {
7413                              /* adjudicate after user-specified nr of repeats */
7414                              int result = GameIsDrawn;
7415                              char *details = "XBoard adjudication: repetition draw";
7416                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7417                                 // [HGM] xiangqi: check for forbidden perpetuals
7418                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7419                                 for(m=forwardMostMove; m>k; m-=2) {
7420                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7421                                         ourPerpetual = 0; // the current mover did not always check
7422                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7423                                         hisPerpetual = 0; // the opponent did not always check
7424                                 }
7425                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7426                                                                         ourPerpetual, hisPerpetual);
7427                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7428                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7429                                     details = "Xboard adjudication: perpetual checking";
7430                                 } else
7431                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7432                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7433                                 } else
7434                                 // Now check for perpetual chases
7435                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7436                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7437                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7438                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7439                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7440                                         details = "Xboard adjudication: perpetual chasing";
7441                                     } else
7442                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7443                                         break; // Abort repetition-checking loop.
7444                                 }
7445                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7446                              }
7447                              if(engineOpponent) {
7448                                SendToProgram("force\n", engineOpponent); // suppress reply
7449                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7450                              }
7451                              GameEnds( result, details, GE_XBOARD );
7452                              return 1;
7453                         }
7454                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7455                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7456                     }
7457                 }
7458
7459                 /* Now we test for 50-move draws. Determine ply count */
7460                 count = forwardMostMove;
7461                 /* look for last irreversble move */
7462                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7463                     count--;
7464                 /* if we hit starting position, add initial plies */
7465                 if( count == backwardMostMove )
7466                     count -= initialRulePlies;
7467                 count = forwardMostMove - count;
7468                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7469                         // adjust reversible move counter for checks in Xiangqi
7470                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7471                         if(i < backwardMostMove) i = backwardMostMove;
7472                         while(i <= forwardMostMove) {
7473                                 lastCheck = inCheck; // check evasion does not count
7474                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7475                                 if(inCheck || lastCheck) count--; // check does not count
7476                                 i++;
7477                         }
7478                 }
7479                 if( count >= 100)
7480                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7481                          /* this is used to judge if draw claims are legal */
7482                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7483                          if(engineOpponent) {
7484                            SendToProgram("force\n", engineOpponent); // suppress reply
7485                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7486                          }
7487                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7488                          return 1;
7489                 }
7490
7491                 /* if draw offer is pending, treat it as a draw claim
7492                  * when draw condition present, to allow engines a way to
7493                  * claim draws before making their move to avoid a race
7494                  * condition occurring after their move
7495                  */
7496                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7497                          char *p = NULL;
7498                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7499                              p = "Draw claim: 50-move rule";
7500                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7501                              p = "Draw claim: 3-fold repetition";
7502                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7503                              p = "Draw claim: insufficient mating material";
7504                          if( p != NULL && canAdjudicate) {
7505                              if(engineOpponent) {
7506                                SendToProgram("force\n", engineOpponent); // suppress reply
7507                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7508                              }
7509                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7510                              return 1;
7511                          }
7512                 }
7513
7514                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7515                     if(engineOpponent) {
7516                       SendToProgram("force\n", engineOpponent); // suppress reply
7517                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7518                     }
7519                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7520                     return 1;
7521                 }
7522         return 0;
7523 }
7524
7525 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7526 {   // [HGM] book: this routine intercepts moves to simulate book replies
7527     char *bookHit = NULL;
7528
7529     //first determine if the incoming move brings opponent into his book
7530     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7531         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7532     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7533     if(bookHit != NULL && !cps->bookSuspend) {
7534         // make sure opponent is not going to reply after receiving move to book position
7535         SendToProgram("force\n", cps);
7536         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7537     }
7538     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7539     // now arrange restart after book miss
7540     if(bookHit) {
7541         // after a book hit we never send 'go', and the code after the call to this routine
7542         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7543         char buf[MSG_SIZ];
7544         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7545         SendToProgram(buf, cps);
7546         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7547     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7548         SendToProgram("go\n", cps);
7549         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7550     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7551         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7552             SendToProgram("go\n", cps);
7553         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7554     }
7555     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7556 }
7557
7558 char *savedMessage;
7559 ChessProgramState *savedState;
7560 void DeferredBookMove(void)
7561 {
7562         if(savedState->lastPing != savedState->lastPong)
7563                     ScheduleDelayedEvent(DeferredBookMove, 10);
7564         else
7565         HandleMachineMove(savedMessage, savedState);
7566 }
7567
7568 void
7569 HandleMachineMove(message, cps)
7570      char *message;
7571      ChessProgramState *cps;
7572 {
7573     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7574     char realname[MSG_SIZ];
7575     int fromX, fromY, toX, toY;
7576     ChessMove moveType;
7577     char promoChar;
7578     char *p;
7579     int machineWhite;
7580     char *bookHit;
7581
7582     cps->userError = 0;
7583
7584 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7585     /*
7586      * Kludge to ignore BEL characters
7587      */
7588     while (*message == '\007') message++;
7589
7590     /*
7591      * [HGM] engine debug message: ignore lines starting with '#' character
7592      */
7593     if(cps->debug && *message == '#') return;
7594
7595     /*
7596      * Look for book output
7597      */
7598     if (cps == &first && bookRequested) {
7599         if (message[0] == '\t' || message[0] == ' ') {
7600             /* Part of the book output is here; append it */
7601             strcat(bookOutput, message);
7602             strcat(bookOutput, "  \n");
7603             return;
7604         } else if (bookOutput[0] != NULLCHAR) {
7605             /* All of book output has arrived; display it */
7606             char *p = bookOutput;
7607             while (*p != NULLCHAR) {
7608                 if (*p == '\t') *p = ' ';
7609                 p++;
7610             }
7611             DisplayInformation(bookOutput);
7612             bookRequested = FALSE;
7613             /* Fall through to parse the current output */
7614         }
7615     }
7616
7617     /*
7618      * Look for machine move.
7619      */
7620     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7621         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7622     {
7623         /* This method is only useful on engines that support ping */
7624         if (cps->lastPing != cps->lastPong) {
7625           if (gameMode == BeginningOfGame) {
7626             /* Extra move from before last new; ignore */
7627             if (appData.debugMode) {
7628                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7629             }
7630           } else {
7631             if (appData.debugMode) {
7632                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7633                         cps->which, gameMode);
7634             }
7635
7636             SendToProgram("undo\n", cps);
7637           }
7638           return;
7639         }
7640
7641         switch (gameMode) {
7642           case BeginningOfGame:
7643             /* Extra move from before last reset; ignore */
7644             if (appData.debugMode) {
7645                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7646             }
7647             return;
7648
7649           case EndOfGame:
7650           case IcsIdle:
7651           default:
7652             /* Extra move after we tried to stop.  The mode test is
7653                not a reliable way of detecting this problem, but it's
7654                the best we can do on engines that don't support ping.
7655             */
7656             if (appData.debugMode) {
7657                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7658                         cps->which, gameMode);
7659             }
7660             SendToProgram("undo\n", cps);
7661             return;
7662
7663           case MachinePlaysWhite:
7664           case IcsPlayingWhite:
7665             machineWhite = TRUE;
7666             break;
7667
7668           case MachinePlaysBlack:
7669           case IcsPlayingBlack:
7670             machineWhite = FALSE;
7671             break;
7672
7673           case TwoMachinesPlay:
7674             machineWhite = (cps->twoMachinesColor[0] == 'w');
7675             break;
7676         }
7677         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7678             if (appData.debugMode) {
7679                 fprintf(debugFP,
7680                         "Ignoring move out of turn by %s, gameMode %d"
7681                         ", forwardMost %d\n",
7682                         cps->which, gameMode, forwardMostMove);
7683             }
7684             return;
7685         }
7686
7687     if (appData.debugMode) { int f = forwardMostMove;
7688         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7689                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7690                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7691     }
7692         if(cps->alphaRank) AlphaRank(machineMove, 4);
7693         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7694                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7695             /* Machine move could not be parsed; ignore it. */
7696           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7697                     machineMove, _(cps->which));
7698             DisplayError(buf1, 0);
7699             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7700                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7701             if (gameMode == TwoMachinesPlay) {
7702               GameEnds(machineWhite ? BlackWins : WhiteWins,
7703                        buf1, GE_XBOARD);
7704             }
7705             return;
7706         }
7707
7708         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7709         /* So we have to redo legality test with true e.p. status here,  */
7710         /* to make sure an illegal e.p. capture does not slip through,   */
7711         /* to cause a forfeit on a justified illegal-move complaint      */
7712         /* of the opponent.                                              */
7713         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7714            ChessMove moveType;
7715            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7716                              fromY, fromX, toY, toX, promoChar);
7717             if (appData.debugMode) {
7718                 int i;
7719                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7720                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7721                 fprintf(debugFP, "castling rights\n");
7722             }
7723             if(moveType == IllegalMove) {
7724               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7725                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7726                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7727                            buf1, GE_XBOARD);
7728                 return;
7729            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7730            /* [HGM] Kludge to handle engines that send FRC-style castling
7731               when they shouldn't (like TSCP-Gothic) */
7732            switch(moveType) {
7733              case WhiteASideCastleFR:
7734              case BlackASideCastleFR:
7735                toX+=2;
7736                currentMoveString[2]++;
7737                break;
7738              case WhiteHSideCastleFR:
7739              case BlackHSideCastleFR:
7740                toX--;
7741                currentMoveString[2]--;
7742                break;
7743              default: ; // nothing to do, but suppresses warning of pedantic compilers
7744            }
7745         }
7746         hintRequested = FALSE;
7747         lastHint[0] = NULLCHAR;
7748         bookRequested = FALSE;
7749         /* Program may be pondering now */
7750         cps->maybeThinking = TRUE;
7751         if (cps->sendTime == 2) cps->sendTime = 1;
7752         if (cps->offeredDraw) cps->offeredDraw--;
7753
7754         /* [AS] Save move info*/
7755         pvInfoList[ forwardMostMove ].score = programStats.score;
7756         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7757         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7758
7759         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7760
7761         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7762         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7763             int count = 0;
7764
7765             while( count < adjudicateLossPlies ) {
7766                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7767
7768                 if( count & 1 ) {
7769                     score = -score; /* Flip score for winning side */
7770                 }
7771
7772                 if( score > adjudicateLossThreshold ) {
7773                     break;
7774                 }
7775
7776                 count++;
7777             }
7778
7779             if( count >= adjudicateLossPlies ) {
7780                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7781
7782                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7783                     "Xboard adjudication",
7784                     GE_XBOARD );
7785
7786                 return;
7787             }
7788         }
7789
7790         if(Adjudicate(cps)) {
7791             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7792             return; // [HGM] adjudicate: for all automatic game ends
7793         }
7794
7795 #if ZIPPY
7796         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7797             first.initDone) {
7798           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7799                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7800                 SendToICS("draw ");
7801                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7802           }
7803           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7804           ics_user_moved = 1;
7805           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7806                 char buf[3*MSG_SIZ];
7807
7808                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7809                         programStats.score / 100.,
7810                         programStats.depth,
7811                         programStats.time / 100.,
7812                         (unsigned int)programStats.nodes,
7813                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7814                         programStats.movelist);
7815                 SendToICS(buf);
7816 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7817           }
7818         }
7819 #endif
7820
7821         /* [AS] Clear stats for next move */
7822         ClearProgramStats();
7823         thinkOutput[0] = NULLCHAR;
7824         hiddenThinkOutputState = 0;
7825
7826         bookHit = NULL;
7827         if (gameMode == TwoMachinesPlay) {
7828             /* [HGM] relaying draw offers moved to after reception of move */
7829             /* and interpreting offer as claim if it brings draw condition */
7830             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7831                 SendToProgram("draw\n", cps->other);
7832             }
7833             if (cps->other->sendTime) {
7834                 SendTimeRemaining(cps->other,
7835                                   cps->other->twoMachinesColor[0] == 'w');
7836             }
7837             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7838             if (firstMove && !bookHit) {
7839                 firstMove = FALSE;
7840                 if (cps->other->useColors) {
7841                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7842                 }
7843                 SendToProgram("go\n", cps->other);
7844             }
7845             cps->other->maybeThinking = TRUE;
7846         }
7847
7848         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7849
7850         if (!pausing && appData.ringBellAfterMoves) {
7851             RingBell();
7852         }
7853
7854         /*
7855          * Reenable menu items that were disabled while
7856          * machine was thinking
7857          */
7858         if (gameMode != TwoMachinesPlay)
7859             SetUserThinkingEnables();
7860
7861         // [HGM] book: after book hit opponent has received move and is now in force mode
7862         // force the book reply into it, and then fake that it outputted this move by jumping
7863         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7864         if(bookHit) {
7865                 static char bookMove[MSG_SIZ]; // a bit generous?
7866
7867                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7868                 strcat(bookMove, bookHit);
7869                 message = bookMove;
7870                 cps = cps->other;
7871                 programStats.nodes = programStats.depth = programStats.time =
7872                 programStats.score = programStats.got_only_move = 0;
7873                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7874
7875                 if(cps->lastPing != cps->lastPong) {
7876                     savedMessage = message; // args for deferred call
7877                     savedState = cps;
7878                     ScheduleDelayedEvent(DeferredBookMove, 10);
7879                     return;
7880                 }
7881                 goto FakeBookMove;
7882         }
7883
7884         return;
7885     }
7886
7887     /* Set special modes for chess engines.  Later something general
7888      *  could be added here; for now there is just one kludge feature,
7889      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7890      *  when "xboard" is given as an interactive command.
7891      */
7892     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7893         cps->useSigint = FALSE;
7894         cps->useSigterm = FALSE;
7895     }
7896     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7897       ParseFeatures(message+8, cps);
7898       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7899     }
7900
7901     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7902       int dummy, s=6; char buf[MSG_SIZ];
7903       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7904       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7905       ParseFEN(boards[0], &dummy, message+s);
7906       DrawPosition(TRUE, boards[0]);
7907       startedFromSetupPosition = TRUE;
7908       return;
7909     }
7910     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7911      * want this, I was asked to put it in, and obliged.
7912      */
7913     if (!strncmp(message, "setboard ", 9)) {
7914         Board initial_position;
7915
7916         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7917
7918         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7919             DisplayError(_("Bad FEN received from engine"), 0);
7920             return ;
7921         } else {
7922            Reset(TRUE, FALSE);
7923            CopyBoard(boards[0], initial_position);
7924            initialRulePlies = FENrulePlies;
7925            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7926            else gameMode = MachinePlaysBlack;
7927            DrawPosition(FALSE, boards[currentMove]);
7928         }
7929         return;
7930     }
7931
7932     /*
7933      * Look for communication commands
7934      */
7935     if (!strncmp(message, "telluser ", 9)) {
7936         if(message[9] == '\\' && message[10] == '\\')
7937             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7938         DisplayNote(message + 9);
7939         return;
7940     }
7941     if (!strncmp(message, "tellusererror ", 14)) {
7942         cps->userError = 1;
7943         if(message[14] == '\\' && message[15] == '\\')
7944             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7945         DisplayError(message + 14, 0);
7946         return;
7947     }
7948     if (!strncmp(message, "tellopponent ", 13)) {
7949       if (appData.icsActive) {
7950         if (loggedOn) {
7951           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7952           SendToICS(buf1);
7953         }
7954       } else {
7955         DisplayNote(message + 13);
7956       }
7957       return;
7958     }
7959     if (!strncmp(message, "tellothers ", 11)) {
7960       if (appData.icsActive) {
7961         if (loggedOn) {
7962           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7963           SendToICS(buf1);
7964         }
7965       }
7966       return;
7967     }
7968     if (!strncmp(message, "tellall ", 8)) {
7969       if (appData.icsActive) {
7970         if (loggedOn) {
7971           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7972           SendToICS(buf1);
7973         }
7974       } else {
7975         DisplayNote(message + 8);
7976       }
7977       return;
7978     }
7979     if (strncmp(message, "warning", 7) == 0) {
7980         /* Undocumented feature, use tellusererror in new code */
7981         DisplayError(message, 0);
7982         return;
7983     }
7984     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7985         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7986         strcat(realname, " query");
7987         AskQuestion(realname, buf2, buf1, cps->pr);
7988         return;
7989     }
7990     /* Commands from the engine directly to ICS.  We don't allow these to be
7991      *  sent until we are logged on. Crafty kibitzes have been known to
7992      *  interfere with the login process.
7993      */
7994     if (loggedOn) {
7995         if (!strncmp(message, "tellics ", 8)) {
7996             SendToICS(message + 8);
7997             SendToICS("\n");
7998             return;
7999         }
8000         if (!strncmp(message, "tellicsnoalias ", 15)) {
8001             SendToICS(ics_prefix);
8002             SendToICS(message + 15);
8003             SendToICS("\n");
8004             return;
8005         }
8006         /* The following are for backward compatibility only */
8007         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8008             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8009             SendToICS(ics_prefix);
8010             SendToICS(message);
8011             SendToICS("\n");
8012             return;
8013         }
8014     }
8015     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8016         return;
8017     }
8018     /*
8019      * If the move is illegal, cancel it and redraw the board.
8020      * Also deal with other error cases.  Matching is rather loose
8021      * here to accommodate engines written before the spec.
8022      */
8023     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8024         strncmp(message, "Error", 5) == 0) {
8025         if (StrStr(message, "name") ||
8026             StrStr(message, "rating") || StrStr(message, "?") ||
8027             StrStr(message, "result") || StrStr(message, "board") ||
8028             StrStr(message, "bk") || StrStr(message, "computer") ||
8029             StrStr(message, "variant") || StrStr(message, "hint") ||
8030             StrStr(message, "random") || StrStr(message, "depth") ||
8031             StrStr(message, "accepted")) {
8032             return;
8033         }
8034         if (StrStr(message, "protover")) {
8035           /* Program is responding to input, so it's apparently done
8036              initializing, and this error message indicates it is
8037              protocol version 1.  So we don't need to wait any longer
8038              for it to initialize and send feature commands. */
8039           FeatureDone(cps, 1);
8040           cps->protocolVersion = 1;
8041           return;
8042         }
8043         cps->maybeThinking = FALSE;
8044
8045         if (StrStr(message, "draw")) {
8046             /* Program doesn't have "draw" command */
8047             cps->sendDrawOffers = 0;
8048             return;
8049         }
8050         if (cps->sendTime != 1 &&
8051             (StrStr(message, "time") || StrStr(message, "otim"))) {
8052           /* Program apparently doesn't have "time" or "otim" command */
8053           cps->sendTime = 0;
8054           return;
8055         }
8056         if (StrStr(message, "analyze")) {
8057             cps->analysisSupport = FALSE;
8058             cps->analyzing = FALSE;
8059             Reset(FALSE, TRUE);
8060             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8061             DisplayError(buf2, 0);
8062             return;
8063         }
8064         if (StrStr(message, "(no matching move)st")) {
8065           /* Special kludge for GNU Chess 4 only */
8066           cps->stKludge = TRUE;
8067           SendTimeControl(cps, movesPerSession, timeControl,
8068                           timeIncrement, appData.searchDepth,
8069                           searchTime);
8070           return;
8071         }
8072         if (StrStr(message, "(no matching move)sd")) {
8073           /* Special kludge for GNU Chess 4 only */
8074           cps->sdKludge = TRUE;
8075           SendTimeControl(cps, movesPerSession, timeControl,
8076                           timeIncrement, appData.searchDepth,
8077                           searchTime);
8078           return;
8079         }
8080         if (!StrStr(message, "llegal")) {
8081             return;
8082         }
8083         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8084             gameMode == IcsIdle) return;
8085         if (forwardMostMove <= backwardMostMove) return;
8086         if (pausing) PauseEvent();
8087       if(appData.forceIllegal) {
8088             // [HGM] illegal: machine refused move; force position after move into it
8089           SendToProgram("force\n", cps);
8090           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8091                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8092                 // when black is to move, while there might be nothing on a2 or black
8093                 // might already have the move. So send the board as if white has the move.
8094                 // But first we must change the stm of the engine, as it refused the last move
8095                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8096                 if(WhiteOnMove(forwardMostMove)) {
8097                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8098                     SendBoard(cps, forwardMostMove); // kludgeless board
8099                 } else {
8100                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8101                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8102                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8103                 }
8104           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8105             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8106                  gameMode == TwoMachinesPlay)
8107               SendToProgram("go\n", cps);
8108             return;
8109       } else
8110         if (gameMode == PlayFromGameFile) {
8111             /* Stop reading this game file */
8112             gameMode = EditGame;
8113             ModeHighlight();
8114         }
8115         /* [HGM] illegal-move claim should forfeit game when Xboard */
8116         /* only passes fully legal moves                            */
8117         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8118             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8119                                 "False illegal-move claim", GE_XBOARD );
8120             return; // do not take back move we tested as valid
8121         }
8122         currentMove = forwardMostMove-1;
8123         DisplayMove(currentMove-1); /* before DisplayMoveError */
8124         SwitchClocks(forwardMostMove-1); // [HGM] race
8125         DisplayBothClocks();
8126         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8127                 parseList[currentMove], _(cps->which));
8128         DisplayMoveError(buf1);
8129         DrawPosition(FALSE, boards[currentMove]);
8130         return;
8131     }
8132     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8133         /* Program has a broken "time" command that
8134            outputs a string not ending in newline.
8135            Don't use it. */
8136         cps->sendTime = 0;
8137     }
8138
8139     /*
8140      * If chess program startup fails, exit with an error message.
8141      * Attempts to recover here are futile.
8142      */
8143     if ((StrStr(message, "unknown host") != NULL)
8144         || (StrStr(message, "No remote directory") != NULL)
8145         || (StrStr(message, "not found") != NULL)
8146         || (StrStr(message, "No such file") != NULL)
8147         || (StrStr(message, "can't alloc") != NULL)
8148         || (StrStr(message, "Permission denied") != NULL)) {
8149
8150         cps->maybeThinking = FALSE;
8151         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8152                 _(cps->which), cps->program, cps->host, message);
8153         RemoveInputSource(cps->isr);
8154         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8155             if(cps == &first) appData.noChessProgram = TRUE;
8156             DisplayError(buf1, 0);
8157         }
8158         return;
8159     }
8160
8161     /*
8162      * Look for hint output
8163      */
8164     if (sscanf(message, "Hint: %s", buf1) == 1) {
8165         if (cps == &first && hintRequested) {
8166             hintRequested = FALSE;
8167             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8168                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8169                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8170                                     PosFlags(forwardMostMove),
8171                                     fromY, fromX, toY, toX, promoChar, buf1);
8172                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8173                 DisplayInformation(buf2);
8174             } else {
8175                 /* Hint move could not be parsed!? */
8176               snprintf(buf2, sizeof(buf2),
8177                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8178                         buf1, _(cps->which));
8179                 DisplayError(buf2, 0);
8180             }
8181         } else {
8182           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8183         }
8184         return;
8185     }
8186
8187     /*
8188      * Ignore other messages if game is not in progress
8189      */
8190     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8191         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8192
8193     /*
8194      * look for win, lose, draw, or draw offer
8195      */
8196     if (strncmp(message, "1-0", 3) == 0) {
8197         char *p, *q, *r = "";
8198         p = strchr(message, '{');
8199         if (p) {
8200             q = strchr(p, '}');
8201             if (q) {
8202                 *q = NULLCHAR;
8203                 r = p + 1;
8204             }
8205         }
8206         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8207         return;
8208     } else if (strncmp(message, "0-1", 3) == 0) {
8209         char *p, *q, *r = "";
8210         p = strchr(message, '{');
8211         if (p) {
8212             q = strchr(p, '}');
8213             if (q) {
8214                 *q = NULLCHAR;
8215                 r = p + 1;
8216             }
8217         }
8218         /* Kludge for Arasan 4.1 bug */
8219         if (strcmp(r, "Black resigns") == 0) {
8220             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8221             return;
8222         }
8223         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8224         return;
8225     } else if (strncmp(message, "1/2", 3) == 0) {
8226         char *p, *q, *r = "";
8227         p = strchr(message, '{');
8228         if (p) {
8229             q = strchr(p, '}');
8230             if (q) {
8231                 *q = NULLCHAR;
8232                 r = p + 1;
8233             }
8234         }
8235
8236         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8237         return;
8238
8239     } else if (strncmp(message, "White resign", 12) == 0) {
8240         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8241         return;
8242     } else if (strncmp(message, "Black resign", 12) == 0) {
8243         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8244         return;
8245     } else if (strncmp(message, "White matches", 13) == 0 ||
8246                strncmp(message, "Black matches", 13) == 0   ) {
8247         /* [HGM] ignore GNUShogi noises */
8248         return;
8249     } else if (strncmp(message, "White", 5) == 0 &&
8250                message[5] != '(' &&
8251                StrStr(message, "Black") == NULL) {
8252         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8253         return;
8254     } else if (strncmp(message, "Black", 5) == 0 &&
8255                message[5] != '(') {
8256         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8257         return;
8258     } else if (strcmp(message, "resign") == 0 ||
8259                strcmp(message, "computer resigns") == 0) {
8260         switch (gameMode) {
8261           case MachinePlaysBlack:
8262           case IcsPlayingBlack:
8263             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8264             break;
8265           case MachinePlaysWhite:
8266           case IcsPlayingWhite:
8267             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8268             break;
8269           case TwoMachinesPlay:
8270             if (cps->twoMachinesColor[0] == 'w')
8271               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8272             else
8273               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8274             break;
8275           default:
8276             /* can't happen */
8277             break;
8278         }
8279         return;
8280     } else if (strncmp(message, "opponent mates", 14) == 0) {
8281         switch (gameMode) {
8282           case MachinePlaysBlack:
8283           case IcsPlayingBlack:
8284             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8285             break;
8286           case MachinePlaysWhite:
8287           case IcsPlayingWhite:
8288             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8289             break;
8290           case TwoMachinesPlay:
8291             if (cps->twoMachinesColor[0] == 'w')
8292               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8293             else
8294               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8295             break;
8296           default:
8297             /* can't happen */
8298             break;
8299         }
8300         return;
8301     } else if (strncmp(message, "computer mates", 14) == 0) {
8302         switch (gameMode) {
8303           case MachinePlaysBlack:
8304           case IcsPlayingBlack:
8305             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8306             break;
8307           case MachinePlaysWhite:
8308           case IcsPlayingWhite:
8309             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8310             break;
8311           case TwoMachinesPlay:
8312             if (cps->twoMachinesColor[0] == 'w')
8313               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8314             else
8315               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8316             break;
8317           default:
8318             /* can't happen */
8319             break;
8320         }
8321         return;
8322     } else if (strncmp(message, "checkmate", 9) == 0) {
8323         if (WhiteOnMove(forwardMostMove)) {
8324             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8325         } else {
8326             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8327         }
8328         return;
8329     } else if (strstr(message, "Draw") != NULL ||
8330                strstr(message, "game is a draw") != NULL) {
8331         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8332         return;
8333     } else if (strstr(message, "offer") != NULL &&
8334                strstr(message, "draw") != NULL) {
8335 #if ZIPPY
8336         if (appData.zippyPlay && first.initDone) {
8337             /* Relay offer to ICS */
8338             SendToICS(ics_prefix);
8339             SendToICS("draw\n");
8340         }
8341 #endif
8342         cps->offeredDraw = 2; /* valid until this engine moves twice */
8343         if (gameMode == TwoMachinesPlay) {
8344             if (cps->other->offeredDraw) {
8345                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8346             /* [HGM] in two-machine mode we delay relaying draw offer      */
8347             /* until after we also have move, to see if it is really claim */
8348             }
8349         } else if (gameMode == MachinePlaysWhite ||
8350                    gameMode == MachinePlaysBlack) {
8351           if (userOfferedDraw) {
8352             DisplayInformation(_("Machine accepts your draw offer"));
8353             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8354           } else {
8355             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8356           }
8357         }
8358     }
8359
8360
8361     /*
8362      * Look for thinking output
8363      */
8364     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8365           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8366                                 ) {
8367         int plylev, mvleft, mvtot, curscore, time;
8368         char mvname[MOVE_LEN];
8369         u64 nodes; // [DM]
8370         char plyext;
8371         int ignore = FALSE;
8372         int prefixHint = FALSE;
8373         mvname[0] = NULLCHAR;
8374
8375         switch (gameMode) {
8376           case MachinePlaysBlack:
8377           case IcsPlayingBlack:
8378             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8379             break;
8380           case MachinePlaysWhite:
8381           case IcsPlayingWhite:
8382             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8383             break;
8384           case AnalyzeMode:
8385           case AnalyzeFile:
8386             break;
8387           case IcsObserving: /* [DM] icsEngineAnalyze */
8388             if (!appData.icsEngineAnalyze) ignore = TRUE;
8389             break;
8390           case TwoMachinesPlay:
8391             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8392                 ignore = TRUE;
8393             }
8394             break;
8395           default:
8396             ignore = TRUE;
8397             break;
8398         }
8399
8400         if (!ignore) {
8401             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8402             buf1[0] = NULLCHAR;
8403             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8404                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8405
8406                 if (plyext != ' ' && plyext != '\t') {
8407                     time *= 100;
8408                 }
8409
8410                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8411                 if( cps->scoreIsAbsolute &&
8412                     ( gameMode == MachinePlaysBlack ||
8413                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8414                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8415                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8416                      !WhiteOnMove(currentMove)
8417                     ) )
8418                 {
8419                     curscore = -curscore;
8420                 }
8421
8422
8423                 tempStats.depth = plylev;
8424                 tempStats.nodes = nodes;
8425                 tempStats.time = time;
8426                 tempStats.score = curscore;
8427                 tempStats.got_only_move = 0;
8428
8429                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8430                         int ticklen;
8431
8432                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8433                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8434                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8435                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8436                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8437                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8438                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8439                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8440                 }
8441
8442                 /* Buffer overflow protection */
8443                 if (buf1[0] != NULLCHAR) {
8444                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8445                         && appData.debugMode) {
8446                         fprintf(debugFP,
8447                                 "PV is too long; using the first %u bytes.\n",
8448                                 (unsigned) sizeof(tempStats.movelist) - 1);
8449                     }
8450
8451                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8452                 } else {
8453                     sprintf(tempStats.movelist, " no PV\n");
8454                 }
8455
8456                 if (tempStats.seen_stat) {
8457                     tempStats.ok_to_send = 1;
8458                 }
8459
8460                 if (strchr(tempStats.movelist, '(') != NULL) {
8461                     tempStats.line_is_book = 1;
8462                     tempStats.nr_moves = 0;
8463                     tempStats.moves_left = 0;
8464                 } else {
8465                     tempStats.line_is_book = 0;
8466                 }
8467
8468                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8469                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8470
8471                 SendProgramStatsToFrontend( cps, &tempStats );
8472
8473                 /*
8474                     [AS] Protect the thinkOutput buffer from overflow... this
8475                     is only useful if buf1 hasn't overflowed first!
8476                 */
8477                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8478                          plylev,
8479                          (gameMode == TwoMachinesPlay ?
8480                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8481                          ((double) curscore) / 100.0,
8482                          prefixHint ? lastHint : "",
8483                          prefixHint ? " " : "" );
8484
8485                 if( buf1[0] != NULLCHAR ) {
8486                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8487
8488                     if( strlen(buf1) > max_len ) {
8489                         if( appData.debugMode) {
8490                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8491                         }
8492                         buf1[max_len+1] = '\0';
8493                     }
8494
8495                     strcat( thinkOutput, buf1 );
8496                 }
8497
8498                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8499                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8500                     DisplayMove(currentMove - 1);
8501                 }
8502                 return;
8503
8504             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8505                 /* crafty (9.25+) says "(only move) <move>"
8506                  * if there is only 1 legal move
8507                  */
8508                 sscanf(p, "(only move) %s", buf1);
8509                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8510                 sprintf(programStats.movelist, "%s (only move)", buf1);
8511                 programStats.depth = 1;
8512                 programStats.nr_moves = 1;
8513                 programStats.moves_left = 1;
8514                 programStats.nodes = 1;
8515                 programStats.time = 1;
8516                 programStats.got_only_move = 1;
8517
8518                 /* Not really, but we also use this member to
8519                    mean "line isn't going to change" (Crafty
8520                    isn't searching, so stats won't change) */
8521                 programStats.line_is_book = 1;
8522
8523                 SendProgramStatsToFrontend( cps, &programStats );
8524
8525                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8526                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8527                     DisplayMove(currentMove - 1);
8528                 }
8529                 return;
8530             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8531                               &time, &nodes, &plylev, &mvleft,
8532                               &mvtot, mvname) >= 5) {
8533                 /* The stat01: line is from Crafty (9.29+) in response
8534                    to the "." command */
8535                 programStats.seen_stat = 1;
8536                 cps->maybeThinking = TRUE;
8537
8538                 if (programStats.got_only_move || !appData.periodicUpdates)
8539                   return;
8540
8541                 programStats.depth = plylev;
8542                 programStats.time = time;
8543                 programStats.nodes = nodes;
8544                 programStats.moves_left = mvleft;
8545                 programStats.nr_moves = mvtot;
8546                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8547                 programStats.ok_to_send = 1;
8548                 programStats.movelist[0] = '\0';
8549
8550                 SendProgramStatsToFrontend( cps, &programStats );
8551
8552                 return;
8553
8554             } else if (strncmp(message,"++",2) == 0) {
8555                 /* Crafty 9.29+ outputs this */
8556                 programStats.got_fail = 2;
8557                 return;
8558
8559             } else if (strncmp(message,"--",2) == 0) {
8560                 /* Crafty 9.29+ outputs this */
8561                 programStats.got_fail = 1;
8562                 return;
8563
8564             } else if (thinkOutput[0] != NULLCHAR &&
8565                        strncmp(message, "    ", 4) == 0) {
8566                 unsigned message_len;
8567
8568                 p = message;
8569                 while (*p && *p == ' ') p++;
8570
8571                 message_len = strlen( p );
8572
8573                 /* [AS] Avoid buffer overflow */
8574                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8575                     strcat(thinkOutput, " ");
8576                     strcat(thinkOutput, p);
8577                 }
8578
8579                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8580                     strcat(programStats.movelist, " ");
8581                     strcat(programStats.movelist, p);
8582                 }
8583
8584                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8585                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8586                     DisplayMove(currentMove - 1);
8587                 }
8588                 return;
8589             }
8590         }
8591         else {
8592             buf1[0] = NULLCHAR;
8593
8594             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8595                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8596             {
8597                 ChessProgramStats cpstats;
8598
8599                 if (plyext != ' ' && plyext != '\t') {
8600                     time *= 100;
8601                 }
8602
8603                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8604                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8605                     curscore = -curscore;
8606                 }
8607
8608                 cpstats.depth = plylev;
8609                 cpstats.nodes = nodes;
8610                 cpstats.time = time;
8611                 cpstats.score = curscore;
8612                 cpstats.got_only_move = 0;
8613                 cpstats.movelist[0] = '\0';
8614
8615                 if (buf1[0] != NULLCHAR) {
8616                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8617                 }
8618
8619                 cpstats.ok_to_send = 0;
8620                 cpstats.line_is_book = 0;
8621                 cpstats.nr_moves = 0;
8622                 cpstats.moves_left = 0;
8623
8624                 SendProgramStatsToFrontend( cps, &cpstats );
8625             }
8626         }
8627     }
8628 }
8629
8630
8631 /* Parse a game score from the character string "game", and
8632    record it as the history of the current game.  The game
8633    score is NOT assumed to start from the standard position.
8634    The display is not updated in any way.
8635    */
8636 void
8637 ParseGameHistory(game)
8638      char *game;
8639 {
8640     ChessMove moveType;
8641     int fromX, fromY, toX, toY, boardIndex;
8642     char promoChar;
8643     char *p, *q;
8644     char buf[MSG_SIZ];
8645
8646     if (appData.debugMode)
8647       fprintf(debugFP, "Parsing game history: %s\n", game);
8648
8649     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8650     gameInfo.site = StrSave(appData.icsHost);
8651     gameInfo.date = PGNDate();
8652     gameInfo.round = StrSave("-");
8653
8654     /* Parse out names of players */
8655     while (*game == ' ') game++;
8656     p = buf;
8657     while (*game != ' ') *p++ = *game++;
8658     *p = NULLCHAR;
8659     gameInfo.white = StrSave(buf);
8660     while (*game == ' ') game++;
8661     p = buf;
8662     while (*game != ' ' && *game != '\n') *p++ = *game++;
8663     *p = NULLCHAR;
8664     gameInfo.black = StrSave(buf);
8665
8666     /* Parse moves */
8667     boardIndex = blackPlaysFirst ? 1 : 0;
8668     yynewstr(game);
8669     for (;;) {
8670         yyboardindex = boardIndex;
8671         moveType = (ChessMove) Myylex();
8672         switch (moveType) {
8673           case IllegalMove:             /* maybe suicide chess, etc. */
8674   if (appData.debugMode) {
8675     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8676     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8677     setbuf(debugFP, NULL);
8678   }
8679           case WhitePromotion:
8680           case BlackPromotion:
8681           case WhiteNonPromotion:
8682           case BlackNonPromotion:
8683           case NormalMove:
8684           case WhiteCapturesEnPassant:
8685           case BlackCapturesEnPassant:
8686           case WhiteKingSideCastle:
8687           case WhiteQueenSideCastle:
8688           case BlackKingSideCastle:
8689           case BlackQueenSideCastle:
8690           case WhiteKingSideCastleWild:
8691           case WhiteQueenSideCastleWild:
8692           case BlackKingSideCastleWild:
8693           case BlackQueenSideCastleWild:
8694           /* PUSH Fabien */
8695           case WhiteHSideCastleFR:
8696           case WhiteASideCastleFR:
8697           case BlackHSideCastleFR:
8698           case BlackASideCastleFR:
8699           /* POP Fabien */
8700             fromX = currentMoveString[0] - AAA;
8701             fromY = currentMoveString[1] - ONE;
8702             toX = currentMoveString[2] - AAA;
8703             toY = currentMoveString[3] - ONE;
8704             promoChar = currentMoveString[4];
8705             break;
8706           case WhiteDrop:
8707           case BlackDrop:
8708             fromX = moveType == WhiteDrop ?
8709               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8710             (int) CharToPiece(ToLower(currentMoveString[0]));
8711             fromY = DROP_RANK;
8712             toX = currentMoveString[2] - AAA;
8713             toY = currentMoveString[3] - ONE;
8714             promoChar = NULLCHAR;
8715             break;
8716           case AmbiguousMove:
8717             /* bug? */
8718             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8719   if (appData.debugMode) {
8720     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8721     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8722     setbuf(debugFP, NULL);
8723   }
8724             DisplayError(buf, 0);
8725             return;
8726           case ImpossibleMove:
8727             /* bug? */
8728             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8729   if (appData.debugMode) {
8730     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8731     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8732     setbuf(debugFP, NULL);
8733   }
8734             DisplayError(buf, 0);
8735             return;
8736           case EndOfFile:
8737             if (boardIndex < backwardMostMove) {
8738                 /* Oops, gap.  How did that happen? */
8739                 DisplayError(_("Gap in move list"), 0);
8740                 return;
8741             }
8742             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8743             if (boardIndex > forwardMostMove) {
8744                 forwardMostMove = boardIndex;
8745             }
8746             return;
8747           case ElapsedTime:
8748             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8749                 strcat(parseList[boardIndex-1], " ");
8750                 strcat(parseList[boardIndex-1], yy_text);
8751             }
8752             continue;
8753           case Comment:
8754           case PGNTag:
8755           case NAG:
8756           default:
8757             /* ignore */
8758             continue;
8759           case WhiteWins:
8760           case BlackWins:
8761           case GameIsDrawn:
8762           case GameUnfinished:
8763             if (gameMode == IcsExamining) {
8764                 if (boardIndex < backwardMostMove) {
8765                     /* Oops, gap.  How did that happen? */
8766                     return;
8767                 }
8768                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8769                 return;
8770             }
8771             gameInfo.result = moveType;
8772             p = strchr(yy_text, '{');
8773             if (p == NULL) p = strchr(yy_text, '(');
8774             if (p == NULL) {
8775                 p = yy_text;
8776                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8777             } else {
8778                 q = strchr(p, *p == '{' ? '}' : ')');
8779                 if (q != NULL) *q = NULLCHAR;
8780                 p++;
8781             }
8782             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8783             gameInfo.resultDetails = StrSave(p);
8784             continue;
8785         }
8786         if (boardIndex >= forwardMostMove &&
8787             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8788             backwardMostMove = blackPlaysFirst ? 1 : 0;
8789             return;
8790         }
8791         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8792                                  fromY, fromX, toY, toX, promoChar,
8793                                  parseList[boardIndex]);
8794         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8795         /* currentMoveString is set as a side-effect of yylex */
8796         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8797         strcat(moveList[boardIndex], "\n");
8798         boardIndex++;
8799         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8800         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8801           case MT_NONE:
8802           case MT_STALEMATE:
8803           default:
8804             break;
8805           case MT_CHECK:
8806             if(gameInfo.variant != VariantShogi)
8807                 strcat(parseList[boardIndex - 1], "+");
8808             break;
8809           case MT_CHECKMATE:
8810           case MT_STAINMATE:
8811             strcat(parseList[boardIndex - 1], "#");
8812             break;
8813         }
8814     }
8815 }
8816
8817
8818 /* Apply a move to the given board  */
8819 void
8820 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8821      int fromX, fromY, toX, toY;
8822      int promoChar;
8823      Board board;
8824 {
8825   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8826   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8827
8828     /* [HGM] compute & store e.p. status and castling rights for new position */
8829     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8830
8831       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8832       oldEP = (signed char)board[EP_STATUS];
8833       board[EP_STATUS] = EP_NONE;
8834
8835       if( board[toY][toX] != EmptySquare )
8836            board[EP_STATUS] = EP_CAPTURE;
8837
8838   if (fromY == DROP_RANK) {
8839         /* must be first */
8840         piece = board[toY][toX] = (ChessSquare) fromX;
8841   } else {
8842       int i;
8843
8844       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8845            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8846                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8847       } else
8848       if( board[fromY][fromX] == WhitePawn ) {
8849            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8850                board[EP_STATUS] = EP_PAWN_MOVE;
8851            if( toY-fromY==2) {
8852                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8853                         gameInfo.variant != VariantBerolina || toX < fromX)
8854                       board[EP_STATUS] = toX | berolina;
8855                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8856                         gameInfo.variant != VariantBerolina || toX > fromX)
8857                       board[EP_STATUS] = toX;
8858            }
8859       } else
8860       if( board[fromY][fromX] == BlackPawn ) {
8861            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8862                board[EP_STATUS] = EP_PAWN_MOVE;
8863            if( toY-fromY== -2) {
8864                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8865                         gameInfo.variant != VariantBerolina || toX < fromX)
8866                       board[EP_STATUS] = toX | berolina;
8867                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8868                         gameInfo.variant != VariantBerolina || toX > fromX)
8869                       board[EP_STATUS] = toX;
8870            }
8871        }
8872
8873        for(i=0; i<nrCastlingRights; i++) {
8874            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8875               board[CASTLING][i] == toX   && castlingRank[i] == toY
8876              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8877        }
8878
8879      if (fromX == toX && fromY == toY) return;
8880
8881      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8882      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8883      if(gameInfo.variant == VariantKnightmate)
8884          king += (int) WhiteUnicorn - (int) WhiteKing;
8885
8886     /* Code added by Tord: */
8887     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8888     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8889         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8890       board[fromY][fromX] = EmptySquare;
8891       board[toY][toX] = EmptySquare;
8892       if((toX > fromX) != (piece == WhiteRook)) {
8893         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8894       } else {
8895         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8896       }
8897     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8898                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8899       board[fromY][fromX] = EmptySquare;
8900       board[toY][toX] = EmptySquare;
8901       if((toX > fromX) != (piece == BlackRook)) {
8902         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8903       } else {
8904         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8905       }
8906     /* End of code added by Tord */
8907
8908     } else if (board[fromY][fromX] == king
8909         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8910         && toY == fromY && toX > fromX+1) {
8911         board[fromY][fromX] = EmptySquare;
8912         board[toY][toX] = king;
8913         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8914         board[fromY][BOARD_RGHT-1] = EmptySquare;
8915     } else if (board[fromY][fromX] == king
8916         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8917                && toY == fromY && toX < fromX-1) {
8918         board[fromY][fromX] = EmptySquare;
8919         board[toY][toX] = king;
8920         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8921         board[fromY][BOARD_LEFT] = EmptySquare;
8922     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8923                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8924                && toY >= BOARD_HEIGHT-promoRank
8925                ) {
8926         /* white pawn promotion */
8927         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8928         if (board[toY][toX] == EmptySquare) {
8929             board[toY][toX] = WhiteQueen;
8930         }
8931         if(gameInfo.variant==VariantBughouse ||
8932            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8933             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8934         board[fromY][fromX] = EmptySquare;
8935     } else if ((fromY == BOARD_HEIGHT-4)
8936                && (toX != fromX)
8937                && gameInfo.variant != VariantXiangqi
8938                && gameInfo.variant != VariantBerolina
8939                && (board[fromY][fromX] == WhitePawn)
8940                && (board[toY][toX] == EmptySquare)) {
8941         board[fromY][fromX] = EmptySquare;
8942         board[toY][toX] = WhitePawn;
8943         captured = board[toY - 1][toX];
8944         board[toY - 1][toX] = EmptySquare;
8945     } else if ((fromY == BOARD_HEIGHT-4)
8946                && (toX == fromX)
8947                && gameInfo.variant == VariantBerolina
8948                && (board[fromY][fromX] == WhitePawn)
8949                && (board[toY][toX] == EmptySquare)) {
8950         board[fromY][fromX] = EmptySquare;
8951         board[toY][toX] = WhitePawn;
8952         if(oldEP & EP_BEROLIN_A) {
8953                 captured = board[fromY][fromX-1];
8954                 board[fromY][fromX-1] = EmptySquare;
8955         }else{  captured = board[fromY][fromX+1];
8956                 board[fromY][fromX+1] = EmptySquare;
8957         }
8958     } else if (board[fromY][fromX] == king
8959         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8960                && toY == fromY && toX > fromX+1) {
8961         board[fromY][fromX] = EmptySquare;
8962         board[toY][toX] = king;
8963         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8964         board[fromY][BOARD_RGHT-1] = EmptySquare;
8965     } else if (board[fromY][fromX] == king
8966         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8967                && toY == fromY && toX < fromX-1) {
8968         board[fromY][fromX] = EmptySquare;
8969         board[toY][toX] = king;
8970         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8971         board[fromY][BOARD_LEFT] = EmptySquare;
8972     } else if (fromY == 7 && fromX == 3
8973                && board[fromY][fromX] == BlackKing
8974                && toY == 7 && toX == 5) {
8975         board[fromY][fromX] = EmptySquare;
8976         board[toY][toX] = BlackKing;
8977         board[fromY][7] = EmptySquare;
8978         board[toY][4] = BlackRook;
8979     } else if (fromY == 7 && fromX == 3
8980                && board[fromY][fromX] == BlackKing
8981                && toY == 7 && toX == 1) {
8982         board[fromY][fromX] = EmptySquare;
8983         board[toY][toX] = BlackKing;
8984         board[fromY][0] = EmptySquare;
8985         board[toY][2] = BlackRook;
8986     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8987                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8988                && toY < promoRank
8989                ) {
8990         /* black pawn promotion */
8991         board[toY][toX] = CharToPiece(ToLower(promoChar));
8992         if (board[toY][toX] == EmptySquare) {
8993             board[toY][toX] = BlackQueen;
8994         }
8995         if(gameInfo.variant==VariantBughouse ||
8996            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8997             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8998         board[fromY][fromX] = EmptySquare;
8999     } else if ((fromY == 3)
9000                && (toX != fromX)
9001                && gameInfo.variant != VariantXiangqi
9002                && gameInfo.variant != VariantBerolina
9003                && (board[fromY][fromX] == BlackPawn)
9004                && (board[toY][toX] == EmptySquare)) {
9005         board[fromY][fromX] = EmptySquare;
9006         board[toY][toX] = BlackPawn;
9007         captured = board[toY + 1][toX];
9008         board[toY + 1][toX] = EmptySquare;
9009     } else if ((fromY == 3)
9010                && (toX == fromX)
9011                && gameInfo.variant == VariantBerolina
9012                && (board[fromY][fromX] == BlackPawn)
9013                && (board[toY][toX] == EmptySquare)) {
9014         board[fromY][fromX] = EmptySquare;
9015         board[toY][toX] = BlackPawn;
9016         if(oldEP & EP_BEROLIN_A) {
9017                 captured = board[fromY][fromX-1];
9018                 board[fromY][fromX-1] = EmptySquare;
9019         }else{  captured = board[fromY][fromX+1];
9020                 board[fromY][fromX+1] = EmptySquare;
9021         }
9022     } else {
9023         board[toY][toX] = board[fromY][fromX];
9024         board[fromY][fromX] = EmptySquare;
9025     }
9026   }
9027
9028     if (gameInfo.holdingsWidth != 0) {
9029
9030       /* !!A lot more code needs to be written to support holdings  */
9031       /* [HGM] OK, so I have written it. Holdings are stored in the */
9032       /* penultimate board files, so they are automaticlly stored   */
9033       /* in the game history.                                       */
9034       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9035                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9036         /* Delete from holdings, by decreasing count */
9037         /* and erasing image if necessary            */
9038         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9039         if(p < (int) BlackPawn) { /* white drop */
9040              p -= (int)WhitePawn;
9041                  p = PieceToNumber((ChessSquare)p);
9042              if(p >= gameInfo.holdingsSize) p = 0;
9043              if(--board[p][BOARD_WIDTH-2] <= 0)
9044                   board[p][BOARD_WIDTH-1] = EmptySquare;
9045              if((int)board[p][BOARD_WIDTH-2] < 0)
9046                         board[p][BOARD_WIDTH-2] = 0;
9047         } else {                  /* black drop */
9048              p -= (int)BlackPawn;
9049                  p = PieceToNumber((ChessSquare)p);
9050              if(p >= gameInfo.holdingsSize) p = 0;
9051              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9052                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9053              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9054                         board[BOARD_HEIGHT-1-p][1] = 0;
9055         }
9056       }
9057       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9058           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9059         /* [HGM] holdings: Add to holdings, if holdings exist */
9060         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9061                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9062                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9063         }
9064         p = (int) captured;
9065         if (p >= (int) BlackPawn) {
9066           p -= (int)BlackPawn;
9067           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9068                   /* in Shogi restore piece to its original  first */
9069                   captured = (ChessSquare) (DEMOTED captured);
9070                   p = DEMOTED p;
9071           }
9072           p = PieceToNumber((ChessSquare)p);
9073           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9074           board[p][BOARD_WIDTH-2]++;
9075           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9076         } else {
9077           p -= (int)WhitePawn;
9078           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9079                   captured = (ChessSquare) (DEMOTED captured);
9080                   p = DEMOTED p;
9081           }
9082           p = PieceToNumber((ChessSquare)p);
9083           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9084           board[BOARD_HEIGHT-1-p][1]++;
9085           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9086         }
9087       }
9088     } else if (gameInfo.variant == VariantAtomic) {
9089       if (captured != EmptySquare) {
9090         int y, x;
9091         for (y = toY-1; y <= toY+1; y++) {
9092           for (x = toX-1; x <= toX+1; x++) {
9093             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9094                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9095               board[y][x] = EmptySquare;
9096             }
9097           }
9098         }
9099         board[toY][toX] = EmptySquare;
9100       }
9101     }
9102     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9103         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9104     } else
9105     if(promoChar == '+') {
9106         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9107         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9108     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9109         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9110     }
9111     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9112                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9113         // [HGM] superchess: take promotion piece out of holdings
9114         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9115         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9116             if(!--board[k][BOARD_WIDTH-2])
9117                 board[k][BOARD_WIDTH-1] = EmptySquare;
9118         } else {
9119             if(!--board[BOARD_HEIGHT-1-k][1])
9120                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9121         }
9122     }
9123
9124 }
9125
9126 /* Updates forwardMostMove */
9127 void
9128 MakeMove(fromX, fromY, toX, toY, promoChar)
9129      int fromX, fromY, toX, toY;
9130      int promoChar;
9131 {
9132 //    forwardMostMove++; // [HGM] bare: moved downstream
9133
9134     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9135         int timeLeft; static int lastLoadFlag=0; int king, piece;
9136         piece = boards[forwardMostMove][fromY][fromX];
9137         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9138         if(gameInfo.variant == VariantKnightmate)
9139             king += (int) WhiteUnicorn - (int) WhiteKing;
9140         if(forwardMostMove == 0) {
9141             if(blackPlaysFirst)
9142                 fprintf(serverMoves, "%s;", second.tidy);
9143             fprintf(serverMoves, "%s;", first.tidy);
9144             if(!blackPlaysFirst)
9145                 fprintf(serverMoves, "%s;", second.tidy);
9146         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9147         lastLoadFlag = loadFlag;
9148         // print base move
9149         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9150         // print castling suffix
9151         if( toY == fromY && piece == king ) {
9152             if(toX-fromX > 1)
9153                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9154             if(fromX-toX >1)
9155                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9156         }
9157         // e.p. suffix
9158         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9159              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9160              boards[forwardMostMove][toY][toX] == EmptySquare
9161              && fromX != toX && fromY != toY)
9162                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9163         // promotion suffix
9164         if(promoChar != NULLCHAR)
9165                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9166         if(!loadFlag) {
9167             fprintf(serverMoves, "/%d/%d",
9168                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9169             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9170             else                      timeLeft = blackTimeRemaining/1000;
9171             fprintf(serverMoves, "/%d", timeLeft);
9172         }
9173         fflush(serverMoves);
9174     }
9175
9176     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9177       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9178                         0, 1);
9179       return;
9180     }
9181     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9182     if (commentList[forwardMostMove+1] != NULL) {
9183         free(commentList[forwardMostMove+1]);
9184         commentList[forwardMostMove+1] = NULL;
9185     }
9186     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9187     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9188     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9189     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9190     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9191     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9192     gameInfo.result = GameUnfinished;
9193     if (gameInfo.resultDetails != NULL) {
9194         free(gameInfo.resultDetails);
9195         gameInfo.resultDetails = NULL;
9196     }
9197     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9198                               moveList[forwardMostMove - 1]);
9199     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9200                              PosFlags(forwardMostMove - 1),
9201                              fromY, fromX, toY, toX, promoChar,
9202                              parseList[forwardMostMove - 1]);
9203     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9204       case MT_NONE:
9205       case MT_STALEMATE:
9206       default:
9207         break;
9208       case MT_CHECK:
9209         if(gameInfo.variant != VariantShogi)
9210             strcat(parseList[forwardMostMove - 1], "+");
9211         break;
9212       case MT_CHECKMATE:
9213       case MT_STAINMATE:
9214         strcat(parseList[forwardMostMove - 1], "#");
9215         break;
9216     }
9217     if (appData.debugMode) {
9218         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9219     }
9220
9221 }
9222
9223 /* Updates currentMove if not pausing */
9224 void
9225 ShowMove(fromX, fromY, toX, toY)
9226 {
9227     int instant = (gameMode == PlayFromGameFile) ?
9228         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9229     if(appData.noGUI) return;
9230     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9231         if (!instant) {
9232             if (forwardMostMove == currentMove + 1) {
9233                 AnimateMove(boards[forwardMostMove - 1],
9234                             fromX, fromY, toX, toY);
9235             }
9236             if (appData.highlightLastMove) {
9237                 SetHighlights(fromX, fromY, toX, toY);
9238             }
9239         }
9240         currentMove = forwardMostMove;
9241     }
9242
9243     if (instant) return;
9244
9245     DisplayMove(currentMove - 1);
9246     DrawPosition(FALSE, boards[currentMove]);
9247     DisplayBothClocks();
9248     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9249 }
9250
9251 void SendEgtPath(ChessProgramState *cps)
9252 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9253         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9254
9255         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9256
9257         while(*p) {
9258             char c, *q = name+1, *r, *s;
9259
9260             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9261             while(*p && *p != ',') *q++ = *p++;
9262             *q++ = ':'; *q = 0;
9263             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9264                 strcmp(name, ",nalimov:") == 0 ) {
9265                 // take nalimov path from the menu-changeable option first, if it is defined
9266               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9267                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9268             } else
9269             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9270                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9271                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9272                 s = r = StrStr(s, ":") + 1; // beginning of path info
9273                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9274                 c = *r; *r = 0;             // temporarily null-terminate path info
9275                     *--q = 0;               // strip of trailig ':' from name
9276                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9277                 *r = c;
9278                 SendToProgram(buf,cps);     // send egtbpath command for this format
9279             }
9280             if(*p == ',') p++; // read away comma to position for next format name
9281         }
9282 }
9283
9284 void
9285 InitChessProgram(cps, setup)
9286      ChessProgramState *cps;
9287      int setup; /* [HGM] needed to setup FRC opening position */
9288 {
9289     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9290     if (appData.noChessProgram) return;
9291     hintRequested = FALSE;
9292     bookRequested = FALSE;
9293
9294     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9295     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9296     if(cps->memSize) { /* [HGM] memory */
9297       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9298         SendToProgram(buf, cps);
9299     }
9300     SendEgtPath(cps); /* [HGM] EGT */
9301     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9302       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9303         SendToProgram(buf, cps);
9304     }
9305
9306     SendToProgram(cps->initString, cps);
9307     if (gameInfo.variant != VariantNormal &&
9308         gameInfo.variant != VariantLoadable
9309         /* [HGM] also send variant if board size non-standard */
9310         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9311                                             ) {
9312       char *v = VariantName(gameInfo.variant);
9313       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9314         /* [HGM] in protocol 1 we have to assume all variants valid */
9315         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9316         DisplayFatalError(buf, 0, 1);
9317         return;
9318       }
9319
9320       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9321       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9322       if( gameInfo.variant == VariantXiangqi )
9323            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9324       if( gameInfo.variant == VariantShogi )
9325            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9326       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9327            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9328       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9329           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9330            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9331       if( gameInfo.variant == VariantCourier )
9332            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9333       if( gameInfo.variant == VariantSuper )
9334            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9335       if( gameInfo.variant == VariantGreat )
9336            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9337       if( gameInfo.variant == VariantSChess )
9338            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9339
9340       if(overruled) {
9341         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9342                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9343            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9344            if(StrStr(cps->variants, b) == NULL) {
9345                // specific sized variant not known, check if general sizing allowed
9346                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9347                    if(StrStr(cps->variants, "boardsize") == NULL) {
9348                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9349                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9350                        DisplayFatalError(buf, 0, 1);
9351                        return;
9352                    }
9353                    /* [HGM] here we really should compare with the maximum supported board size */
9354                }
9355            }
9356       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9357       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9358       SendToProgram(buf, cps);
9359     }
9360     currentlyInitializedVariant = gameInfo.variant;
9361
9362     /* [HGM] send opening position in FRC to first engine */
9363     if(setup) {
9364           SendToProgram("force\n", cps);
9365           SendBoard(cps, 0);
9366           /* engine is now in force mode! Set flag to wake it up after first move. */
9367           setboardSpoiledMachineBlack = 1;
9368     }
9369
9370     if (cps->sendICS) {
9371       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9372       SendToProgram(buf, cps);
9373     }
9374     cps->maybeThinking = FALSE;
9375     cps->offeredDraw = 0;
9376     if (!appData.icsActive) {
9377         SendTimeControl(cps, movesPerSession, timeControl,
9378                         timeIncrement, appData.searchDepth,
9379                         searchTime);
9380     }
9381     if (appData.showThinking
9382         // [HGM] thinking: four options require thinking output to be sent
9383         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9384                                 ) {
9385         SendToProgram("post\n", cps);
9386     }
9387     SendToProgram("hard\n", cps);
9388     if (!appData.ponderNextMove) {
9389         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9390            it without being sure what state we are in first.  "hard"
9391            is not a toggle, so that one is OK.
9392          */
9393         SendToProgram("easy\n", cps);
9394     }
9395     if (cps->usePing) {
9396       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9397       SendToProgram(buf, cps);
9398     }
9399     cps->initDone = TRUE;
9400 }
9401
9402
9403 void
9404 StartChessProgram(cps)
9405      ChessProgramState *cps;
9406 {
9407     char buf[MSG_SIZ];
9408     int err;
9409
9410     if (appData.noChessProgram) return;
9411     cps->initDone = FALSE;
9412
9413     if (strcmp(cps->host, "localhost") == 0) {
9414         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9415     } else if (*appData.remoteShell == NULLCHAR) {
9416         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9417     } else {
9418         if (*appData.remoteUser == NULLCHAR) {
9419           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9420                     cps->program);
9421         } else {
9422           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9423                     cps->host, appData.remoteUser, cps->program);
9424         }
9425         err = StartChildProcess(buf, "", &cps->pr);
9426     }
9427
9428     if (err != 0) {
9429       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9430         DisplayFatalError(buf, err, 1);
9431         cps->pr = NoProc;
9432         cps->isr = NULL;
9433         return;
9434     }
9435
9436     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9437     if (cps->protocolVersion > 1) {
9438       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9439       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9440       cps->comboCnt = 0;  //                and values of combo boxes
9441       SendToProgram(buf, cps);
9442     } else {
9443       SendToProgram("xboard\n", cps);
9444     }
9445 }
9446
9447 void
9448 TwoMachinesEventIfReady P((void))
9449 {
9450   static int curMess = 0;
9451   if (first.lastPing != first.lastPong) {
9452     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9453     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9454     return;
9455   }
9456   if (second.lastPing != second.lastPong) {
9457     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9458     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9459     return;
9460   }
9461   DisplayMessage("", ""); curMess = 0;
9462   ThawUI();
9463   TwoMachinesEvent();
9464 }
9465
9466 int
9467 CreateTourney(char *name)
9468 {
9469         FILE *f;
9470         if(name[0] == NULLCHAR) return 0;
9471         f = fopen(appData.tourneyFile, "r");
9472         if(f) { // file exists
9473             ParseArgsFromFile(f); // parse it
9474         } else {
9475             f = fopen(appData.tourneyFile, "w");
9476             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9477                 // create a file with tournament description
9478                 fprintf(f, "-participants {%s}\n", appData.participants);
9479                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9480                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9481                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9482                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9483                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9484                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9485                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9486                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9487                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9488                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9489                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9490                 if(searchTime > 0)
9491                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9492                 else {
9493                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9494                         fprintf(f, "-tc %s\n", appData.timeControl);
9495                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9496                 }
9497                 fprintf(f, "-results \"\"\n");
9498             }
9499         }
9500         fclose(f);
9501         appData.noChessProgram = FALSE;
9502         appData.clockMode = TRUE;
9503         SetGNUMode();
9504         return 1;
9505 }
9506
9507 #define MAXENGINES 1000
9508 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9509
9510 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9511 {
9512     char buf[MSG_SIZ], *p, *q;
9513     int i=1;
9514     while(*names) {
9515         p = names; q = buf;
9516         while(*p && *p != '\n') *q++ = *p++;
9517         *q = 0;
9518         if(engineList[i]) free(engineList[i]);
9519         engineList[i] = strdup(buf);
9520         if(*p == '\n') p++;
9521         TidyProgramName(engineList[i], "localhost", buf);
9522         if(engineMnemonic[i]) free(engineMnemonic[i]);
9523         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9524             strcat(buf, " (");
9525             sscanf(q + 8, "%s", buf + strlen(buf));
9526             strcat(buf, ")");
9527         }
9528         engineMnemonic[i] = strdup(buf);
9529         names = p; i++;
9530       if(i > MAXENGINES - 2) break;
9531     }
9532     engineList[i] = NULL;
9533 }
9534
9535 // following implemented as macro to avoid type limitations
9536 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9537
9538 void SwapEngines(int n)
9539 {   // swap settings for first engine and other engine (so far only some selected options)
9540     int h;
9541     char *p;
9542     if(n == 0) return;
9543     SWAP(directory, p)
9544     SWAP(chessProgram, p)
9545     SWAP(isUCI, h)
9546     SWAP(hasOwnBookUCI, h)
9547     SWAP(protocolVersion, h)
9548     SWAP(reuse, h)
9549     SWAP(scoreIsAbsolute, h)
9550     SWAP(timeOdds, h)
9551     SWAP(logo, p)
9552     SWAP(pgnName, p)
9553 }
9554
9555 void
9556 SetPlayer(int player)
9557 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9558     int i;
9559     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9560     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9561                                  "-firstNeedsNoncompliantFEN false -firstNPS -1 -fn \"\"";
9562     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9563     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9564     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9565     if(mnemonic[i]) {
9566         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9567         ParseArgsFromString(resetOptions);
9568         ParseArgsFromString(buf);
9569     }
9570     free(engineName);
9571 }
9572
9573 int
9574 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9575 {   // determine players from game number
9576     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9577
9578     if(appData.tourneyType == 0) {
9579         roundsPerCycle = (nPlayers - 1) | 1;
9580         pairingsPerRound = nPlayers / 2;
9581     } else if(appData.tourneyType > 0) {
9582         roundsPerCycle = nPlayers - appData.tourneyType;
9583         pairingsPerRound = appData.tourneyType;
9584     }
9585     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9586     gamesPerCycle = gamesPerRound * roundsPerCycle;
9587     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9588     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9589     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9590     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9591     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9592     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9593
9594     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9595     if(appData.roundSync) *syncInterval = gamesPerRound;
9596
9597     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9598
9599     if(appData.tourneyType == 0) {
9600         if(curPairing == (nPlayers-1)/2 ) {
9601             *whitePlayer = curRound;
9602             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9603         } else {
9604             *whitePlayer = curRound - pairingsPerRound + curPairing;
9605             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9606             *blackPlayer = curRound + pairingsPerRound - curPairing;
9607             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9608         }
9609     } else if(appData.tourneyType > 0) {
9610         *whitePlayer = curPairing;
9611         *blackPlayer = curRound + appData.tourneyType;
9612     }
9613
9614     // take care of white/black alternation per round. 
9615     // For cycles and games this is already taken care of by default, derived from matchGame!
9616     return curRound & 1;
9617 }
9618
9619 int
9620 NextTourneyGame(int nr, int *swapColors)
9621 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9622     char *p, *q;
9623     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9624     FILE *tf;
9625     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9626     tf = fopen(appData.tourneyFile, "r");
9627     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9628     ParseArgsFromFile(tf); fclose(tf);
9629     InitTimeControls(); // TC might be altered from tourney file
9630
9631     p = appData.participants;
9632     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9633     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9634
9635     if(syncInterval) {
9636         p = q = appData.results;
9637         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9638         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9639             DisplayMessage(_("Waiting for other game(s)"),"");
9640             waitingForGame = TRUE;
9641             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9642             return 0;
9643         }
9644         waitingForGame = FALSE;
9645     }
9646
9647     if(first.pr != NoProc) return 1; // engines already loaded
9648
9649     // redefine engines, engine dir, etc.
9650     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9651     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9652     SwapEngines(1);
9653     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9654     SwapEngines(1);         // and make that valid for second engine by swapping
9655     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9656     InitEngine(&second, 1);
9657     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9658     return 1;
9659 }
9660
9661 void
9662 NextMatchGame()
9663 {   // performs game initialization that does not invoke engines, and then tries to start the game
9664     int firstWhite, swapColors = 0;
9665     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9666     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9667     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9668     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9669     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9670     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9671     Reset(FALSE, first.pr != NoProc);
9672     appData.noChessProgram = FALSE;
9673     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9674     TwoMachinesEvent();
9675 }
9676
9677 void UserAdjudicationEvent( int result )
9678 {
9679     ChessMove gameResult = GameIsDrawn;
9680
9681     if( result > 0 ) {
9682         gameResult = WhiteWins;
9683     }
9684     else if( result < 0 ) {
9685         gameResult = BlackWins;
9686     }
9687
9688     if( gameMode == TwoMachinesPlay ) {
9689         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9690     }
9691 }
9692
9693
9694 // [HGM] save: calculate checksum of game to make games easily identifiable
9695 int StringCheckSum(char *s)
9696 {
9697         int i = 0;
9698         if(s==NULL) return 0;
9699         while(*s) i = i*259 + *s++;
9700         return i;
9701 }
9702
9703 int GameCheckSum()
9704 {
9705         int i, sum=0;
9706         for(i=backwardMostMove; i<forwardMostMove; i++) {
9707                 sum += pvInfoList[i].depth;
9708                 sum += StringCheckSum(parseList[i]);
9709                 sum += StringCheckSum(commentList[i]);
9710                 sum *= 261;
9711         }
9712         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9713         return sum + StringCheckSum(commentList[i]);
9714 } // end of save patch
9715
9716 void
9717 GameEnds(result, resultDetails, whosays)
9718      ChessMove result;
9719      char *resultDetails;
9720      int whosays;
9721 {
9722     GameMode nextGameMode;
9723     int isIcsGame;
9724     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9725
9726     if(endingGame) return; /* [HGM] crash: forbid recursion */
9727     endingGame = 1;
9728     if(twoBoards) { // [HGM] dual: switch back to one board
9729         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9730         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9731     }
9732     if (appData.debugMode) {
9733       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9734               result, resultDetails ? resultDetails : "(null)", whosays);
9735     }
9736
9737     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9738
9739     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9740         /* If we are playing on ICS, the server decides when the
9741            game is over, but the engine can offer to draw, claim
9742            a draw, or resign.
9743          */
9744 #if ZIPPY
9745         if (appData.zippyPlay && first.initDone) {
9746             if (result == GameIsDrawn) {
9747                 /* In case draw still needs to be claimed */
9748                 SendToICS(ics_prefix);
9749                 SendToICS("draw\n");
9750             } else if (StrCaseStr(resultDetails, "resign")) {
9751                 SendToICS(ics_prefix);
9752                 SendToICS("resign\n");
9753             }
9754         }
9755 #endif
9756         endingGame = 0; /* [HGM] crash */
9757         return;
9758     }
9759
9760     /* If we're loading the game from a file, stop */
9761     if (whosays == GE_FILE) {
9762       (void) StopLoadGameTimer();
9763       gameFileFP = NULL;
9764     }
9765
9766     /* Cancel draw offers */
9767     first.offeredDraw = second.offeredDraw = 0;
9768
9769     /* If this is an ICS game, only ICS can really say it's done;
9770        if not, anyone can. */
9771     isIcsGame = (gameMode == IcsPlayingWhite ||
9772                  gameMode == IcsPlayingBlack ||
9773                  gameMode == IcsObserving    ||
9774                  gameMode == IcsExamining);
9775
9776     if (!isIcsGame || whosays == GE_ICS) {
9777         /* OK -- not an ICS game, or ICS said it was done */
9778         StopClocks();
9779         if (!isIcsGame && !appData.noChessProgram)
9780           SetUserThinkingEnables();
9781
9782         /* [HGM] if a machine claims the game end we verify this claim */
9783         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9784             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9785                 char claimer;
9786                 ChessMove trueResult = (ChessMove) -1;
9787
9788                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9789                                             first.twoMachinesColor[0] :
9790                                             second.twoMachinesColor[0] ;
9791
9792                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9793                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9794                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9795                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9796                 } else
9797                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9798                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9799                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9800                 } else
9801                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9802                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9803                 }
9804
9805                 // now verify win claims, but not in drop games, as we don't understand those yet
9806                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9807                                                  || gameInfo.variant == VariantGreat) &&
9808                     (result == WhiteWins && claimer == 'w' ||
9809                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9810                       if (appData.debugMode) {
9811                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9812                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9813                       }
9814                       if(result != trueResult) {
9815                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9816                               result = claimer == 'w' ? BlackWins : WhiteWins;
9817                               resultDetails = buf;
9818                       }
9819                 } else
9820                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9821                     && (forwardMostMove <= backwardMostMove ||
9822                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9823                         (claimer=='b')==(forwardMostMove&1))
9824                                                                                   ) {
9825                       /* [HGM] verify: draws that were not flagged are false claims */
9826                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9827                       result = claimer == 'w' ? BlackWins : WhiteWins;
9828                       resultDetails = buf;
9829                 }
9830                 /* (Claiming a loss is accepted no questions asked!) */
9831             }
9832             /* [HGM] bare: don't allow bare King to win */
9833             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9834                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9835                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9836                && result != GameIsDrawn)
9837             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9838                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9839                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9840                         if(p >= 0 && p <= (int)WhiteKing) k++;
9841                 }
9842                 if (appData.debugMode) {
9843                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9844                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9845                 }
9846                 if(k <= 1) {
9847                         result = GameIsDrawn;
9848                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9849                         resultDetails = buf;
9850                 }
9851             }
9852         }
9853
9854
9855         if(serverMoves != NULL && !loadFlag) { char c = '=';
9856             if(result==WhiteWins) c = '+';
9857             if(result==BlackWins) c = '-';
9858             if(resultDetails != NULL)
9859                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9860         }
9861         if (resultDetails != NULL) {
9862             gameInfo.result = result;
9863             gameInfo.resultDetails = StrSave(resultDetails);
9864
9865             /* display last move only if game was not loaded from file */
9866             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9867                 DisplayMove(currentMove - 1);
9868
9869             if (forwardMostMove != 0) {
9870                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9871                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9872                                                                 ) {
9873                     if (*appData.saveGameFile != NULLCHAR) {
9874                         SaveGameToFile(appData.saveGameFile, TRUE);
9875                     } else if (appData.autoSaveGames) {
9876                         AutoSaveGame();
9877                     }
9878                     if (*appData.savePositionFile != NULLCHAR) {
9879                         SavePositionToFile(appData.savePositionFile);
9880                     }
9881                 }
9882             }
9883
9884             /* Tell program how game ended in case it is learning */
9885             /* [HGM] Moved this to after saving the PGN, just in case */
9886             /* engine died and we got here through time loss. In that */
9887             /* case we will get a fatal error writing the pipe, which */
9888             /* would otherwise lose us the PGN.                       */
9889             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9890             /* output during GameEnds should never be fatal anymore   */
9891             if (gameMode == MachinePlaysWhite ||
9892                 gameMode == MachinePlaysBlack ||
9893                 gameMode == TwoMachinesPlay ||
9894                 gameMode == IcsPlayingWhite ||
9895                 gameMode == IcsPlayingBlack ||
9896                 gameMode == BeginningOfGame) {
9897                 char buf[MSG_SIZ];
9898                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9899                         resultDetails);
9900                 if (first.pr != NoProc) {
9901                     SendToProgram(buf, &first);
9902                 }
9903                 if (second.pr != NoProc &&
9904                     gameMode == TwoMachinesPlay) {
9905                     SendToProgram(buf, &second);
9906                 }
9907             }
9908         }
9909
9910         if (appData.icsActive) {
9911             if (appData.quietPlay &&
9912                 (gameMode == IcsPlayingWhite ||
9913                  gameMode == IcsPlayingBlack)) {
9914                 SendToICS(ics_prefix);
9915                 SendToICS("set shout 1\n");
9916             }
9917             nextGameMode = IcsIdle;
9918             ics_user_moved = FALSE;
9919             /* clean up premove.  It's ugly when the game has ended and the
9920              * premove highlights are still on the board.
9921              */
9922             if (gotPremove) {
9923               gotPremove = FALSE;
9924               ClearPremoveHighlights();
9925               DrawPosition(FALSE, boards[currentMove]);
9926             }
9927             if (whosays == GE_ICS) {
9928                 switch (result) {
9929                 case WhiteWins:
9930                     if (gameMode == IcsPlayingWhite)
9931                         PlayIcsWinSound();
9932                     else if(gameMode == IcsPlayingBlack)
9933                         PlayIcsLossSound();
9934                     break;
9935                 case BlackWins:
9936                     if (gameMode == IcsPlayingBlack)
9937                         PlayIcsWinSound();
9938                     else if(gameMode == IcsPlayingWhite)
9939                         PlayIcsLossSound();
9940                     break;
9941                 case GameIsDrawn:
9942                     PlayIcsDrawSound();
9943                     break;
9944                 default:
9945                     PlayIcsUnfinishedSound();
9946                 }
9947             }
9948         } else if (gameMode == EditGame ||
9949                    gameMode == PlayFromGameFile ||
9950                    gameMode == AnalyzeMode ||
9951                    gameMode == AnalyzeFile) {
9952             nextGameMode = gameMode;
9953         } else {
9954             nextGameMode = EndOfGame;
9955         }
9956         pausing = FALSE;
9957         ModeHighlight();
9958     } else {
9959         nextGameMode = gameMode;
9960     }
9961
9962     if (appData.noChessProgram) {
9963         gameMode = nextGameMode;
9964         ModeHighlight();
9965         endingGame = 0; /* [HGM] crash */
9966         return;
9967     }
9968
9969     if (first.reuse) {
9970         /* Put first chess program into idle state */
9971         if (first.pr != NoProc &&
9972             (gameMode == MachinePlaysWhite ||
9973              gameMode == MachinePlaysBlack ||
9974              gameMode == TwoMachinesPlay ||
9975              gameMode == IcsPlayingWhite ||
9976              gameMode == IcsPlayingBlack ||
9977              gameMode == BeginningOfGame)) {
9978             SendToProgram("force\n", &first);
9979             if (first.usePing) {
9980               char buf[MSG_SIZ];
9981               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9982               SendToProgram(buf, &first);
9983             }
9984         }
9985     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9986         /* Kill off first chess program */
9987         if (first.isr != NULL)
9988           RemoveInputSource(first.isr);
9989         first.isr = NULL;
9990
9991         if (first.pr != NoProc) {
9992             ExitAnalyzeMode();
9993             DoSleep( appData.delayBeforeQuit );
9994             SendToProgram("quit\n", &first);
9995             DoSleep( appData.delayAfterQuit );
9996             DestroyChildProcess(first.pr, first.useSigterm);
9997         }
9998         first.pr = NoProc;
9999     }
10000     if (second.reuse) {
10001         /* Put second chess program into idle state */
10002         if (second.pr != NoProc &&
10003             gameMode == TwoMachinesPlay) {
10004             SendToProgram("force\n", &second);
10005             if (second.usePing) {
10006               char buf[MSG_SIZ];
10007               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10008               SendToProgram(buf, &second);
10009             }
10010         }
10011     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10012         /* Kill off second chess program */
10013         if (second.isr != NULL)
10014           RemoveInputSource(second.isr);
10015         second.isr = NULL;
10016
10017         if (second.pr != NoProc) {
10018             DoSleep( appData.delayBeforeQuit );
10019             SendToProgram("quit\n", &second);
10020             DoSleep( appData.delayAfterQuit );
10021             DestroyChildProcess(second.pr, second.useSigterm);
10022         }
10023         second.pr = NoProc;
10024     }
10025
10026     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10027         char resChar = '=';
10028         switch (result) {
10029         case WhiteWins:
10030           resChar = '+';
10031           if (first.twoMachinesColor[0] == 'w') {
10032             first.matchWins++;
10033           } else {
10034             second.matchWins++;
10035           }
10036           break;
10037         case BlackWins:
10038           resChar = '-';
10039           if (first.twoMachinesColor[0] == 'b') {
10040             first.matchWins++;
10041           } else {
10042             second.matchWins++;
10043           }
10044           break;
10045         case GameUnfinished:
10046           resChar = ' ';
10047         default:
10048           break;
10049         }
10050
10051         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10052         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10053             ReserveGame(nextGame, resChar); // sets nextGame
10054             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10055         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10056
10057         if (nextGame <= appData.matchGames && !abortMatch) {
10058             gameMode = nextGameMode;
10059             matchGame = nextGame; // this will be overruled in tourney mode!
10060             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10061             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10062             endingGame = 0; /* [HGM] crash */
10063             return;
10064         } else {
10065             gameMode = nextGameMode;
10066             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10067                      first.tidy, second.tidy,
10068                      first.matchWins, second.matchWins,
10069                      appData.matchGames - (first.matchWins + second.matchWins));
10070             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10071             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10072                 first.twoMachinesColor = "black\n";
10073                 second.twoMachinesColor = "white\n";
10074             } else {
10075                 first.twoMachinesColor = "white\n";
10076                 second.twoMachinesColor = "black\n";
10077             }
10078         }
10079     }
10080     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10081         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10082       ExitAnalyzeMode();
10083     gameMode = nextGameMode;
10084     ModeHighlight();
10085     endingGame = 0;  /* [HGM] crash */
10086     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10087         if(matchMode == TRUE) { // match through command line: exit with or without popup
10088             if(ranking) {
10089                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10090                 else ExitEvent(0);
10091             } else DisplayFatalError(buf, 0, 0);
10092         } else { // match through menu; just stop, with or without popup
10093             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10094             if(ranking){
10095                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10096             } else DisplayNote(buf);
10097       }
10098       if(ranking) free(ranking);
10099     }
10100 }
10101
10102 /* Assumes program was just initialized (initString sent).
10103    Leaves program in force mode. */
10104 void
10105 FeedMovesToProgram(cps, upto)
10106      ChessProgramState *cps;
10107      int upto;
10108 {
10109     int i;
10110
10111     if (appData.debugMode)
10112       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10113               startedFromSetupPosition ? "position and " : "",
10114               backwardMostMove, upto, cps->which);
10115     if(currentlyInitializedVariant != gameInfo.variant) {
10116       char buf[MSG_SIZ];
10117         // [HGM] variantswitch: make engine aware of new variant
10118         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10119                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10120         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10121         SendToProgram(buf, cps);
10122         currentlyInitializedVariant = gameInfo.variant;
10123     }
10124     SendToProgram("force\n", cps);
10125     if (startedFromSetupPosition) {
10126         SendBoard(cps, backwardMostMove);
10127     if (appData.debugMode) {
10128         fprintf(debugFP, "feedMoves\n");
10129     }
10130     }
10131     for (i = backwardMostMove; i < upto; i++) {
10132         SendMoveToProgram(i, cps);
10133     }
10134 }
10135
10136
10137 int
10138 ResurrectChessProgram()
10139 {
10140      /* The chess program may have exited.
10141         If so, restart it and feed it all the moves made so far. */
10142     static int doInit = 0;
10143
10144     if (appData.noChessProgram) return 1;
10145
10146     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10147         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10148         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10149         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10150     } else {
10151         if (first.pr != NoProc) return 1;
10152         StartChessProgram(&first);
10153     }
10154     InitChessProgram(&first, FALSE);
10155     FeedMovesToProgram(&first, currentMove);
10156
10157     if (!first.sendTime) {
10158         /* can't tell gnuchess what its clock should read,
10159            so we bow to its notion. */
10160         ResetClocks();
10161         timeRemaining[0][currentMove] = whiteTimeRemaining;
10162         timeRemaining[1][currentMove] = blackTimeRemaining;
10163     }
10164
10165     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10166                 appData.icsEngineAnalyze) && first.analysisSupport) {
10167       SendToProgram("analyze\n", &first);
10168       first.analyzing = TRUE;
10169     }
10170     return 1;
10171 }
10172
10173 /*
10174  * Button procedures
10175  */
10176 void
10177 Reset(redraw, init)
10178      int redraw, init;
10179 {
10180     int i;
10181
10182     if (appData.debugMode) {
10183         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10184                 redraw, init, gameMode);
10185     }
10186     CleanupTail(); // [HGM] vari: delete any stored variations
10187     pausing = pauseExamInvalid = FALSE;
10188     startedFromSetupPosition = blackPlaysFirst = FALSE;
10189     firstMove = TRUE;
10190     whiteFlag = blackFlag = FALSE;
10191     userOfferedDraw = FALSE;
10192     hintRequested = bookRequested = FALSE;
10193     first.maybeThinking = FALSE;
10194     second.maybeThinking = FALSE;
10195     first.bookSuspend = FALSE; // [HGM] book
10196     second.bookSuspend = FALSE;
10197     thinkOutput[0] = NULLCHAR;
10198     lastHint[0] = NULLCHAR;
10199     ClearGameInfo(&gameInfo);
10200     gameInfo.variant = StringToVariant(appData.variant);
10201     ics_user_moved = ics_clock_paused = FALSE;
10202     ics_getting_history = H_FALSE;
10203     ics_gamenum = -1;
10204     white_holding[0] = black_holding[0] = NULLCHAR;
10205     ClearProgramStats();
10206     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10207
10208     ResetFrontEnd();
10209     ClearHighlights();
10210     flipView = appData.flipView;
10211     ClearPremoveHighlights();
10212     gotPremove = FALSE;
10213     alarmSounded = FALSE;
10214
10215     GameEnds(EndOfFile, NULL, GE_PLAYER);
10216     if(appData.serverMovesName != NULL) {
10217         /* [HGM] prepare to make moves file for broadcasting */
10218         clock_t t = clock();
10219         if(serverMoves != NULL) fclose(serverMoves);
10220         serverMoves = fopen(appData.serverMovesName, "r");
10221         if(serverMoves != NULL) {
10222             fclose(serverMoves);
10223             /* delay 15 sec before overwriting, so all clients can see end */
10224             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10225         }
10226         serverMoves = fopen(appData.serverMovesName, "w");
10227     }
10228
10229     ExitAnalyzeMode();
10230     gameMode = BeginningOfGame;
10231     ModeHighlight();
10232     if(appData.icsActive) gameInfo.variant = VariantNormal;
10233     currentMove = forwardMostMove = backwardMostMove = 0;
10234     InitPosition(redraw);
10235     for (i = 0; i < MAX_MOVES; i++) {
10236         if (commentList[i] != NULL) {
10237             free(commentList[i]);
10238             commentList[i] = NULL;
10239         }
10240     }
10241     ResetClocks();
10242     timeRemaining[0][0] = whiteTimeRemaining;
10243     timeRemaining[1][0] = blackTimeRemaining;
10244
10245     if (first.pr == NULL) {
10246         StartChessProgram(&first);
10247     }
10248     if (init) {
10249             InitChessProgram(&first, startedFromSetupPosition);
10250     }
10251     DisplayTitle("");
10252     DisplayMessage("", "");
10253     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10254     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10255 }
10256
10257 void
10258 AutoPlayGameLoop()
10259 {
10260     for (;;) {
10261         if (!AutoPlayOneMove())
10262           return;
10263         if (matchMode || appData.timeDelay == 0)
10264           continue;
10265         if (appData.timeDelay < 0)
10266           return;
10267         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10268         break;
10269     }
10270 }
10271
10272
10273 int
10274 AutoPlayOneMove()
10275 {
10276     int fromX, fromY, toX, toY;
10277
10278     if (appData.debugMode) {
10279       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10280     }
10281
10282     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10283       return FALSE;
10284
10285     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10286       pvInfoList[currentMove].depth = programStats.depth;
10287       pvInfoList[currentMove].score = programStats.score;
10288       pvInfoList[currentMove].time  = 0;
10289       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10290     }
10291
10292     if (currentMove >= forwardMostMove) {
10293       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10294       gameMode = EditGame;
10295       ModeHighlight();
10296
10297       /* [AS] Clear current move marker at the end of a game */
10298       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10299
10300       return FALSE;
10301     }
10302
10303     toX = moveList[currentMove][2] - AAA;
10304     toY = moveList[currentMove][3] - ONE;
10305
10306     if (moveList[currentMove][1] == '@') {
10307         if (appData.highlightLastMove) {
10308             SetHighlights(-1, -1, toX, toY);
10309         }
10310     } else {
10311         fromX = moveList[currentMove][0] - AAA;
10312         fromY = moveList[currentMove][1] - ONE;
10313
10314         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10315
10316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10317
10318         if (appData.highlightLastMove) {
10319             SetHighlights(fromX, fromY, toX, toY);
10320         }
10321     }
10322     DisplayMove(currentMove);
10323     SendMoveToProgram(currentMove++, &first);
10324     DisplayBothClocks();
10325     DrawPosition(FALSE, boards[currentMove]);
10326     // [HGM] PV info: always display, routine tests if empty
10327     DisplayComment(currentMove - 1, commentList[currentMove]);
10328     return TRUE;
10329 }
10330
10331
10332 int
10333 LoadGameOneMove(readAhead)
10334      ChessMove readAhead;
10335 {
10336     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10337     char promoChar = NULLCHAR;
10338     ChessMove moveType;
10339     char move[MSG_SIZ];
10340     char *p, *q;
10341
10342     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10343         gameMode != AnalyzeMode && gameMode != Training) {
10344         gameFileFP = NULL;
10345         return FALSE;
10346     }
10347
10348     yyboardindex = forwardMostMove;
10349     if (readAhead != EndOfFile) {
10350       moveType = readAhead;
10351     } else {
10352       if (gameFileFP == NULL)
10353           return FALSE;
10354       moveType = (ChessMove) Myylex();
10355     }
10356
10357     done = FALSE;
10358     switch (moveType) {
10359       case Comment:
10360         if (appData.debugMode)
10361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10362         p = yy_text;
10363
10364         /* append the comment but don't display it */
10365         AppendComment(currentMove, p, FALSE);
10366         return TRUE;
10367
10368       case WhiteCapturesEnPassant:
10369       case BlackCapturesEnPassant:
10370       case WhitePromotion:
10371       case BlackPromotion:
10372       case WhiteNonPromotion:
10373       case BlackNonPromotion:
10374       case NormalMove:
10375       case WhiteKingSideCastle:
10376       case WhiteQueenSideCastle:
10377       case BlackKingSideCastle:
10378       case BlackQueenSideCastle:
10379       case WhiteKingSideCastleWild:
10380       case WhiteQueenSideCastleWild:
10381       case BlackKingSideCastleWild:
10382       case BlackQueenSideCastleWild:
10383       /* PUSH Fabien */
10384       case WhiteHSideCastleFR:
10385       case WhiteASideCastleFR:
10386       case BlackHSideCastleFR:
10387       case BlackASideCastleFR:
10388       /* POP Fabien */
10389         if (appData.debugMode)
10390           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10391         fromX = currentMoveString[0] - AAA;
10392         fromY = currentMoveString[1] - ONE;
10393         toX = currentMoveString[2] - AAA;
10394         toY = currentMoveString[3] - ONE;
10395         promoChar = currentMoveString[4];
10396         break;
10397
10398       case WhiteDrop:
10399       case BlackDrop:
10400         if (appData.debugMode)
10401           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10402         fromX = moveType == WhiteDrop ?
10403           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10404         (int) CharToPiece(ToLower(currentMoveString[0]));
10405         fromY = DROP_RANK;
10406         toX = currentMoveString[2] - AAA;
10407         toY = currentMoveString[3] - ONE;
10408         break;
10409
10410       case WhiteWins:
10411       case BlackWins:
10412       case GameIsDrawn:
10413       case GameUnfinished:
10414         if (appData.debugMode)
10415           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10416         p = strchr(yy_text, '{');
10417         if (p == NULL) p = strchr(yy_text, '(');
10418         if (p == NULL) {
10419             p = yy_text;
10420             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10421         } else {
10422             q = strchr(p, *p == '{' ? '}' : ')');
10423             if (q != NULL) *q = NULLCHAR;
10424             p++;
10425         }
10426         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10427         GameEnds(moveType, p, GE_FILE);
10428         done = TRUE;
10429         if (cmailMsgLoaded) {
10430             ClearHighlights();
10431             flipView = WhiteOnMove(currentMove);
10432             if (moveType == GameUnfinished) flipView = !flipView;
10433             if (appData.debugMode)
10434               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10435         }
10436         break;
10437
10438       case EndOfFile:
10439         if (appData.debugMode)
10440           fprintf(debugFP, "Parser hit end of file\n");
10441         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10442           case MT_NONE:
10443           case MT_CHECK:
10444             break;
10445           case MT_CHECKMATE:
10446           case MT_STAINMATE:
10447             if (WhiteOnMove(currentMove)) {
10448                 GameEnds(BlackWins, "Black mates", GE_FILE);
10449             } else {
10450                 GameEnds(WhiteWins, "White mates", GE_FILE);
10451             }
10452             break;
10453           case MT_STALEMATE:
10454             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10455             break;
10456         }
10457         done = TRUE;
10458         break;
10459
10460       case MoveNumberOne:
10461         if (lastLoadGameStart == GNUChessGame) {
10462             /* GNUChessGames have numbers, but they aren't move numbers */
10463             if (appData.debugMode)
10464               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10465                       yy_text, (int) moveType);
10466             return LoadGameOneMove(EndOfFile); /* tail recursion */
10467         }
10468         /* else fall thru */
10469
10470       case XBoardGame:
10471       case GNUChessGame:
10472       case PGNTag:
10473         /* Reached start of next game in file */
10474         if (appData.debugMode)
10475           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10476         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10477           case MT_NONE:
10478           case MT_CHECK:
10479             break;
10480           case MT_CHECKMATE:
10481           case MT_STAINMATE:
10482             if (WhiteOnMove(currentMove)) {
10483                 GameEnds(BlackWins, "Black mates", GE_FILE);
10484             } else {
10485                 GameEnds(WhiteWins, "White mates", GE_FILE);
10486             }
10487             break;
10488           case MT_STALEMATE:
10489             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10490             break;
10491         }
10492         done = TRUE;
10493         break;
10494
10495       case PositionDiagram:     /* should not happen; ignore */
10496       case ElapsedTime:         /* ignore */
10497       case NAG:                 /* ignore */
10498         if (appData.debugMode)
10499           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10500                   yy_text, (int) moveType);
10501         return LoadGameOneMove(EndOfFile); /* tail recursion */
10502
10503       case IllegalMove:
10504         if (appData.testLegality) {
10505             if (appData.debugMode)
10506               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10507             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10508                     (forwardMostMove / 2) + 1,
10509                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10510             DisplayError(move, 0);
10511             done = TRUE;
10512         } else {
10513             if (appData.debugMode)
10514               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10515                       yy_text, currentMoveString);
10516             fromX = currentMoveString[0] - AAA;
10517             fromY = currentMoveString[1] - ONE;
10518             toX = currentMoveString[2] - AAA;
10519             toY = currentMoveString[3] - ONE;
10520             promoChar = currentMoveString[4];
10521         }
10522         break;
10523
10524       case AmbiguousMove:
10525         if (appData.debugMode)
10526           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10527         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10528                 (forwardMostMove / 2) + 1,
10529                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10530         DisplayError(move, 0);
10531         done = TRUE;
10532         break;
10533
10534       default:
10535       case ImpossibleMove:
10536         if (appData.debugMode)
10537           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10538         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10539                 (forwardMostMove / 2) + 1,
10540                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10541         DisplayError(move, 0);
10542         done = TRUE;
10543         break;
10544     }
10545
10546     if (done) {
10547         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10548             DrawPosition(FALSE, boards[currentMove]);
10549             DisplayBothClocks();
10550             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10551               DisplayComment(currentMove - 1, commentList[currentMove]);
10552         }
10553         (void) StopLoadGameTimer();
10554         gameFileFP = NULL;
10555         cmailOldMove = forwardMostMove;
10556         return FALSE;
10557     } else {
10558         /* currentMoveString is set as a side-effect of yylex */
10559
10560         thinkOutput[0] = NULLCHAR;
10561         MakeMove(fromX, fromY, toX, toY, promoChar);
10562         currentMove = forwardMostMove;
10563         return TRUE;
10564     }
10565 }
10566
10567 /* Load the nth game from the given file */
10568 int
10569 LoadGameFromFile(filename, n, title, useList)
10570      char *filename;
10571      int n;
10572      char *title;
10573      /*Boolean*/ int useList;
10574 {
10575     FILE *f;
10576     char buf[MSG_SIZ];
10577
10578     if (strcmp(filename, "-") == 0) {
10579         f = stdin;
10580         title = "stdin";
10581     } else {
10582         f = fopen(filename, "rb");
10583         if (f == NULL) {
10584           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10585             DisplayError(buf, errno);
10586             return FALSE;
10587         }
10588     }
10589     if (fseek(f, 0, 0) == -1) {
10590         /* f is not seekable; probably a pipe */
10591         useList = FALSE;
10592     }
10593     if (useList && n == 0) {
10594         int error = GameListBuild(f);
10595         if (error) {
10596             DisplayError(_("Cannot build game list"), error);
10597         } else if (!ListEmpty(&gameList) &&
10598                    ((ListGame *) gameList.tailPred)->number > 1) {
10599             GameListPopUp(f, title);
10600             return TRUE;
10601         }
10602         GameListDestroy();
10603         n = 1;
10604     }
10605     if (n == 0) n = 1;
10606     return LoadGame(f, n, title, FALSE);
10607 }
10608
10609
10610 void
10611 MakeRegisteredMove()
10612 {
10613     int fromX, fromY, toX, toY;
10614     char promoChar;
10615     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10616         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10617           case CMAIL_MOVE:
10618           case CMAIL_DRAW:
10619             if (appData.debugMode)
10620               fprintf(debugFP, "Restoring %s for game %d\n",
10621                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10622
10623             thinkOutput[0] = NULLCHAR;
10624             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10625             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10626             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10627             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10628             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10629             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10630             MakeMove(fromX, fromY, toX, toY, promoChar);
10631             ShowMove(fromX, fromY, toX, toY);
10632
10633             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10634               case MT_NONE:
10635               case MT_CHECK:
10636                 break;
10637
10638               case MT_CHECKMATE:
10639               case MT_STAINMATE:
10640                 if (WhiteOnMove(currentMove)) {
10641                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10642                 } else {
10643                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10644                 }
10645                 break;
10646
10647               case MT_STALEMATE:
10648                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10649                 break;
10650             }
10651
10652             break;
10653
10654           case CMAIL_RESIGN:
10655             if (WhiteOnMove(currentMove)) {
10656                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10657             } else {
10658                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10659             }
10660             break;
10661
10662           case CMAIL_ACCEPT:
10663             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10664             break;
10665
10666           default:
10667             break;
10668         }
10669     }
10670
10671     return;
10672 }
10673
10674 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10675 int
10676 CmailLoadGame(f, gameNumber, title, useList)
10677      FILE *f;
10678      int gameNumber;
10679      char *title;
10680      int useList;
10681 {
10682     int retVal;
10683
10684     if (gameNumber > nCmailGames) {
10685         DisplayError(_("No more games in this message"), 0);
10686         return FALSE;
10687     }
10688     if (f == lastLoadGameFP) {
10689         int offset = gameNumber - lastLoadGameNumber;
10690         if (offset == 0) {
10691             cmailMsg[0] = NULLCHAR;
10692             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10693                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10694                 nCmailMovesRegistered--;
10695             }
10696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10697             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10698                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10699             }
10700         } else {
10701             if (! RegisterMove()) return FALSE;
10702         }
10703     }
10704
10705     retVal = LoadGame(f, gameNumber, title, useList);
10706
10707     /* Make move registered during previous look at this game, if any */
10708     MakeRegisteredMove();
10709
10710     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10711         commentList[currentMove]
10712           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10713         DisplayComment(currentMove - 1, commentList[currentMove]);
10714     }
10715
10716     return retVal;
10717 }
10718
10719 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10720 int
10721 ReloadGame(offset)
10722      int offset;
10723 {
10724     int gameNumber = lastLoadGameNumber + offset;
10725     if (lastLoadGameFP == NULL) {
10726         DisplayError(_("No game has been loaded yet"), 0);
10727         return FALSE;
10728     }
10729     if (gameNumber <= 0) {
10730         DisplayError(_("Can't back up any further"), 0);
10731         return FALSE;
10732     }
10733     if (cmailMsgLoaded) {
10734         return CmailLoadGame(lastLoadGameFP, gameNumber,
10735                              lastLoadGameTitle, lastLoadGameUseList);
10736     } else {
10737         return LoadGame(lastLoadGameFP, gameNumber,
10738                         lastLoadGameTitle, lastLoadGameUseList);
10739     }
10740 }
10741
10742
10743
10744 /* Load the nth game from open file f */
10745 int
10746 LoadGame(f, gameNumber, title, useList)
10747      FILE *f;
10748      int gameNumber;
10749      char *title;
10750      int useList;
10751 {
10752     ChessMove cm;
10753     char buf[MSG_SIZ];
10754     int gn = gameNumber;
10755     ListGame *lg = NULL;
10756     int numPGNTags = 0;
10757     int err;
10758     GameMode oldGameMode;
10759     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10760
10761     if (appData.debugMode)
10762         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10763
10764     if (gameMode == Training )
10765         SetTrainingModeOff();
10766
10767     oldGameMode = gameMode;
10768     if (gameMode != BeginningOfGame) {
10769       Reset(FALSE, TRUE);
10770     }
10771
10772     gameFileFP = f;
10773     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10774         fclose(lastLoadGameFP);
10775     }
10776
10777     if (useList) {
10778         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10779
10780         if (lg) {
10781             fseek(f, lg->offset, 0);
10782             GameListHighlight(gameNumber);
10783             gn = 1;
10784         }
10785         else {
10786             DisplayError(_("Game number out of range"), 0);
10787             return FALSE;
10788         }
10789     } else {
10790         GameListDestroy();
10791         if (fseek(f, 0, 0) == -1) {
10792             if (f == lastLoadGameFP ?
10793                 gameNumber == lastLoadGameNumber + 1 :
10794                 gameNumber == 1) {
10795                 gn = 1;
10796             } else {
10797                 DisplayError(_("Can't seek on game file"), 0);
10798                 return FALSE;
10799             }
10800         }
10801     }
10802     lastLoadGameFP = f;
10803     lastLoadGameNumber = gameNumber;
10804     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10805     lastLoadGameUseList = useList;
10806
10807     yynewfile(f);
10808
10809     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10810       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10811                 lg->gameInfo.black);
10812             DisplayTitle(buf);
10813     } else if (*title != NULLCHAR) {
10814         if (gameNumber > 1) {
10815           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10816             DisplayTitle(buf);
10817         } else {
10818             DisplayTitle(title);
10819         }
10820     }
10821
10822     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10823         gameMode = PlayFromGameFile;
10824         ModeHighlight();
10825     }
10826
10827     currentMove = forwardMostMove = backwardMostMove = 0;
10828     CopyBoard(boards[0], initialPosition);
10829     StopClocks();
10830
10831     /*
10832      * Skip the first gn-1 games in the file.
10833      * Also skip over anything that precedes an identifiable
10834      * start of game marker, to avoid being confused by
10835      * garbage at the start of the file.  Currently
10836      * recognized start of game markers are the move number "1",
10837      * the pattern "gnuchess .* game", the pattern
10838      * "^[#;%] [^ ]* game file", and a PGN tag block.
10839      * A game that starts with one of the latter two patterns
10840      * will also have a move number 1, possibly
10841      * following a position diagram.
10842      * 5-4-02: Let's try being more lenient and allowing a game to
10843      * start with an unnumbered move.  Does that break anything?
10844      */
10845     cm = lastLoadGameStart = EndOfFile;
10846     while (gn > 0) {
10847         yyboardindex = forwardMostMove;
10848         cm = (ChessMove) Myylex();
10849         switch (cm) {
10850           case EndOfFile:
10851             if (cmailMsgLoaded) {
10852                 nCmailGames = CMAIL_MAX_GAMES - gn;
10853             } else {
10854                 Reset(TRUE, TRUE);
10855                 DisplayError(_("Game not found in file"), 0);
10856             }
10857             return FALSE;
10858
10859           case GNUChessGame:
10860           case XBoardGame:
10861             gn--;
10862             lastLoadGameStart = cm;
10863             break;
10864
10865           case MoveNumberOne:
10866             switch (lastLoadGameStart) {
10867               case GNUChessGame:
10868               case XBoardGame:
10869               case PGNTag:
10870                 break;
10871               case MoveNumberOne:
10872               case EndOfFile:
10873                 gn--;           /* count this game */
10874                 lastLoadGameStart = cm;
10875                 break;
10876               default:
10877                 /* impossible */
10878                 break;
10879             }
10880             break;
10881
10882           case PGNTag:
10883             switch (lastLoadGameStart) {
10884               case GNUChessGame:
10885               case PGNTag:
10886               case MoveNumberOne:
10887               case EndOfFile:
10888                 gn--;           /* count this game */
10889                 lastLoadGameStart = cm;
10890                 break;
10891               case XBoardGame:
10892                 lastLoadGameStart = cm; /* game counted already */
10893                 break;
10894               default:
10895                 /* impossible */
10896                 break;
10897             }
10898             if (gn > 0) {
10899                 do {
10900                     yyboardindex = forwardMostMove;
10901                     cm = (ChessMove) Myylex();
10902                 } while (cm == PGNTag || cm == Comment);
10903             }
10904             break;
10905
10906           case WhiteWins:
10907           case BlackWins:
10908           case GameIsDrawn:
10909             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10910                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10911                     != CMAIL_OLD_RESULT) {
10912                     nCmailResults ++ ;
10913                     cmailResult[  CMAIL_MAX_GAMES
10914                                 - gn - 1] = CMAIL_OLD_RESULT;
10915                 }
10916             }
10917             break;
10918
10919           case NormalMove:
10920             /* Only a NormalMove can be at the start of a game
10921              * without a position diagram. */
10922             if (lastLoadGameStart == EndOfFile ) {
10923               gn--;
10924               lastLoadGameStart = MoveNumberOne;
10925             }
10926             break;
10927
10928           default:
10929             break;
10930         }
10931     }
10932
10933     if (appData.debugMode)
10934       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10935
10936     if (cm == XBoardGame) {
10937         /* Skip any header junk before position diagram and/or move 1 */
10938         for (;;) {
10939             yyboardindex = forwardMostMove;
10940             cm = (ChessMove) Myylex();
10941
10942             if (cm == EndOfFile ||
10943                 cm == GNUChessGame || cm == XBoardGame) {
10944                 /* Empty game; pretend end-of-file and handle later */
10945                 cm = EndOfFile;
10946                 break;
10947             }
10948
10949             if (cm == MoveNumberOne || cm == PositionDiagram ||
10950                 cm == PGNTag || cm == Comment)
10951               break;
10952         }
10953     } else if (cm == GNUChessGame) {
10954         if (gameInfo.event != NULL) {
10955             free(gameInfo.event);
10956         }
10957         gameInfo.event = StrSave(yy_text);
10958     }
10959
10960     startedFromSetupPosition = FALSE;
10961     while (cm == PGNTag) {
10962         if (appData.debugMode)
10963           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10964         err = ParsePGNTag(yy_text, &gameInfo);
10965         if (!err) numPGNTags++;
10966
10967         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10968         if(gameInfo.variant != oldVariant) {
10969             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10970             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10971             InitPosition(TRUE);
10972             oldVariant = gameInfo.variant;
10973             if (appData.debugMode)
10974               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10975         }
10976
10977
10978         if (gameInfo.fen != NULL) {
10979           Board initial_position;
10980           startedFromSetupPosition = TRUE;
10981           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10982             Reset(TRUE, TRUE);
10983             DisplayError(_("Bad FEN position in file"), 0);
10984             return FALSE;
10985           }
10986           CopyBoard(boards[0], initial_position);
10987           if (blackPlaysFirst) {
10988             currentMove = forwardMostMove = backwardMostMove = 1;
10989             CopyBoard(boards[1], initial_position);
10990             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10991             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10992             timeRemaining[0][1] = whiteTimeRemaining;
10993             timeRemaining[1][1] = blackTimeRemaining;
10994             if (commentList[0] != NULL) {
10995               commentList[1] = commentList[0];
10996               commentList[0] = NULL;
10997             }
10998           } else {
10999             currentMove = forwardMostMove = backwardMostMove = 0;
11000           }
11001           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11002           {   int i;
11003               initialRulePlies = FENrulePlies;
11004               for( i=0; i< nrCastlingRights; i++ )
11005                   initialRights[i] = initial_position[CASTLING][i];
11006           }
11007           yyboardindex = forwardMostMove;
11008           free(gameInfo.fen);
11009           gameInfo.fen = NULL;
11010         }
11011
11012         yyboardindex = forwardMostMove;
11013         cm = (ChessMove) Myylex();
11014
11015         /* Handle comments interspersed among the tags */
11016         while (cm == Comment) {
11017             char *p;
11018             if (appData.debugMode)
11019               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11020             p = yy_text;
11021             AppendComment(currentMove, p, FALSE);
11022             yyboardindex = forwardMostMove;
11023             cm = (ChessMove) Myylex();
11024         }
11025     }
11026
11027     /* don't rely on existence of Event tag since if game was
11028      * pasted from clipboard the Event tag may not exist
11029      */
11030     if (numPGNTags > 0){
11031         char *tags;
11032         if (gameInfo.variant == VariantNormal) {
11033           VariantClass v = StringToVariant(gameInfo.event);
11034           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11035           if(v < VariantShogi) gameInfo.variant = v;
11036         }
11037         if (!matchMode) {
11038           if( appData.autoDisplayTags ) {
11039             tags = PGNTags(&gameInfo);
11040             TagsPopUp(tags, CmailMsg());
11041             free(tags);
11042           }
11043         }
11044     } else {
11045         /* Make something up, but don't display it now */
11046         SetGameInfo();
11047         TagsPopDown();
11048     }
11049
11050     if (cm == PositionDiagram) {
11051         int i, j;
11052         char *p;
11053         Board initial_position;
11054
11055         if (appData.debugMode)
11056           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11057
11058         if (!startedFromSetupPosition) {
11059             p = yy_text;
11060             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11061               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11062                 switch (*p) {
11063                   case '{':
11064                   case '[':
11065                   case '-':
11066                   case ' ':
11067                   case '\t':
11068                   case '\n':
11069                   case '\r':
11070                     break;
11071                   default:
11072                     initial_position[i][j++] = CharToPiece(*p);
11073                     break;
11074                 }
11075             while (*p == ' ' || *p == '\t' ||
11076                    *p == '\n' || *p == '\r') p++;
11077
11078             if (strncmp(p, "black", strlen("black"))==0)
11079               blackPlaysFirst = TRUE;
11080             else
11081               blackPlaysFirst = FALSE;
11082             startedFromSetupPosition = TRUE;
11083
11084             CopyBoard(boards[0], initial_position);
11085             if (blackPlaysFirst) {
11086                 currentMove = forwardMostMove = backwardMostMove = 1;
11087                 CopyBoard(boards[1], initial_position);
11088                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11089                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11090                 timeRemaining[0][1] = whiteTimeRemaining;
11091                 timeRemaining[1][1] = blackTimeRemaining;
11092                 if (commentList[0] != NULL) {
11093                     commentList[1] = commentList[0];
11094                     commentList[0] = NULL;
11095                 }
11096             } else {
11097                 currentMove = forwardMostMove = backwardMostMove = 0;
11098             }
11099         }
11100         yyboardindex = forwardMostMove;
11101         cm = (ChessMove) Myylex();
11102     }
11103
11104     if (first.pr == NoProc) {
11105         StartChessProgram(&first);
11106     }
11107     InitChessProgram(&first, FALSE);
11108     SendToProgram("force\n", &first);
11109     if (startedFromSetupPosition) {
11110         SendBoard(&first, forwardMostMove);
11111     if (appData.debugMode) {
11112         fprintf(debugFP, "Load Game\n");
11113     }
11114         DisplayBothClocks();
11115     }
11116
11117     /* [HGM] server: flag to write setup moves in broadcast file as one */
11118     loadFlag = appData.suppressLoadMoves;
11119
11120     while (cm == Comment) {
11121         char *p;
11122         if (appData.debugMode)
11123           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11124         p = yy_text;
11125         AppendComment(currentMove, p, FALSE);
11126         yyboardindex = forwardMostMove;
11127         cm = (ChessMove) Myylex();
11128     }
11129
11130     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11131         cm == WhiteWins || cm == BlackWins ||
11132         cm == GameIsDrawn || cm == GameUnfinished) {
11133         DisplayMessage("", _("No moves in game"));
11134         if (cmailMsgLoaded) {
11135             if (appData.debugMode)
11136               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11137             ClearHighlights();
11138             flipView = FALSE;
11139         }
11140         DrawPosition(FALSE, boards[currentMove]);
11141         DisplayBothClocks();
11142         gameMode = EditGame;
11143         ModeHighlight();
11144         gameFileFP = NULL;
11145         cmailOldMove = 0;
11146         return TRUE;
11147     }
11148
11149     // [HGM] PV info: routine tests if comment empty
11150     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11151         DisplayComment(currentMove - 1, commentList[currentMove]);
11152     }
11153     if (!matchMode && appData.timeDelay != 0)
11154       DrawPosition(FALSE, boards[currentMove]);
11155
11156     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11157       programStats.ok_to_send = 1;
11158     }
11159
11160     /* if the first token after the PGN tags is a move
11161      * and not move number 1, retrieve it from the parser
11162      */
11163     if (cm != MoveNumberOne)
11164         LoadGameOneMove(cm);
11165
11166     /* load the remaining moves from the file */
11167     while (LoadGameOneMove(EndOfFile)) {
11168       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11169       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11170     }
11171
11172     /* rewind to the start of the game */
11173     currentMove = backwardMostMove;
11174
11175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11176
11177     if (oldGameMode == AnalyzeFile ||
11178         oldGameMode == AnalyzeMode) {
11179       AnalyzeFileEvent();
11180     }
11181
11182     if (matchMode || appData.timeDelay == 0) {
11183       ToEndEvent();
11184       gameMode = EditGame;
11185       ModeHighlight();
11186     } else if (appData.timeDelay > 0) {
11187       AutoPlayGameLoop();
11188     }
11189
11190     if (appData.debugMode)
11191         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11192
11193     loadFlag = 0; /* [HGM] true game starts */
11194     return TRUE;
11195 }
11196
11197 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11198 int
11199 ReloadPosition(offset)
11200      int offset;
11201 {
11202     int positionNumber = lastLoadPositionNumber + offset;
11203     if (lastLoadPositionFP == NULL) {
11204         DisplayError(_("No position has been loaded yet"), 0);
11205         return FALSE;
11206     }
11207     if (positionNumber <= 0) {
11208         DisplayError(_("Can't back up any further"), 0);
11209         return FALSE;
11210     }
11211     return LoadPosition(lastLoadPositionFP, positionNumber,
11212                         lastLoadPositionTitle);
11213 }
11214
11215 /* Load the nth position from the given file */
11216 int
11217 LoadPositionFromFile(filename, n, title)
11218      char *filename;
11219      int n;
11220      char *title;
11221 {
11222     FILE *f;
11223     char buf[MSG_SIZ];
11224
11225     if (strcmp(filename, "-") == 0) {
11226         return LoadPosition(stdin, n, "stdin");
11227     } else {
11228         f = fopen(filename, "rb");
11229         if (f == NULL) {
11230             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11231             DisplayError(buf, errno);
11232             return FALSE;
11233         } else {
11234             return LoadPosition(f, n, title);
11235         }
11236     }
11237 }
11238
11239 /* Load the nth position from the given open file, and close it */
11240 int
11241 LoadPosition(f, positionNumber, title)
11242      FILE *f;
11243      int positionNumber;
11244      char *title;
11245 {
11246     char *p, line[MSG_SIZ];
11247     Board initial_position;
11248     int i, j, fenMode, pn;
11249
11250     if (gameMode == Training )
11251         SetTrainingModeOff();
11252
11253     if (gameMode != BeginningOfGame) {
11254         Reset(FALSE, TRUE);
11255     }
11256     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11257         fclose(lastLoadPositionFP);
11258     }
11259     if (positionNumber == 0) positionNumber = 1;
11260     lastLoadPositionFP = f;
11261     lastLoadPositionNumber = positionNumber;
11262     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11263     if (first.pr == NoProc) {
11264       StartChessProgram(&first);
11265       InitChessProgram(&first, FALSE);
11266     }
11267     pn = positionNumber;
11268     if (positionNumber < 0) {
11269         /* Negative position number means to seek to that byte offset */
11270         if (fseek(f, -positionNumber, 0) == -1) {
11271             DisplayError(_("Can't seek on position file"), 0);
11272             return FALSE;
11273         };
11274         pn = 1;
11275     } else {
11276         if (fseek(f, 0, 0) == -1) {
11277             if (f == lastLoadPositionFP ?
11278                 positionNumber == lastLoadPositionNumber + 1 :
11279                 positionNumber == 1) {
11280                 pn = 1;
11281             } else {
11282                 DisplayError(_("Can't seek on position file"), 0);
11283                 return FALSE;
11284             }
11285         }
11286     }
11287     /* See if this file is FEN or old-style xboard */
11288     if (fgets(line, MSG_SIZ, f) == NULL) {
11289         DisplayError(_("Position not found in file"), 0);
11290         return FALSE;
11291     }
11292     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11293     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11294
11295     if (pn >= 2) {
11296         if (fenMode || line[0] == '#') pn--;
11297         while (pn > 0) {
11298             /* skip positions before number pn */
11299             if (fgets(line, MSG_SIZ, f) == NULL) {
11300                 Reset(TRUE, TRUE);
11301                 DisplayError(_("Position not found in file"), 0);
11302                 return FALSE;
11303             }
11304             if (fenMode || line[0] == '#') pn--;
11305         }
11306     }
11307
11308     if (fenMode) {
11309         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11310             DisplayError(_("Bad FEN position in file"), 0);
11311             return FALSE;
11312         }
11313     } else {
11314         (void) fgets(line, MSG_SIZ, f);
11315         (void) fgets(line, MSG_SIZ, f);
11316
11317         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11318             (void) fgets(line, MSG_SIZ, f);
11319             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11320                 if (*p == ' ')
11321                   continue;
11322                 initial_position[i][j++] = CharToPiece(*p);
11323             }
11324         }
11325
11326         blackPlaysFirst = FALSE;
11327         if (!feof(f)) {
11328             (void) fgets(line, MSG_SIZ, f);
11329             if (strncmp(line, "black", strlen("black"))==0)
11330               blackPlaysFirst = TRUE;
11331         }
11332     }
11333     startedFromSetupPosition = TRUE;
11334
11335     SendToProgram("force\n", &first);
11336     CopyBoard(boards[0], initial_position);
11337     if (blackPlaysFirst) {
11338         currentMove = forwardMostMove = backwardMostMove = 1;
11339         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11340         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11341         CopyBoard(boards[1], initial_position);
11342         DisplayMessage("", _("Black to play"));
11343     } else {
11344         currentMove = forwardMostMove = backwardMostMove = 0;
11345         DisplayMessage("", _("White to play"));
11346     }
11347     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11348     SendBoard(&first, forwardMostMove);
11349     if (appData.debugMode) {
11350 int i, j;
11351   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11352   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11353         fprintf(debugFP, "Load Position\n");
11354     }
11355
11356     if (positionNumber > 1) {
11357       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11358         DisplayTitle(line);
11359     } else {
11360         DisplayTitle(title);
11361     }
11362     gameMode = EditGame;
11363     ModeHighlight();
11364     ResetClocks();
11365     timeRemaining[0][1] = whiteTimeRemaining;
11366     timeRemaining[1][1] = blackTimeRemaining;
11367     DrawPosition(FALSE, boards[currentMove]);
11368
11369     return TRUE;
11370 }
11371
11372
11373 void
11374 CopyPlayerNameIntoFileName(dest, src)
11375      char **dest, *src;
11376 {
11377     while (*src != NULLCHAR && *src != ',') {
11378         if (*src == ' ') {
11379             *(*dest)++ = '_';
11380             src++;
11381         } else {
11382             *(*dest)++ = *src++;
11383         }
11384     }
11385 }
11386
11387 char *DefaultFileName(ext)
11388      char *ext;
11389 {
11390     static char def[MSG_SIZ];
11391     char *p;
11392
11393     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11394         p = def;
11395         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11396         *p++ = '-';
11397         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11398         *p++ = '.';
11399         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11400     } else {
11401         def[0] = NULLCHAR;
11402     }
11403     return def;
11404 }
11405
11406 /* Save the current game to the given file */
11407 int
11408 SaveGameToFile(filename, append)
11409      char *filename;
11410      int append;
11411 {
11412     FILE *f;
11413     char buf[MSG_SIZ];
11414     int result;
11415
11416     if (strcmp(filename, "-") == 0) {
11417         return SaveGame(stdout, 0, NULL);
11418     } else {
11419         f = fopen(filename, append ? "a" : "w");
11420         if (f == NULL) {
11421             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11422             DisplayError(buf, errno);
11423             return FALSE;
11424         } else {
11425             safeStrCpy(buf, lastMsg, MSG_SIZ);
11426             DisplayMessage(_("Waiting for access to save file"), "");
11427             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11428             DisplayMessage(_("Saving game"), "");
11429             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11430             result = SaveGame(f, 0, NULL);
11431             DisplayMessage(buf, "");
11432             return result;
11433         }
11434     }
11435 }
11436
11437 char *
11438 SavePart(str)
11439      char *str;
11440 {
11441     static char buf[MSG_SIZ];
11442     char *p;
11443
11444     p = strchr(str, ' ');
11445     if (p == NULL) return str;
11446     strncpy(buf, str, p - str);
11447     buf[p - str] = NULLCHAR;
11448     return buf;
11449 }
11450
11451 #define PGN_MAX_LINE 75
11452
11453 #define PGN_SIDE_WHITE  0
11454 #define PGN_SIDE_BLACK  1
11455
11456 /* [AS] */
11457 static int FindFirstMoveOutOfBook( int side )
11458 {
11459     int result = -1;
11460
11461     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11462         int index = backwardMostMove;
11463         int has_book_hit = 0;
11464
11465         if( (index % 2) != side ) {
11466             index++;
11467         }
11468
11469         while( index < forwardMostMove ) {
11470             /* Check to see if engine is in book */
11471             int depth = pvInfoList[index].depth;
11472             int score = pvInfoList[index].score;
11473             int in_book = 0;
11474
11475             if( depth <= 2 ) {
11476                 in_book = 1;
11477             }
11478             else if( score == 0 && depth == 63 ) {
11479                 in_book = 1; /* Zappa */
11480             }
11481             else if( score == 2 && depth == 99 ) {
11482                 in_book = 1; /* Abrok */
11483             }
11484
11485             has_book_hit += in_book;
11486
11487             if( ! in_book ) {
11488                 result = index;
11489
11490                 break;
11491             }
11492
11493             index += 2;
11494         }
11495     }
11496
11497     return result;
11498 }
11499
11500 /* [AS] */
11501 void GetOutOfBookInfo( char * buf )
11502 {
11503     int oob[2];
11504     int i;
11505     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11506
11507     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11508     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11509
11510     *buf = '\0';
11511
11512     if( oob[0] >= 0 || oob[1] >= 0 ) {
11513         for( i=0; i<2; i++ ) {
11514             int idx = oob[i];
11515
11516             if( idx >= 0 ) {
11517                 if( i > 0 && oob[0] >= 0 ) {
11518                     strcat( buf, "   " );
11519                 }
11520
11521                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11522                 sprintf( buf+strlen(buf), "%s%.2f",
11523                     pvInfoList[idx].score >= 0 ? "+" : "",
11524                     pvInfoList[idx].score / 100.0 );
11525             }
11526         }
11527     }
11528 }
11529
11530 /* Save game in PGN style and close the file */
11531 int
11532 SaveGamePGN(f)
11533      FILE *f;
11534 {
11535     int i, offset, linelen, newblock;
11536     time_t tm;
11537 //    char *movetext;
11538     char numtext[32];
11539     int movelen, numlen, blank;
11540     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11541
11542     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11543
11544     tm = time((time_t *) NULL);
11545
11546     PrintPGNTags(f, &gameInfo);
11547
11548     if (backwardMostMove > 0 || startedFromSetupPosition) {
11549         char *fen = PositionToFEN(backwardMostMove, NULL);
11550         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11551         fprintf(f, "\n{--------------\n");
11552         PrintPosition(f, backwardMostMove);
11553         fprintf(f, "--------------}\n");
11554         free(fen);
11555     }
11556     else {
11557         /* [AS] Out of book annotation */
11558         if( appData.saveOutOfBookInfo ) {
11559             char buf[64];
11560
11561             GetOutOfBookInfo( buf );
11562
11563             if( buf[0] != '\0' ) {
11564                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11565             }
11566         }
11567
11568         fprintf(f, "\n");
11569     }
11570
11571     i = backwardMostMove;
11572     linelen = 0;
11573     newblock = TRUE;
11574
11575     while (i < forwardMostMove) {
11576         /* Print comments preceding this move */
11577         if (commentList[i] != NULL) {
11578             if (linelen > 0) fprintf(f, "\n");
11579             fprintf(f, "%s", commentList[i]);
11580             linelen = 0;
11581             newblock = TRUE;
11582         }
11583
11584         /* Format move number */
11585         if ((i % 2) == 0)
11586           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11587         else
11588           if (newblock)
11589             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11590           else
11591             numtext[0] = NULLCHAR;
11592
11593         numlen = strlen(numtext);
11594         newblock = FALSE;
11595
11596         /* Print move number */
11597         blank = linelen > 0 && numlen > 0;
11598         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11599             fprintf(f, "\n");
11600             linelen = 0;
11601             blank = 0;
11602         }
11603         if (blank) {
11604             fprintf(f, " ");
11605             linelen++;
11606         }
11607         fprintf(f, "%s", numtext);
11608         linelen += numlen;
11609
11610         /* Get move */
11611         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11612         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11613
11614         /* Print move */
11615         blank = linelen > 0 && movelen > 0;
11616         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11617             fprintf(f, "\n");
11618             linelen = 0;
11619             blank = 0;
11620         }
11621         if (blank) {
11622             fprintf(f, " ");
11623             linelen++;
11624         }
11625         fprintf(f, "%s", move_buffer);
11626         linelen += movelen;
11627
11628         /* [AS] Add PV info if present */
11629         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11630             /* [HGM] add time */
11631             char buf[MSG_SIZ]; int seconds;
11632
11633             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11634
11635             if( seconds <= 0)
11636               buf[0] = 0;
11637             else
11638               if( seconds < 30 )
11639                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11640               else
11641                 {
11642                   seconds = (seconds + 4)/10; // round to full seconds
11643                   if( seconds < 60 )
11644                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11645                   else
11646                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11647                 }
11648
11649             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11650                       pvInfoList[i].score >= 0 ? "+" : "",
11651                       pvInfoList[i].score / 100.0,
11652                       pvInfoList[i].depth,
11653                       buf );
11654
11655             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11656
11657             /* Print score/depth */
11658             blank = linelen > 0 && movelen > 0;
11659             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11660                 fprintf(f, "\n");
11661                 linelen = 0;
11662                 blank = 0;
11663             }
11664             if (blank) {
11665                 fprintf(f, " ");
11666                 linelen++;
11667             }
11668             fprintf(f, "%s", move_buffer);
11669             linelen += movelen;
11670         }
11671
11672         i++;
11673     }
11674
11675     /* Start a new line */
11676     if (linelen > 0) fprintf(f, "\n");
11677
11678     /* Print comments after last move */
11679     if (commentList[i] != NULL) {
11680         fprintf(f, "%s\n", commentList[i]);
11681     }
11682
11683     /* Print result */
11684     if (gameInfo.resultDetails != NULL &&
11685         gameInfo.resultDetails[0] != NULLCHAR) {
11686         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11687                 PGNResult(gameInfo.result));
11688     } else {
11689         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11690     }
11691
11692     fclose(f);
11693     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11694     return TRUE;
11695 }
11696
11697 /* Save game in old style and close the file */
11698 int
11699 SaveGameOldStyle(f)
11700      FILE *f;
11701 {
11702     int i, offset;
11703     time_t tm;
11704
11705     tm = time((time_t *) NULL);
11706
11707     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11708     PrintOpponents(f);
11709
11710     if (backwardMostMove > 0 || startedFromSetupPosition) {
11711         fprintf(f, "\n[--------------\n");
11712         PrintPosition(f, backwardMostMove);
11713         fprintf(f, "--------------]\n");
11714     } else {
11715         fprintf(f, "\n");
11716     }
11717
11718     i = backwardMostMove;
11719     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11720
11721     while (i < forwardMostMove) {
11722         if (commentList[i] != NULL) {
11723             fprintf(f, "[%s]\n", commentList[i]);
11724         }
11725
11726         if ((i % 2) == 1) {
11727             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11728             i++;
11729         } else {
11730             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11731             i++;
11732             if (commentList[i] != NULL) {
11733                 fprintf(f, "\n");
11734                 continue;
11735             }
11736             if (i >= forwardMostMove) {
11737                 fprintf(f, "\n");
11738                 break;
11739             }
11740             fprintf(f, "%s\n", parseList[i]);
11741             i++;
11742         }
11743     }
11744
11745     if (commentList[i] != NULL) {
11746         fprintf(f, "[%s]\n", commentList[i]);
11747     }
11748
11749     /* This isn't really the old style, but it's close enough */
11750     if (gameInfo.resultDetails != NULL &&
11751         gameInfo.resultDetails[0] != NULLCHAR) {
11752         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11753                 gameInfo.resultDetails);
11754     } else {
11755         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11756     }
11757
11758     fclose(f);
11759     return TRUE;
11760 }
11761
11762 /* Save the current game to open file f and close the file */
11763 int
11764 SaveGame(f, dummy, dummy2)
11765      FILE *f;
11766      int dummy;
11767      char *dummy2;
11768 {
11769     if (gameMode == EditPosition) EditPositionDone(TRUE);
11770     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11771     if (appData.oldSaveStyle)
11772       return SaveGameOldStyle(f);
11773     else
11774       return SaveGamePGN(f);
11775 }
11776
11777 /* Save the current position to the given file */
11778 int
11779 SavePositionToFile(filename)
11780      char *filename;
11781 {
11782     FILE *f;
11783     char buf[MSG_SIZ];
11784
11785     if (strcmp(filename, "-") == 0) {
11786         return SavePosition(stdout, 0, NULL);
11787     } else {
11788         f = fopen(filename, "a");
11789         if (f == NULL) {
11790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11791             DisplayError(buf, errno);
11792             return FALSE;
11793         } else {
11794             safeStrCpy(buf, lastMsg, MSG_SIZ);
11795             DisplayMessage(_("Waiting for access to save file"), "");
11796             flock(fileno(f), LOCK_EX); // [HGM] lock
11797             DisplayMessage(_("Saving position"), "");
11798             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11799             SavePosition(f, 0, NULL);
11800             DisplayMessage(buf, "");
11801             return TRUE;
11802         }
11803     }
11804 }
11805
11806 /* Save the current position to the given open file and close the file */
11807 int
11808 SavePosition(f, dummy, dummy2)
11809      FILE *f;
11810      int dummy;
11811      char *dummy2;
11812 {
11813     time_t tm;
11814     char *fen;
11815
11816     if (gameMode == EditPosition) EditPositionDone(TRUE);
11817     if (appData.oldSaveStyle) {
11818         tm = time((time_t *) NULL);
11819
11820         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11821         PrintOpponents(f);
11822         fprintf(f, "[--------------\n");
11823         PrintPosition(f, currentMove);
11824         fprintf(f, "--------------]\n");
11825     } else {
11826         fen = PositionToFEN(currentMove, NULL);
11827         fprintf(f, "%s\n", fen);
11828         free(fen);
11829     }
11830     fclose(f);
11831     return TRUE;
11832 }
11833
11834 void
11835 ReloadCmailMsgEvent(unregister)
11836      int unregister;
11837 {
11838 #if !WIN32
11839     static char *inFilename = NULL;
11840     static char *outFilename;
11841     int i;
11842     struct stat inbuf, outbuf;
11843     int status;
11844
11845     /* Any registered moves are unregistered if unregister is set, */
11846     /* i.e. invoked by the signal handler */
11847     if (unregister) {
11848         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11849             cmailMoveRegistered[i] = FALSE;
11850             if (cmailCommentList[i] != NULL) {
11851                 free(cmailCommentList[i]);
11852                 cmailCommentList[i] = NULL;
11853             }
11854         }
11855         nCmailMovesRegistered = 0;
11856     }
11857
11858     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11859         cmailResult[i] = CMAIL_NOT_RESULT;
11860     }
11861     nCmailResults = 0;
11862
11863     if (inFilename == NULL) {
11864         /* Because the filenames are static they only get malloced once  */
11865         /* and they never get freed                                      */
11866         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11867         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11868
11869         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11870         sprintf(outFilename, "%s.out", appData.cmailGameName);
11871     }
11872
11873     status = stat(outFilename, &outbuf);
11874     if (status < 0) {
11875         cmailMailedMove = FALSE;
11876     } else {
11877         status = stat(inFilename, &inbuf);
11878         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11879     }
11880
11881     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11882        counts the games, notes how each one terminated, etc.
11883
11884        It would be nice to remove this kludge and instead gather all
11885        the information while building the game list.  (And to keep it
11886        in the game list nodes instead of having a bunch of fixed-size
11887        parallel arrays.)  Note this will require getting each game's
11888        termination from the PGN tags, as the game list builder does
11889        not process the game moves.  --mann
11890        */
11891     cmailMsgLoaded = TRUE;
11892     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11893
11894     /* Load first game in the file or popup game menu */
11895     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11896
11897 #endif /* !WIN32 */
11898     return;
11899 }
11900
11901 int
11902 RegisterMove()
11903 {
11904     FILE *f;
11905     char string[MSG_SIZ];
11906
11907     if (   cmailMailedMove
11908         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11909         return TRUE;            /* Allow free viewing  */
11910     }
11911
11912     /* Unregister move to ensure that we don't leave RegisterMove        */
11913     /* with the move registered when the conditions for registering no   */
11914     /* longer hold                                                       */
11915     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11916         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11917         nCmailMovesRegistered --;
11918
11919         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11920           {
11921               free(cmailCommentList[lastLoadGameNumber - 1]);
11922               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11923           }
11924     }
11925
11926     if (cmailOldMove == -1) {
11927         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11928         return FALSE;
11929     }
11930
11931     if (currentMove > cmailOldMove + 1) {
11932         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11933         return FALSE;
11934     }
11935
11936     if (currentMove < cmailOldMove) {
11937         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11938         return FALSE;
11939     }
11940
11941     if (forwardMostMove > currentMove) {
11942         /* Silently truncate extra moves */
11943         TruncateGame();
11944     }
11945
11946     if (   (currentMove == cmailOldMove + 1)
11947         || (   (currentMove == cmailOldMove)
11948             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11949                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11950         if (gameInfo.result != GameUnfinished) {
11951             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11952         }
11953
11954         if (commentList[currentMove] != NULL) {
11955             cmailCommentList[lastLoadGameNumber - 1]
11956               = StrSave(commentList[currentMove]);
11957         }
11958         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11959
11960         if (appData.debugMode)
11961           fprintf(debugFP, "Saving %s for game %d\n",
11962                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11963
11964         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11965
11966         f = fopen(string, "w");
11967         if (appData.oldSaveStyle) {
11968             SaveGameOldStyle(f); /* also closes the file */
11969
11970             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11971             f = fopen(string, "w");
11972             SavePosition(f, 0, NULL); /* also closes the file */
11973         } else {
11974             fprintf(f, "{--------------\n");
11975             PrintPosition(f, currentMove);
11976             fprintf(f, "--------------}\n\n");
11977
11978             SaveGame(f, 0, NULL); /* also closes the file*/
11979         }
11980
11981         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11982         nCmailMovesRegistered ++;
11983     } else if (nCmailGames == 1) {
11984         DisplayError(_("You have not made a move yet"), 0);
11985         return FALSE;
11986     }
11987
11988     return TRUE;
11989 }
11990
11991 void
11992 MailMoveEvent()
11993 {
11994 #if !WIN32
11995     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11996     FILE *commandOutput;
11997     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11998     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11999     int nBuffers;
12000     int i;
12001     int archived;
12002     char *arcDir;
12003
12004     if (! cmailMsgLoaded) {
12005         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12006         return;
12007     }
12008
12009     if (nCmailGames == nCmailResults) {
12010         DisplayError(_("No unfinished games"), 0);
12011         return;
12012     }
12013
12014 #if CMAIL_PROHIBIT_REMAIL
12015     if (cmailMailedMove) {
12016       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);
12017         DisplayError(msg, 0);
12018         return;
12019     }
12020 #endif
12021
12022     if (! (cmailMailedMove || RegisterMove())) return;
12023
12024     if (   cmailMailedMove
12025         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12026       snprintf(string, MSG_SIZ, partCommandString,
12027                appData.debugMode ? " -v" : "", appData.cmailGameName);
12028         commandOutput = popen(string, "r");
12029
12030         if (commandOutput == NULL) {
12031             DisplayError(_("Failed to invoke cmail"), 0);
12032         } else {
12033             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12034                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12035             }
12036             if (nBuffers > 1) {
12037                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12038                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12039                 nBytes = MSG_SIZ - 1;
12040             } else {
12041                 (void) memcpy(msg, buffer, nBytes);
12042             }
12043             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12044
12045             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12046                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12047
12048                 archived = TRUE;
12049                 for (i = 0; i < nCmailGames; i ++) {
12050                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12051                         archived = FALSE;
12052                     }
12053                 }
12054                 if (   archived
12055                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12056                         != NULL)) {
12057                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12058                            arcDir,
12059                            appData.cmailGameName,
12060                            gameInfo.date);
12061                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12062                     cmailMsgLoaded = FALSE;
12063                 }
12064             }
12065
12066             DisplayInformation(msg);
12067             pclose(commandOutput);
12068         }
12069     } else {
12070         if ((*cmailMsg) != '\0') {
12071             DisplayInformation(cmailMsg);
12072         }
12073     }
12074
12075     return;
12076 #endif /* !WIN32 */
12077 }
12078
12079 char *
12080 CmailMsg()
12081 {
12082 #if WIN32
12083     return NULL;
12084 #else
12085     int  prependComma = 0;
12086     char number[5];
12087     char string[MSG_SIZ];       /* Space for game-list */
12088     int  i;
12089
12090     if (!cmailMsgLoaded) return "";
12091
12092     if (cmailMailedMove) {
12093       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12094     } else {
12095         /* Create a list of games left */
12096       snprintf(string, MSG_SIZ, "[");
12097         for (i = 0; i < nCmailGames; i ++) {
12098             if (! (   cmailMoveRegistered[i]
12099                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12100                 if (prependComma) {
12101                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12102                 } else {
12103                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12104                     prependComma = 1;
12105                 }
12106
12107                 strcat(string, number);
12108             }
12109         }
12110         strcat(string, "]");
12111
12112         if (nCmailMovesRegistered + nCmailResults == 0) {
12113             switch (nCmailGames) {
12114               case 1:
12115                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12116                 break;
12117
12118               case 2:
12119                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12120                 break;
12121
12122               default:
12123                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12124                          nCmailGames);
12125                 break;
12126             }
12127         } else {
12128             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12129               case 1:
12130                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12131                          string);
12132                 break;
12133
12134               case 0:
12135                 if (nCmailResults == nCmailGames) {
12136                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12137                 } else {
12138                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12139                 }
12140                 break;
12141
12142               default:
12143                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12144                          string);
12145             }
12146         }
12147     }
12148     return cmailMsg;
12149 #endif /* WIN32 */
12150 }
12151
12152 void
12153 ResetGameEvent()
12154 {
12155     if (gameMode == Training)
12156       SetTrainingModeOff();
12157
12158     Reset(TRUE, TRUE);
12159     cmailMsgLoaded = FALSE;
12160     if (appData.icsActive) {
12161       SendToICS(ics_prefix);
12162       SendToICS("refresh\n");
12163     }
12164 }
12165
12166 void
12167 ExitEvent(status)
12168      int status;
12169 {
12170     exiting++;
12171     if (exiting > 2) {
12172       /* Give up on clean exit */
12173       exit(status);
12174     }
12175     if (exiting > 1) {
12176       /* Keep trying for clean exit */
12177       return;
12178     }
12179
12180     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12181
12182     if (telnetISR != NULL) {
12183       RemoveInputSource(telnetISR);
12184     }
12185     if (icsPR != NoProc) {
12186       DestroyChildProcess(icsPR, TRUE);
12187     }
12188
12189     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12190     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12191
12192     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12193     /* make sure this other one finishes before killing it!                  */
12194     if(endingGame) { int count = 0;
12195         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12196         while(endingGame && count++ < 10) DoSleep(1);
12197         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12198     }
12199
12200     /* Kill off chess programs */
12201     if (first.pr != NoProc) {
12202         ExitAnalyzeMode();
12203
12204         DoSleep( appData.delayBeforeQuit );
12205         SendToProgram("quit\n", &first);
12206         DoSleep( appData.delayAfterQuit );
12207         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12208     }
12209     if (second.pr != NoProc) {
12210         DoSleep( appData.delayBeforeQuit );
12211         SendToProgram("quit\n", &second);
12212         DoSleep( appData.delayAfterQuit );
12213         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12214     }
12215     if (first.isr != NULL) {
12216         RemoveInputSource(first.isr);
12217     }
12218     if (second.isr != NULL) {
12219         RemoveInputSource(second.isr);
12220     }
12221
12222     ShutDownFrontEnd();
12223     exit(status);
12224 }
12225
12226 void
12227 PauseEvent()
12228 {
12229     if (appData.debugMode)
12230         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12231     if (pausing) {
12232         pausing = FALSE;
12233         ModeHighlight();
12234         if (gameMode == MachinePlaysWhite ||
12235             gameMode == MachinePlaysBlack) {
12236             StartClocks();
12237         } else {
12238             DisplayBothClocks();
12239         }
12240         if (gameMode == PlayFromGameFile) {
12241             if (appData.timeDelay >= 0)
12242                 AutoPlayGameLoop();
12243         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12244             Reset(FALSE, TRUE);
12245             SendToICS(ics_prefix);
12246             SendToICS("refresh\n");
12247         } else if (currentMove < forwardMostMove) {
12248             ForwardInner(forwardMostMove);
12249         }
12250         pauseExamInvalid = FALSE;
12251     } else {
12252         switch (gameMode) {
12253           default:
12254             return;
12255           case IcsExamining:
12256             pauseExamForwardMostMove = forwardMostMove;
12257             pauseExamInvalid = FALSE;
12258             /* fall through */
12259           case IcsObserving:
12260           case IcsPlayingWhite:
12261           case IcsPlayingBlack:
12262             pausing = TRUE;
12263             ModeHighlight();
12264             return;
12265           case PlayFromGameFile:
12266             (void) StopLoadGameTimer();
12267             pausing = TRUE;
12268             ModeHighlight();
12269             break;
12270           case BeginningOfGame:
12271             if (appData.icsActive) return;
12272             /* else fall through */
12273           case MachinePlaysWhite:
12274           case MachinePlaysBlack:
12275           case TwoMachinesPlay:
12276             if (forwardMostMove == 0)
12277               return;           /* don't pause if no one has moved */
12278             if ((gameMode == MachinePlaysWhite &&
12279                  !WhiteOnMove(forwardMostMove)) ||
12280                 (gameMode == MachinePlaysBlack &&
12281                  WhiteOnMove(forwardMostMove))) {
12282                 StopClocks();
12283             }
12284             pausing = TRUE;
12285             ModeHighlight();
12286             break;
12287         }
12288     }
12289 }
12290
12291 void
12292 EditCommentEvent()
12293 {
12294     char title[MSG_SIZ];
12295
12296     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12297       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12298     } else {
12299       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12300                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12301                parseList[currentMove - 1]);
12302     }
12303
12304     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12305 }
12306
12307
12308 void
12309 EditTagsEvent()
12310 {
12311     char *tags = PGNTags(&gameInfo);
12312     EditTagsPopUp(tags, NULL);
12313     free(tags);
12314 }
12315
12316 void
12317 AnalyzeModeEvent()
12318 {
12319     if (appData.noChessProgram || gameMode == AnalyzeMode)
12320       return;
12321
12322     if (gameMode != AnalyzeFile) {
12323         if (!appData.icsEngineAnalyze) {
12324                EditGameEvent();
12325                if (gameMode != EditGame) return;
12326         }
12327         ResurrectChessProgram();
12328         SendToProgram("analyze\n", &first);
12329         first.analyzing = TRUE;
12330         /*first.maybeThinking = TRUE;*/
12331         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12332         EngineOutputPopUp();
12333     }
12334     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12335     pausing = FALSE;
12336     ModeHighlight();
12337     SetGameInfo();
12338
12339     StartAnalysisClock();
12340     GetTimeMark(&lastNodeCountTime);
12341     lastNodeCount = 0;
12342 }
12343
12344 void
12345 AnalyzeFileEvent()
12346 {
12347     if (appData.noChessProgram || gameMode == AnalyzeFile)
12348       return;
12349
12350     if (gameMode != AnalyzeMode) {
12351         EditGameEvent();
12352         if (gameMode != EditGame) return;
12353         ResurrectChessProgram();
12354         SendToProgram("analyze\n", &first);
12355         first.analyzing = TRUE;
12356         /*first.maybeThinking = TRUE;*/
12357         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12358         EngineOutputPopUp();
12359     }
12360     gameMode = AnalyzeFile;
12361     pausing = FALSE;
12362     ModeHighlight();
12363     SetGameInfo();
12364
12365     StartAnalysisClock();
12366     GetTimeMark(&lastNodeCountTime);
12367     lastNodeCount = 0;
12368 }
12369
12370 void
12371 MachineWhiteEvent()
12372 {
12373     char buf[MSG_SIZ];
12374     char *bookHit = NULL;
12375
12376     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12377       return;
12378
12379
12380     if (gameMode == PlayFromGameFile ||
12381         gameMode == TwoMachinesPlay  ||
12382         gameMode == Training         ||
12383         gameMode == AnalyzeMode      ||
12384         gameMode == EndOfGame)
12385         EditGameEvent();
12386
12387     if (gameMode == EditPosition)
12388         EditPositionDone(TRUE);
12389
12390     if (!WhiteOnMove(currentMove)) {
12391         DisplayError(_("It is not White's turn"), 0);
12392         return;
12393     }
12394
12395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12396       ExitAnalyzeMode();
12397
12398     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12399         gameMode == AnalyzeFile)
12400         TruncateGame();
12401
12402     ResurrectChessProgram();    /* in case it isn't running */
12403     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12404         gameMode = MachinePlaysWhite;
12405         ResetClocks();
12406     } else
12407     gameMode = MachinePlaysWhite;
12408     pausing = FALSE;
12409     ModeHighlight();
12410     SetGameInfo();
12411     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12412     DisplayTitle(buf);
12413     if (first.sendName) {
12414       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12415       SendToProgram(buf, &first);
12416     }
12417     if (first.sendTime) {
12418       if (first.useColors) {
12419         SendToProgram("black\n", &first); /*gnu kludge*/
12420       }
12421       SendTimeRemaining(&first, TRUE);
12422     }
12423     if (first.useColors) {
12424       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12425     }
12426     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12427     SetMachineThinkingEnables();
12428     first.maybeThinking = TRUE;
12429     StartClocks();
12430     firstMove = FALSE;
12431
12432     if (appData.autoFlipView && !flipView) {
12433       flipView = !flipView;
12434       DrawPosition(FALSE, NULL);
12435       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12436     }
12437
12438     if(bookHit) { // [HGM] book: simulate book reply
12439         static char bookMove[MSG_SIZ]; // a bit generous?
12440
12441         programStats.nodes = programStats.depth = programStats.time =
12442         programStats.score = programStats.got_only_move = 0;
12443         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12444
12445         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12446         strcat(bookMove, bookHit);
12447         HandleMachineMove(bookMove, &first);
12448     }
12449 }
12450
12451 void
12452 MachineBlackEvent()
12453 {
12454   char buf[MSG_SIZ];
12455   char *bookHit = NULL;
12456
12457     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12458         return;
12459
12460
12461     if (gameMode == PlayFromGameFile ||
12462         gameMode == TwoMachinesPlay  ||
12463         gameMode == Training         ||
12464         gameMode == AnalyzeMode      ||
12465         gameMode == EndOfGame)
12466         EditGameEvent();
12467
12468     if (gameMode == EditPosition)
12469         EditPositionDone(TRUE);
12470
12471     if (WhiteOnMove(currentMove)) {
12472         DisplayError(_("It is not Black's turn"), 0);
12473         return;
12474     }
12475
12476     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12477       ExitAnalyzeMode();
12478
12479     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12480         gameMode == AnalyzeFile)
12481         TruncateGame();
12482
12483     ResurrectChessProgram();    /* in case it isn't running */
12484     gameMode = MachinePlaysBlack;
12485     pausing = FALSE;
12486     ModeHighlight();
12487     SetGameInfo();
12488     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12489     DisplayTitle(buf);
12490     if (first.sendName) {
12491       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12492       SendToProgram(buf, &first);
12493     }
12494     if (first.sendTime) {
12495       if (first.useColors) {
12496         SendToProgram("white\n", &first); /*gnu kludge*/
12497       }
12498       SendTimeRemaining(&first, FALSE);
12499     }
12500     if (first.useColors) {
12501       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12502     }
12503     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12504     SetMachineThinkingEnables();
12505     first.maybeThinking = TRUE;
12506     StartClocks();
12507
12508     if (appData.autoFlipView && flipView) {
12509       flipView = !flipView;
12510       DrawPosition(FALSE, NULL);
12511       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12512     }
12513     if(bookHit) { // [HGM] book: simulate book reply
12514         static char bookMove[MSG_SIZ]; // a bit generous?
12515
12516         programStats.nodes = programStats.depth = programStats.time =
12517         programStats.score = programStats.got_only_move = 0;
12518         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12519
12520         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12521         strcat(bookMove, bookHit);
12522         HandleMachineMove(bookMove, &first);
12523     }
12524 }
12525
12526
12527 void
12528 DisplayTwoMachinesTitle()
12529 {
12530     char buf[MSG_SIZ];
12531     if (appData.matchGames > 0) {
12532         if (first.twoMachinesColor[0] == 'w') {
12533           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12534                    gameInfo.white, gameInfo.black,
12535                    first.matchWins, second.matchWins,
12536                    matchGame - 1 - (first.matchWins + second.matchWins));
12537         } else {
12538           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12539                    gameInfo.white, gameInfo.black,
12540                    second.matchWins, first.matchWins,
12541                    matchGame - 1 - (first.matchWins + second.matchWins));
12542         }
12543     } else {
12544       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12545     }
12546     DisplayTitle(buf);
12547 }
12548
12549 void
12550 SettingsMenuIfReady()
12551 {
12552   if (second.lastPing != second.lastPong) {
12553     DisplayMessage("", _("Waiting for second chess program"));
12554     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12555     return;
12556   }
12557   ThawUI();
12558   DisplayMessage("", "");
12559   SettingsPopUp(&second);
12560 }
12561
12562 int
12563 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12564 {
12565     char buf[MSG_SIZ];
12566     if (cps->pr == NULL) {
12567         StartChessProgram(cps);
12568         if (cps->protocolVersion == 1) {
12569           retry();
12570         } else {
12571           /* kludge: allow timeout for initial "feature" command */
12572           FreezeUI();
12573           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12574           DisplayMessage("", buf);
12575           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12576         }
12577         return 1;
12578     }
12579     return 0;
12580 }
12581
12582 void
12583 TwoMachinesEvent P((void))
12584 {
12585     int i;
12586     char buf[MSG_SIZ];
12587     ChessProgramState *onmove;
12588     char *bookHit = NULL;
12589     static int stalling = 0;
12590     TimeMark now;
12591     long wait;
12592
12593     if (appData.noChessProgram) return;
12594
12595     switch (gameMode) {
12596       case TwoMachinesPlay:
12597         return;
12598       case MachinePlaysWhite:
12599       case MachinePlaysBlack:
12600         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12601             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12602             return;
12603         }
12604         /* fall through */
12605       case BeginningOfGame:
12606       case PlayFromGameFile:
12607       case EndOfGame:
12608         EditGameEvent();
12609         if (gameMode != EditGame) return;
12610         break;
12611       case EditPosition:
12612         EditPositionDone(TRUE);
12613         break;
12614       case AnalyzeMode:
12615       case AnalyzeFile:
12616         ExitAnalyzeMode();
12617         break;
12618       case EditGame:
12619       default:
12620         break;
12621     }
12622
12623 //    forwardMostMove = currentMove;
12624     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12625
12626     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12627
12628     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12629     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12630       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12631       return;
12632     }
12633     if(!stalling) {
12634       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12635       SendToProgram("force\n", &second);
12636       stalling = 1;
12637       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12638       return;
12639     }
12640     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12641     if(appData.matchPause>10000 || appData.matchPause<10)
12642                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12643     wait = SubtractTimeMarks(&now, &pauseStart);
12644     if(wait < appData.matchPause) {
12645         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12646         return;
12647     }
12648     stalling = 0;
12649     DisplayMessage("", "");
12650     if (startedFromSetupPosition) {
12651         SendBoard(&second, backwardMostMove);
12652     if (appData.debugMode) {
12653         fprintf(debugFP, "Two Machines\n");
12654     }
12655     }
12656     for (i = backwardMostMove; i < forwardMostMove; i++) {
12657         SendMoveToProgram(i, &second);
12658     }
12659
12660     gameMode = TwoMachinesPlay;
12661     pausing = FALSE;
12662     ModeHighlight();
12663     SetGameInfo();
12664     DisplayTwoMachinesTitle();
12665     firstMove = TRUE;
12666     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12667         onmove = &first;
12668     } else {
12669         onmove = &second;
12670     }
12671     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12672     SendToProgram(first.computerString, &first);
12673     if (first.sendName) {
12674       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12675       SendToProgram(buf, &first);
12676     }
12677     SendToProgram(second.computerString, &second);
12678     if (second.sendName) {
12679       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12680       SendToProgram(buf, &second);
12681     }
12682
12683     ResetClocks();
12684     if (!first.sendTime || !second.sendTime) {
12685         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12686         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12687     }
12688     if (onmove->sendTime) {
12689       if (onmove->useColors) {
12690         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12691       }
12692       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12693     }
12694     if (onmove->useColors) {
12695       SendToProgram(onmove->twoMachinesColor, onmove);
12696     }
12697     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12698 //    SendToProgram("go\n", onmove);
12699     onmove->maybeThinking = TRUE;
12700     SetMachineThinkingEnables();
12701
12702     StartClocks();
12703
12704     if(bookHit) { // [HGM] book: simulate book reply
12705         static char bookMove[MSG_SIZ]; // a bit generous?
12706
12707         programStats.nodes = programStats.depth = programStats.time =
12708         programStats.score = programStats.got_only_move = 0;
12709         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12710
12711         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12712         strcat(bookMove, bookHit);
12713         savedMessage = bookMove; // args for deferred call
12714         savedState = onmove;
12715         ScheduleDelayedEvent(DeferredBookMove, 1);
12716     }
12717 }
12718
12719 void
12720 TrainingEvent()
12721 {
12722     if (gameMode == Training) {
12723       SetTrainingModeOff();
12724       gameMode = PlayFromGameFile;
12725       DisplayMessage("", _("Training mode off"));
12726     } else {
12727       gameMode = Training;
12728       animateTraining = appData.animate;
12729
12730       /* make sure we are not already at the end of the game */
12731       if (currentMove < forwardMostMove) {
12732         SetTrainingModeOn();
12733         DisplayMessage("", _("Training mode on"));
12734       } else {
12735         gameMode = PlayFromGameFile;
12736         DisplayError(_("Already at end of game"), 0);
12737       }
12738     }
12739     ModeHighlight();
12740 }
12741
12742 void
12743 IcsClientEvent()
12744 {
12745     if (!appData.icsActive) return;
12746     switch (gameMode) {
12747       case IcsPlayingWhite:
12748       case IcsPlayingBlack:
12749       case IcsObserving:
12750       case IcsIdle:
12751       case BeginningOfGame:
12752       case IcsExamining:
12753         return;
12754
12755       case EditGame:
12756         break;
12757
12758       case EditPosition:
12759         EditPositionDone(TRUE);
12760         break;
12761
12762       case AnalyzeMode:
12763       case AnalyzeFile:
12764         ExitAnalyzeMode();
12765         break;
12766
12767       default:
12768         EditGameEvent();
12769         break;
12770     }
12771
12772     gameMode = IcsIdle;
12773     ModeHighlight();
12774     return;
12775 }
12776
12777
12778 void
12779 EditGameEvent()
12780 {
12781     int i;
12782
12783     switch (gameMode) {
12784       case Training:
12785         SetTrainingModeOff();
12786         break;
12787       case MachinePlaysWhite:
12788       case MachinePlaysBlack:
12789       case BeginningOfGame:
12790         SendToProgram("force\n", &first);
12791         SetUserThinkingEnables();
12792         break;
12793       case PlayFromGameFile:
12794         (void) StopLoadGameTimer();
12795         if (gameFileFP != NULL) {
12796             gameFileFP = NULL;
12797         }
12798         break;
12799       case EditPosition:
12800         EditPositionDone(TRUE);
12801         break;
12802       case AnalyzeMode:
12803       case AnalyzeFile:
12804         ExitAnalyzeMode();
12805         SendToProgram("force\n", &first);
12806         break;
12807       case TwoMachinesPlay:
12808         GameEnds(EndOfFile, NULL, GE_PLAYER);
12809         ResurrectChessProgram();
12810         SetUserThinkingEnables();
12811         break;
12812       case EndOfGame:
12813         ResurrectChessProgram();
12814         break;
12815       case IcsPlayingBlack:
12816       case IcsPlayingWhite:
12817         DisplayError(_("Warning: You are still playing a game"), 0);
12818         break;
12819       case IcsObserving:
12820         DisplayError(_("Warning: You are still observing a game"), 0);
12821         break;
12822       case IcsExamining:
12823         DisplayError(_("Warning: You are still examining a game"), 0);
12824         break;
12825       case IcsIdle:
12826         break;
12827       case EditGame:
12828       default:
12829         return;
12830     }
12831
12832     pausing = FALSE;
12833     StopClocks();
12834     first.offeredDraw = second.offeredDraw = 0;
12835
12836     if (gameMode == PlayFromGameFile) {
12837         whiteTimeRemaining = timeRemaining[0][currentMove];
12838         blackTimeRemaining = timeRemaining[1][currentMove];
12839         DisplayTitle("");
12840     }
12841
12842     if (gameMode == MachinePlaysWhite ||
12843         gameMode == MachinePlaysBlack ||
12844         gameMode == TwoMachinesPlay ||
12845         gameMode == EndOfGame) {
12846         i = forwardMostMove;
12847         while (i > currentMove) {
12848             SendToProgram("undo\n", &first);
12849             i--;
12850         }
12851         whiteTimeRemaining = timeRemaining[0][currentMove];
12852         blackTimeRemaining = timeRemaining[1][currentMove];
12853         DisplayBothClocks();
12854         if (whiteFlag || blackFlag) {
12855             whiteFlag = blackFlag = 0;
12856         }
12857         DisplayTitle("");
12858     }
12859
12860     gameMode = EditGame;
12861     ModeHighlight();
12862     SetGameInfo();
12863 }
12864
12865
12866 void
12867 EditPositionEvent()
12868 {
12869     if (gameMode == EditPosition) {
12870         EditGameEvent();
12871         return;
12872     }
12873
12874     EditGameEvent();
12875     if (gameMode != EditGame) return;
12876
12877     gameMode = EditPosition;
12878     ModeHighlight();
12879     SetGameInfo();
12880     if (currentMove > 0)
12881       CopyBoard(boards[0], boards[currentMove]);
12882
12883     blackPlaysFirst = !WhiteOnMove(currentMove);
12884     ResetClocks();
12885     currentMove = forwardMostMove = backwardMostMove = 0;
12886     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12887     DisplayMove(-1);
12888 }
12889
12890 void
12891 ExitAnalyzeMode()
12892 {
12893     /* [DM] icsEngineAnalyze - possible call from other functions */
12894     if (appData.icsEngineAnalyze) {
12895         appData.icsEngineAnalyze = FALSE;
12896
12897         DisplayMessage("",_("Close ICS engine analyze..."));
12898     }
12899     if (first.analysisSupport && first.analyzing) {
12900       SendToProgram("exit\n", &first);
12901       first.analyzing = FALSE;
12902     }
12903     thinkOutput[0] = NULLCHAR;
12904 }
12905
12906 void
12907 EditPositionDone(Boolean fakeRights)
12908 {
12909     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12910
12911     startedFromSetupPosition = TRUE;
12912     InitChessProgram(&first, FALSE);
12913     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12914       boards[0][EP_STATUS] = EP_NONE;
12915       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12916     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12917         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12918         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12919       } else boards[0][CASTLING][2] = NoRights;
12920     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12921         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12922         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12923       } else boards[0][CASTLING][5] = NoRights;
12924     }
12925     SendToProgram("force\n", &first);
12926     if (blackPlaysFirst) {
12927         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12928         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12929         currentMove = forwardMostMove = backwardMostMove = 1;
12930         CopyBoard(boards[1], boards[0]);
12931     } else {
12932         currentMove = forwardMostMove = backwardMostMove = 0;
12933     }
12934     SendBoard(&first, forwardMostMove);
12935     if (appData.debugMode) {
12936         fprintf(debugFP, "EditPosDone\n");
12937     }
12938     DisplayTitle("");
12939     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12940     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12941     gameMode = EditGame;
12942     ModeHighlight();
12943     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12944     ClearHighlights(); /* [AS] */
12945 }
12946
12947 /* Pause for `ms' milliseconds */
12948 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12949 void
12950 TimeDelay(ms)
12951      long ms;
12952 {
12953     TimeMark m1, m2;
12954
12955     GetTimeMark(&m1);
12956     do {
12957         GetTimeMark(&m2);
12958     } while (SubtractTimeMarks(&m2, &m1) < ms);
12959 }
12960
12961 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12962 void
12963 SendMultiLineToICS(buf)
12964      char *buf;
12965 {
12966     char temp[MSG_SIZ+1], *p;
12967     int len;
12968
12969     len = strlen(buf);
12970     if (len > MSG_SIZ)
12971       len = MSG_SIZ;
12972
12973     strncpy(temp, buf, len);
12974     temp[len] = 0;
12975
12976     p = temp;
12977     while (*p) {
12978         if (*p == '\n' || *p == '\r')
12979           *p = ' ';
12980         ++p;
12981     }
12982
12983     strcat(temp, "\n");
12984     SendToICS(temp);
12985     SendToPlayer(temp, strlen(temp));
12986 }
12987
12988 void
12989 SetWhiteToPlayEvent()
12990 {
12991     if (gameMode == EditPosition) {
12992         blackPlaysFirst = FALSE;
12993         DisplayBothClocks();    /* works because currentMove is 0 */
12994     } else if (gameMode == IcsExamining) {
12995         SendToICS(ics_prefix);
12996         SendToICS("tomove white\n");
12997     }
12998 }
12999
13000 void
13001 SetBlackToPlayEvent()
13002 {
13003     if (gameMode == EditPosition) {
13004         blackPlaysFirst = TRUE;
13005         currentMove = 1;        /* kludge */
13006         DisplayBothClocks();
13007         currentMove = 0;
13008     } else if (gameMode == IcsExamining) {
13009         SendToICS(ics_prefix);
13010         SendToICS("tomove black\n");
13011     }
13012 }
13013
13014 void
13015 EditPositionMenuEvent(selection, x, y)
13016      ChessSquare selection;
13017      int x, y;
13018 {
13019     char buf[MSG_SIZ];
13020     ChessSquare piece = boards[0][y][x];
13021
13022     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13023
13024     switch (selection) {
13025       case ClearBoard:
13026         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13027             SendToICS(ics_prefix);
13028             SendToICS("bsetup clear\n");
13029         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13030             SendToICS(ics_prefix);
13031             SendToICS("clearboard\n");
13032         } else {
13033             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13034                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13035                 for (y = 0; y < BOARD_HEIGHT; y++) {
13036                     if (gameMode == IcsExamining) {
13037                         if (boards[currentMove][y][x] != EmptySquare) {
13038                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13039                                     AAA + x, ONE + y);
13040                             SendToICS(buf);
13041                         }
13042                     } else {
13043                         boards[0][y][x] = p;
13044                     }
13045                 }
13046             }
13047         }
13048         if (gameMode == EditPosition) {
13049             DrawPosition(FALSE, boards[0]);
13050         }
13051         break;
13052
13053       case WhitePlay:
13054         SetWhiteToPlayEvent();
13055         break;
13056
13057       case BlackPlay:
13058         SetBlackToPlayEvent();
13059         break;
13060
13061       case EmptySquare:
13062         if (gameMode == IcsExamining) {
13063             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13064             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13065             SendToICS(buf);
13066         } else {
13067             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13068                 if(x == BOARD_LEFT-2) {
13069                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13070                     boards[0][y][1] = 0;
13071                 } else
13072                 if(x == BOARD_RGHT+1) {
13073                     if(y >= gameInfo.holdingsSize) break;
13074                     boards[0][y][BOARD_WIDTH-2] = 0;
13075                 } else break;
13076             }
13077             boards[0][y][x] = EmptySquare;
13078             DrawPosition(FALSE, boards[0]);
13079         }
13080         break;
13081
13082       case PromotePiece:
13083         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13084            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13085             selection = (ChessSquare) (PROMOTED piece);
13086         } else if(piece == EmptySquare) selection = WhiteSilver;
13087         else selection = (ChessSquare)((int)piece - 1);
13088         goto defaultlabel;
13089
13090       case DemotePiece:
13091         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13092            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13093             selection = (ChessSquare) (DEMOTED piece);
13094         } else if(piece == EmptySquare) selection = BlackSilver;
13095         else selection = (ChessSquare)((int)piece + 1);
13096         goto defaultlabel;
13097
13098       case WhiteQueen:
13099       case BlackQueen:
13100         if(gameInfo.variant == VariantShatranj ||
13101            gameInfo.variant == VariantXiangqi  ||
13102            gameInfo.variant == VariantCourier  ||
13103            gameInfo.variant == VariantMakruk     )
13104             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13105         goto defaultlabel;
13106
13107       case WhiteKing:
13108       case BlackKing:
13109         if(gameInfo.variant == VariantXiangqi)
13110             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13111         if(gameInfo.variant == VariantKnightmate)
13112             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13113       default:
13114         defaultlabel:
13115         if (gameMode == IcsExamining) {
13116             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13117             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13118                      PieceToChar(selection), AAA + x, ONE + y);
13119             SendToICS(buf);
13120         } else {
13121             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13122                 int n;
13123                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13124                     n = PieceToNumber(selection - BlackPawn);
13125                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13126                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13127                     boards[0][BOARD_HEIGHT-1-n][1]++;
13128                 } else
13129                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13130                     n = PieceToNumber(selection);
13131                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13132                     boards[0][n][BOARD_WIDTH-1] = selection;
13133                     boards[0][n][BOARD_WIDTH-2]++;
13134                 }
13135             } else
13136             boards[0][y][x] = selection;
13137             DrawPosition(TRUE, boards[0]);
13138         }
13139         break;
13140     }
13141 }
13142
13143
13144 void
13145 DropMenuEvent(selection, x, y)
13146      ChessSquare selection;
13147      int x, y;
13148 {
13149     ChessMove moveType;
13150
13151     switch (gameMode) {
13152       case IcsPlayingWhite:
13153       case MachinePlaysBlack:
13154         if (!WhiteOnMove(currentMove)) {
13155             DisplayMoveError(_("It is Black's turn"));
13156             return;
13157         }
13158         moveType = WhiteDrop;
13159         break;
13160       case IcsPlayingBlack:
13161       case MachinePlaysWhite:
13162         if (WhiteOnMove(currentMove)) {
13163             DisplayMoveError(_("It is White's turn"));
13164             return;
13165         }
13166         moveType = BlackDrop;
13167         break;
13168       case EditGame:
13169         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13170         break;
13171       default:
13172         return;
13173     }
13174
13175     if (moveType == BlackDrop && selection < BlackPawn) {
13176       selection = (ChessSquare) ((int) selection
13177                                  + (int) BlackPawn - (int) WhitePawn);
13178     }
13179     if (boards[currentMove][y][x] != EmptySquare) {
13180         DisplayMoveError(_("That square is occupied"));
13181         return;
13182     }
13183
13184     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13185 }
13186
13187 void
13188 AcceptEvent()
13189 {
13190     /* Accept a pending offer of any kind from opponent */
13191
13192     if (appData.icsActive) {
13193         SendToICS(ics_prefix);
13194         SendToICS("accept\n");
13195     } else if (cmailMsgLoaded) {
13196         if (currentMove == cmailOldMove &&
13197             commentList[cmailOldMove] != NULL &&
13198             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13199                    "Black offers a draw" : "White offers a draw")) {
13200             TruncateGame();
13201             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13202             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13203         } else {
13204             DisplayError(_("There is no pending offer on this move"), 0);
13205             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13206         }
13207     } else {
13208         /* Not used for offers from chess program */
13209     }
13210 }
13211
13212 void
13213 DeclineEvent()
13214 {
13215     /* Decline a pending offer of any kind from opponent */
13216
13217     if (appData.icsActive) {
13218         SendToICS(ics_prefix);
13219         SendToICS("decline\n");
13220     } else if (cmailMsgLoaded) {
13221         if (currentMove == cmailOldMove &&
13222             commentList[cmailOldMove] != NULL &&
13223             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13224                    "Black offers a draw" : "White offers a draw")) {
13225 #ifdef NOTDEF
13226             AppendComment(cmailOldMove, "Draw declined", TRUE);
13227             DisplayComment(cmailOldMove - 1, "Draw declined");
13228 #endif /*NOTDEF*/
13229         } else {
13230             DisplayError(_("There is no pending offer on this move"), 0);
13231         }
13232     } else {
13233         /* Not used for offers from chess program */
13234     }
13235 }
13236
13237 void
13238 RematchEvent()
13239 {
13240     /* Issue ICS rematch command */
13241     if (appData.icsActive) {
13242         SendToICS(ics_prefix);
13243         SendToICS("rematch\n");
13244     }
13245 }
13246
13247 void
13248 CallFlagEvent()
13249 {
13250     /* Call your opponent's flag (claim a win on time) */
13251     if (appData.icsActive) {
13252         SendToICS(ics_prefix);
13253         SendToICS("flag\n");
13254     } else {
13255         switch (gameMode) {
13256           default:
13257             return;
13258           case MachinePlaysWhite:
13259             if (whiteFlag) {
13260                 if (blackFlag)
13261                   GameEnds(GameIsDrawn, "Both players ran out of time",
13262                            GE_PLAYER);
13263                 else
13264                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13265             } else {
13266                 DisplayError(_("Your opponent is not out of time"), 0);
13267             }
13268             break;
13269           case MachinePlaysBlack:
13270             if (blackFlag) {
13271                 if (whiteFlag)
13272                   GameEnds(GameIsDrawn, "Both players ran out of time",
13273                            GE_PLAYER);
13274                 else
13275                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13276             } else {
13277                 DisplayError(_("Your opponent is not out of time"), 0);
13278             }
13279             break;
13280         }
13281     }
13282 }
13283
13284 void
13285 ClockClick(int which)
13286 {       // [HGM] code moved to back-end from winboard.c
13287         if(which) { // black clock
13288           if (gameMode == EditPosition || gameMode == IcsExamining) {
13289             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13290             SetBlackToPlayEvent();
13291           } else if (gameMode == EditGame || shiftKey) {
13292             AdjustClock(which, -1);
13293           } else if (gameMode == IcsPlayingWhite ||
13294                      gameMode == MachinePlaysBlack) {
13295             CallFlagEvent();
13296           }
13297         } else { // white clock
13298           if (gameMode == EditPosition || gameMode == IcsExamining) {
13299             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13300             SetWhiteToPlayEvent();
13301           } else if (gameMode == EditGame || shiftKey) {
13302             AdjustClock(which, -1);
13303           } else if (gameMode == IcsPlayingBlack ||
13304                    gameMode == MachinePlaysWhite) {
13305             CallFlagEvent();
13306           }
13307         }
13308 }
13309
13310 void
13311 DrawEvent()
13312 {
13313     /* Offer draw or accept pending draw offer from opponent */
13314
13315     if (appData.icsActive) {
13316         /* Note: tournament rules require draw offers to be
13317            made after you make your move but before you punch
13318            your clock.  Currently ICS doesn't let you do that;
13319            instead, you immediately punch your clock after making
13320            a move, but you can offer a draw at any time. */
13321
13322         SendToICS(ics_prefix);
13323         SendToICS("draw\n");
13324         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13325     } else if (cmailMsgLoaded) {
13326         if (currentMove == cmailOldMove &&
13327             commentList[cmailOldMove] != NULL &&
13328             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13329                    "Black offers a draw" : "White offers a draw")) {
13330             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13331             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13332         } else if (currentMove == cmailOldMove + 1) {
13333             char *offer = WhiteOnMove(cmailOldMove) ?
13334               "White offers a draw" : "Black offers a draw";
13335             AppendComment(currentMove, offer, TRUE);
13336             DisplayComment(currentMove - 1, offer);
13337             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13338         } else {
13339             DisplayError(_("You must make your move before offering a draw"), 0);
13340             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13341         }
13342     } else if (first.offeredDraw) {
13343         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13344     } else {
13345         if (first.sendDrawOffers) {
13346             SendToProgram("draw\n", &first);
13347             userOfferedDraw = TRUE;
13348         }
13349     }
13350 }
13351
13352 void
13353 AdjournEvent()
13354 {
13355     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13356
13357     if (appData.icsActive) {
13358         SendToICS(ics_prefix);
13359         SendToICS("adjourn\n");
13360     } else {
13361         /* Currently GNU Chess doesn't offer or accept Adjourns */
13362     }
13363 }
13364
13365
13366 void
13367 AbortEvent()
13368 {
13369     /* Offer Abort or accept pending Abort offer from opponent */
13370
13371     if (appData.icsActive) {
13372         SendToICS(ics_prefix);
13373         SendToICS("abort\n");
13374     } else {
13375         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13376     }
13377 }
13378
13379 void
13380 ResignEvent()
13381 {
13382     /* Resign.  You can do this even if it's not your turn. */
13383
13384     if (appData.icsActive) {
13385         SendToICS(ics_prefix);
13386         SendToICS("resign\n");
13387     } else {
13388         switch (gameMode) {
13389           case MachinePlaysWhite:
13390             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13391             break;
13392           case MachinePlaysBlack:
13393             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13394             break;
13395           case EditGame:
13396             if (cmailMsgLoaded) {
13397                 TruncateGame();
13398                 if (WhiteOnMove(cmailOldMove)) {
13399                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13400                 } else {
13401                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13402                 }
13403                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13404             }
13405             break;
13406           default:
13407             break;
13408         }
13409     }
13410 }
13411
13412
13413 void
13414 StopObservingEvent()
13415 {
13416     /* Stop observing current games */
13417     SendToICS(ics_prefix);
13418     SendToICS("unobserve\n");
13419 }
13420
13421 void
13422 StopExaminingEvent()
13423 {
13424     /* Stop observing current game */
13425     SendToICS(ics_prefix);
13426     SendToICS("unexamine\n");
13427 }
13428
13429 void
13430 ForwardInner(target)
13431      int target;
13432 {
13433     int limit;
13434
13435     if (appData.debugMode)
13436         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13437                 target, currentMove, forwardMostMove);
13438
13439     if (gameMode == EditPosition)
13440       return;
13441
13442     if (gameMode == PlayFromGameFile && !pausing)
13443       PauseEvent();
13444
13445     if (gameMode == IcsExamining && pausing)
13446       limit = pauseExamForwardMostMove;
13447     else
13448       limit = forwardMostMove;
13449
13450     if (target > limit) target = limit;
13451
13452     if (target > 0 && moveList[target - 1][0]) {
13453         int fromX, fromY, toX, toY;
13454         toX = moveList[target - 1][2] - AAA;
13455         toY = moveList[target - 1][3] - ONE;
13456         if (moveList[target - 1][1] == '@') {
13457             if (appData.highlightLastMove) {
13458                 SetHighlights(-1, -1, toX, toY);
13459             }
13460         } else {
13461             fromX = moveList[target - 1][0] - AAA;
13462             fromY = moveList[target - 1][1] - ONE;
13463             if (target == currentMove + 1) {
13464                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13465             }
13466             if (appData.highlightLastMove) {
13467                 SetHighlights(fromX, fromY, toX, toY);
13468             }
13469         }
13470     }
13471     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13472         gameMode == Training || gameMode == PlayFromGameFile ||
13473         gameMode == AnalyzeFile) {
13474         while (currentMove < target) {
13475             SendMoveToProgram(currentMove++, &first);
13476         }
13477     } else {
13478         currentMove = target;
13479     }
13480
13481     if (gameMode == EditGame || gameMode == EndOfGame) {
13482         whiteTimeRemaining = timeRemaining[0][currentMove];
13483         blackTimeRemaining = timeRemaining[1][currentMove];
13484     }
13485     DisplayBothClocks();
13486     DisplayMove(currentMove - 1);
13487     DrawPosition(FALSE, boards[currentMove]);
13488     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13489     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13490         DisplayComment(currentMove - 1, commentList[currentMove]);
13491     }
13492 }
13493
13494
13495 void
13496 ForwardEvent()
13497 {
13498     if (gameMode == IcsExamining && !pausing) {
13499         SendToICS(ics_prefix);
13500         SendToICS("forward\n");
13501     } else {
13502         ForwardInner(currentMove + 1);
13503     }
13504 }
13505
13506 void
13507 ToEndEvent()
13508 {
13509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13510         /* to optimze, we temporarily turn off analysis mode while we feed
13511          * the remaining moves to the engine. Otherwise we get analysis output
13512          * after each move.
13513          */
13514         if (first.analysisSupport) {
13515           SendToProgram("exit\nforce\n", &first);
13516           first.analyzing = FALSE;
13517         }
13518     }
13519
13520     if (gameMode == IcsExamining && !pausing) {
13521         SendToICS(ics_prefix);
13522         SendToICS("forward 999999\n");
13523     } else {
13524         ForwardInner(forwardMostMove);
13525     }
13526
13527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13528         /* we have fed all the moves, so reactivate analysis mode */
13529         SendToProgram("analyze\n", &first);
13530         first.analyzing = TRUE;
13531         /*first.maybeThinking = TRUE;*/
13532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13533     }
13534 }
13535
13536 void
13537 BackwardInner(target)
13538      int target;
13539 {
13540     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13541
13542     if (appData.debugMode)
13543         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13544                 target, currentMove, forwardMostMove);
13545
13546     if (gameMode == EditPosition) return;
13547     if (currentMove <= backwardMostMove) {
13548         ClearHighlights();
13549         DrawPosition(full_redraw, boards[currentMove]);
13550         return;
13551     }
13552     if (gameMode == PlayFromGameFile && !pausing)
13553       PauseEvent();
13554
13555     if (moveList[target][0]) {
13556         int fromX, fromY, toX, toY;
13557         toX = moveList[target][2] - AAA;
13558         toY = moveList[target][3] - ONE;
13559         if (moveList[target][1] == '@') {
13560             if (appData.highlightLastMove) {
13561                 SetHighlights(-1, -1, toX, toY);
13562             }
13563         } else {
13564             fromX = moveList[target][0] - AAA;
13565             fromY = moveList[target][1] - ONE;
13566             if (target == currentMove - 1) {
13567                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13568             }
13569             if (appData.highlightLastMove) {
13570                 SetHighlights(fromX, fromY, toX, toY);
13571             }
13572         }
13573     }
13574     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13575         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13576         while (currentMove > target) {
13577             SendToProgram("undo\n", &first);
13578             currentMove--;
13579         }
13580     } else {
13581         currentMove = target;
13582     }
13583
13584     if (gameMode == EditGame || gameMode == EndOfGame) {
13585         whiteTimeRemaining = timeRemaining[0][currentMove];
13586         blackTimeRemaining = timeRemaining[1][currentMove];
13587     }
13588     DisplayBothClocks();
13589     DisplayMove(currentMove - 1);
13590     DrawPosition(full_redraw, boards[currentMove]);
13591     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13592     // [HGM] PV info: routine tests if comment empty
13593     DisplayComment(currentMove - 1, commentList[currentMove]);
13594 }
13595
13596 void
13597 BackwardEvent()
13598 {
13599     if (gameMode == IcsExamining && !pausing) {
13600         SendToICS(ics_prefix);
13601         SendToICS("backward\n");
13602     } else {
13603         BackwardInner(currentMove - 1);
13604     }
13605 }
13606
13607 void
13608 ToStartEvent()
13609 {
13610     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13611         /* to optimize, we temporarily turn off analysis mode while we undo
13612          * all the moves. Otherwise we get analysis output after each undo.
13613          */
13614         if (first.analysisSupport) {
13615           SendToProgram("exit\nforce\n", &first);
13616           first.analyzing = FALSE;
13617         }
13618     }
13619
13620     if (gameMode == IcsExamining && !pausing) {
13621         SendToICS(ics_prefix);
13622         SendToICS("backward 999999\n");
13623     } else {
13624         BackwardInner(backwardMostMove);
13625     }
13626
13627     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13628         /* we have fed all the moves, so reactivate analysis mode */
13629         SendToProgram("analyze\n", &first);
13630         first.analyzing = TRUE;
13631         /*first.maybeThinking = TRUE;*/
13632         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13633     }
13634 }
13635
13636 void
13637 ToNrEvent(int to)
13638 {
13639   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13640   if (to >= forwardMostMove) to = forwardMostMove;
13641   if (to <= backwardMostMove) to = backwardMostMove;
13642   if (to < currentMove) {
13643     BackwardInner(to);
13644   } else {
13645     ForwardInner(to);
13646   }
13647 }
13648
13649 void
13650 RevertEvent(Boolean annotate)
13651 {
13652     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13653         return;
13654     }
13655     if (gameMode != IcsExamining) {
13656         DisplayError(_("You are not examining a game"), 0);
13657         return;
13658     }
13659     if (pausing) {
13660         DisplayError(_("You can't revert while pausing"), 0);
13661         return;
13662     }
13663     SendToICS(ics_prefix);
13664     SendToICS("revert\n");
13665 }
13666
13667 void
13668 RetractMoveEvent()
13669 {
13670     switch (gameMode) {
13671       case MachinePlaysWhite:
13672       case MachinePlaysBlack:
13673         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13674             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13675             return;
13676         }
13677         if (forwardMostMove < 2) return;
13678         currentMove = forwardMostMove = forwardMostMove - 2;
13679         whiteTimeRemaining = timeRemaining[0][currentMove];
13680         blackTimeRemaining = timeRemaining[1][currentMove];
13681         DisplayBothClocks();
13682         DisplayMove(currentMove - 1);
13683         ClearHighlights();/*!! could figure this out*/
13684         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13685         SendToProgram("remove\n", &first);
13686         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13687         break;
13688
13689       case BeginningOfGame:
13690       default:
13691         break;
13692
13693       case IcsPlayingWhite:
13694       case IcsPlayingBlack:
13695         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13696             SendToICS(ics_prefix);
13697             SendToICS("takeback 2\n");
13698         } else {
13699             SendToICS(ics_prefix);
13700             SendToICS("takeback 1\n");
13701         }
13702         break;
13703     }
13704 }
13705
13706 void
13707 MoveNowEvent()
13708 {
13709     ChessProgramState *cps;
13710
13711     switch (gameMode) {
13712       case MachinePlaysWhite:
13713         if (!WhiteOnMove(forwardMostMove)) {
13714             DisplayError(_("It is your turn"), 0);
13715             return;
13716         }
13717         cps = &first;
13718         break;
13719       case MachinePlaysBlack:
13720         if (WhiteOnMove(forwardMostMove)) {
13721             DisplayError(_("It is your turn"), 0);
13722             return;
13723         }
13724         cps = &first;
13725         break;
13726       case TwoMachinesPlay:
13727         if (WhiteOnMove(forwardMostMove) ==
13728             (first.twoMachinesColor[0] == 'w')) {
13729             cps = &first;
13730         } else {
13731             cps = &second;
13732         }
13733         break;
13734       case BeginningOfGame:
13735       default:
13736         return;
13737     }
13738     SendToProgram("?\n", cps);
13739 }
13740
13741 void
13742 TruncateGameEvent()
13743 {
13744     EditGameEvent();
13745     if (gameMode != EditGame) return;
13746     TruncateGame();
13747 }
13748
13749 void
13750 TruncateGame()
13751 {
13752     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13753     if (forwardMostMove > currentMove) {
13754         if (gameInfo.resultDetails != NULL) {
13755             free(gameInfo.resultDetails);
13756             gameInfo.resultDetails = NULL;
13757             gameInfo.result = GameUnfinished;
13758         }
13759         forwardMostMove = currentMove;
13760         HistorySet(parseList, backwardMostMove, forwardMostMove,
13761                    currentMove-1);
13762     }
13763 }
13764
13765 void
13766 HintEvent()
13767 {
13768     if (appData.noChessProgram) return;
13769     switch (gameMode) {
13770       case MachinePlaysWhite:
13771         if (WhiteOnMove(forwardMostMove)) {
13772             DisplayError(_("Wait until your turn"), 0);
13773             return;
13774         }
13775         break;
13776       case BeginningOfGame:
13777       case MachinePlaysBlack:
13778         if (!WhiteOnMove(forwardMostMove)) {
13779             DisplayError(_("Wait until your turn"), 0);
13780             return;
13781         }
13782         break;
13783       default:
13784         DisplayError(_("No hint available"), 0);
13785         return;
13786     }
13787     SendToProgram("hint\n", &first);
13788     hintRequested = TRUE;
13789 }
13790
13791 void
13792 BookEvent()
13793 {
13794     if (appData.noChessProgram) return;
13795     switch (gameMode) {
13796       case MachinePlaysWhite:
13797         if (WhiteOnMove(forwardMostMove)) {
13798             DisplayError(_("Wait until your turn"), 0);
13799             return;
13800         }
13801         break;
13802       case BeginningOfGame:
13803       case MachinePlaysBlack:
13804         if (!WhiteOnMove(forwardMostMove)) {
13805             DisplayError(_("Wait until your turn"), 0);
13806             return;
13807         }
13808         break;
13809       case EditPosition:
13810         EditPositionDone(TRUE);
13811         break;
13812       case TwoMachinesPlay:
13813         return;
13814       default:
13815         break;
13816     }
13817     SendToProgram("bk\n", &first);
13818     bookOutput[0] = NULLCHAR;
13819     bookRequested = TRUE;
13820 }
13821
13822 void
13823 AboutGameEvent()
13824 {
13825     char *tags = PGNTags(&gameInfo);
13826     TagsPopUp(tags, CmailMsg());
13827     free(tags);
13828 }
13829
13830 /* end button procedures */
13831
13832 void
13833 PrintPosition(fp, move)
13834      FILE *fp;
13835      int move;
13836 {
13837     int i, j;
13838
13839     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13841             char c = PieceToChar(boards[move][i][j]);
13842             fputc(c == 'x' ? '.' : c, fp);
13843             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13844         }
13845     }
13846     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13847       fprintf(fp, "white to play\n");
13848     else
13849       fprintf(fp, "black to play\n");
13850 }
13851
13852 void
13853 PrintOpponents(fp)
13854      FILE *fp;
13855 {
13856     if (gameInfo.white != NULL) {
13857         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13858     } else {
13859         fprintf(fp, "\n");
13860     }
13861 }
13862
13863 /* Find last component of program's own name, using some heuristics */
13864 void
13865 TidyProgramName(prog, host, buf)
13866      char *prog, *host, buf[MSG_SIZ];
13867 {
13868     char *p, *q;
13869     int local = (strcmp(host, "localhost") == 0);
13870     while (!local && (p = strchr(prog, ';')) != NULL) {
13871         p++;
13872         while (*p == ' ') p++;
13873         prog = p;
13874     }
13875     if (*prog == '"' || *prog == '\'') {
13876         q = strchr(prog + 1, *prog);
13877     } else {
13878         q = strchr(prog, ' ');
13879     }
13880     if (q == NULL) q = prog + strlen(prog);
13881     p = q;
13882     while (p >= prog && *p != '/' && *p != '\\') p--;
13883     p++;
13884     if(p == prog && *p == '"') p++;
13885     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13886     memcpy(buf, p, q - p);
13887     buf[q - p] = NULLCHAR;
13888     if (!local) {
13889         strcat(buf, "@");
13890         strcat(buf, host);
13891     }
13892 }
13893
13894 char *
13895 TimeControlTagValue()
13896 {
13897     char buf[MSG_SIZ];
13898     if (!appData.clockMode) {
13899       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13900     } else if (movesPerSession > 0) {
13901       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13902     } else if (timeIncrement == 0) {
13903       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13904     } else {
13905       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13906     }
13907     return StrSave(buf);
13908 }
13909
13910 void
13911 SetGameInfo()
13912 {
13913     /* This routine is used only for certain modes */
13914     VariantClass v = gameInfo.variant;
13915     ChessMove r = GameUnfinished;
13916     char *p = NULL;
13917
13918     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13919         r = gameInfo.result;
13920         p = gameInfo.resultDetails;
13921         gameInfo.resultDetails = NULL;
13922     }
13923     ClearGameInfo(&gameInfo);
13924     gameInfo.variant = v;
13925
13926     switch (gameMode) {
13927       case MachinePlaysWhite:
13928         gameInfo.event = StrSave( appData.pgnEventHeader );
13929         gameInfo.site = StrSave(HostName());
13930         gameInfo.date = PGNDate();
13931         gameInfo.round = StrSave("-");
13932         gameInfo.white = StrSave(first.tidy);
13933         gameInfo.black = StrSave(UserName());
13934         gameInfo.timeControl = TimeControlTagValue();
13935         break;
13936
13937       case MachinePlaysBlack:
13938         gameInfo.event = StrSave( appData.pgnEventHeader );
13939         gameInfo.site = StrSave(HostName());
13940         gameInfo.date = PGNDate();
13941         gameInfo.round = StrSave("-");
13942         gameInfo.white = StrSave(UserName());
13943         gameInfo.black = StrSave(first.tidy);
13944         gameInfo.timeControl = TimeControlTagValue();
13945         break;
13946
13947       case TwoMachinesPlay:
13948         gameInfo.event = StrSave( appData.pgnEventHeader );
13949         gameInfo.site = StrSave(HostName());
13950         gameInfo.date = PGNDate();
13951         if (roundNr > 0) {
13952             char buf[MSG_SIZ];
13953             snprintf(buf, MSG_SIZ, "%d", roundNr);
13954             gameInfo.round = StrSave(buf);
13955         } else {
13956             gameInfo.round = StrSave("-");
13957         }
13958         if (first.twoMachinesColor[0] == 'w') {
13959             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13960             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13961         } else {
13962             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13963             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13964         }
13965         gameInfo.timeControl = TimeControlTagValue();
13966         break;
13967
13968       case EditGame:
13969         gameInfo.event = StrSave("Edited game");
13970         gameInfo.site = StrSave(HostName());
13971         gameInfo.date = PGNDate();
13972         gameInfo.round = StrSave("-");
13973         gameInfo.white = StrSave("-");
13974         gameInfo.black = StrSave("-");
13975         gameInfo.result = r;
13976         gameInfo.resultDetails = p;
13977         break;
13978
13979       case EditPosition:
13980         gameInfo.event = StrSave("Edited position");
13981         gameInfo.site = StrSave(HostName());
13982         gameInfo.date = PGNDate();
13983         gameInfo.round = StrSave("-");
13984         gameInfo.white = StrSave("-");
13985         gameInfo.black = StrSave("-");
13986         break;
13987
13988       case IcsPlayingWhite:
13989       case IcsPlayingBlack:
13990       case IcsObserving:
13991       case IcsExamining:
13992         break;
13993
13994       case PlayFromGameFile:
13995         gameInfo.event = StrSave("Game from non-PGN file");
13996         gameInfo.site = StrSave(HostName());
13997         gameInfo.date = PGNDate();
13998         gameInfo.round = StrSave("-");
13999         gameInfo.white = StrSave("?");
14000         gameInfo.black = StrSave("?");
14001         break;
14002
14003       default:
14004         break;
14005     }
14006 }
14007
14008 void
14009 ReplaceComment(index, text)
14010      int index;
14011      char *text;
14012 {
14013     int len;
14014     char *p;
14015     float score;
14016
14017     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14018        pvInfoList[index-1].depth == len &&
14019        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14020        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14021     while (*text == '\n') text++;
14022     len = strlen(text);
14023     while (len > 0 && text[len - 1] == '\n') len--;
14024
14025     if (commentList[index] != NULL)
14026       free(commentList[index]);
14027
14028     if (len == 0) {
14029         commentList[index] = NULL;
14030         return;
14031     }
14032   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14033       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14034       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14035     commentList[index] = (char *) malloc(len + 2);
14036     strncpy(commentList[index], text, len);
14037     commentList[index][len] = '\n';
14038     commentList[index][len + 1] = NULLCHAR;
14039   } else {
14040     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14041     char *p;
14042     commentList[index] = (char *) malloc(len + 7);
14043     safeStrCpy(commentList[index], "{\n", 3);
14044     safeStrCpy(commentList[index]+2, text, len+1);
14045     commentList[index][len+2] = NULLCHAR;
14046     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14047     strcat(commentList[index], "\n}\n");
14048   }
14049 }
14050
14051 void
14052 CrushCRs(text)
14053      char *text;
14054 {
14055   char *p = text;
14056   char *q = text;
14057   char ch;
14058
14059   do {
14060     ch = *p++;
14061     if (ch == '\r') continue;
14062     *q++ = ch;
14063   } while (ch != '\0');
14064 }
14065
14066 void
14067 AppendComment(index, text, addBraces)
14068      int index;
14069      char *text;
14070      Boolean addBraces; // [HGM] braces: tells if we should add {}
14071 {
14072     int oldlen, len;
14073     char *old;
14074
14075 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14076     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14077
14078     CrushCRs(text);
14079     while (*text == '\n') text++;
14080     len = strlen(text);
14081     while (len > 0 && text[len - 1] == '\n') len--;
14082
14083     if (len == 0) return;
14084
14085     if (commentList[index] != NULL) {
14086         old = commentList[index];
14087         oldlen = strlen(old);
14088         while(commentList[index][oldlen-1] ==  '\n')
14089           commentList[index][--oldlen] = NULLCHAR;
14090         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14091         safeStrCpy(commentList[index], old, oldlen + len + 6);
14092         free(old);
14093         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14094         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14095           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14096           while (*text == '\n') { text++; len--; }
14097           commentList[index][--oldlen] = NULLCHAR;
14098       }
14099         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14100         else          strcat(commentList[index], "\n");
14101         strcat(commentList[index], text);
14102         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14103         else          strcat(commentList[index], "\n");
14104     } else {
14105         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14106         if(addBraces)
14107           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14108         else commentList[index][0] = NULLCHAR;
14109         strcat(commentList[index], text);
14110         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14111         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14112     }
14113 }
14114
14115 static char * FindStr( char * text, char * sub_text )
14116 {
14117     char * result = strstr( text, sub_text );
14118
14119     if( result != NULL ) {
14120         result += strlen( sub_text );
14121     }
14122
14123     return result;
14124 }
14125
14126 /* [AS] Try to extract PV info from PGN comment */
14127 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14128 char *GetInfoFromComment( int index, char * text )
14129 {
14130     char * sep = text, *p;
14131
14132     if( text != NULL && index > 0 ) {
14133         int score = 0;
14134         int depth = 0;
14135         int time = -1, sec = 0, deci;
14136         char * s_eval = FindStr( text, "[%eval " );
14137         char * s_emt = FindStr( text, "[%emt " );
14138
14139         if( s_eval != NULL || s_emt != NULL ) {
14140             /* New style */
14141             char delim;
14142
14143             if( s_eval != NULL ) {
14144                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14145                     return text;
14146                 }
14147
14148                 if( delim != ']' ) {
14149                     return text;
14150                 }
14151             }
14152
14153             if( s_emt != NULL ) {
14154             }
14155                 return text;
14156         }
14157         else {
14158             /* We expect something like: [+|-]nnn.nn/dd */
14159             int score_lo = 0;
14160
14161             if(*text != '{') return text; // [HGM] braces: must be normal comment
14162
14163             sep = strchr( text, '/' );
14164             if( sep == NULL || sep < (text+4) ) {
14165                 return text;
14166             }
14167
14168             p = text;
14169             if(p[1] == '(') { // comment starts with PV
14170                p = strchr(p, ')'); // locate end of PV
14171                if(p == NULL || sep < p+5) return text;
14172                // at this point we have something like "{(.*) +0.23/6 ..."
14173                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14174                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14175                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14176             }
14177             time = -1; sec = -1; deci = -1;
14178             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14179                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14180                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14181                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14182                 return text;
14183             }
14184
14185             if( score_lo < 0 || score_lo >= 100 ) {
14186                 return text;
14187             }
14188
14189             if(sec >= 0) time = 600*time + 10*sec; else
14190             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14191
14192             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14193
14194             /* [HGM] PV time: now locate end of PV info */
14195             while( *++sep >= '0' && *sep <= '9'); // strip depth
14196             if(time >= 0)
14197             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14198             if(sec >= 0)
14199             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14200             if(deci >= 0)
14201             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14202             while(*sep == ' ') sep++;
14203         }
14204
14205         if( depth <= 0 ) {
14206             return text;
14207         }
14208
14209         if( time < 0 ) {
14210             time = -1;
14211         }
14212
14213         pvInfoList[index-1].depth = depth;
14214         pvInfoList[index-1].score = score;
14215         pvInfoList[index-1].time  = 10*time; // centi-sec
14216         if(*sep == '}') *sep = 0; else *--sep = '{';
14217         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14218     }
14219     return sep;
14220 }
14221
14222 void
14223 SendToProgram(message, cps)
14224      char *message;
14225      ChessProgramState *cps;
14226 {
14227     int count, outCount, error;
14228     char buf[MSG_SIZ];
14229
14230     if (cps->pr == NULL) return;
14231     Attention(cps);
14232
14233     if (appData.debugMode) {
14234         TimeMark now;
14235         GetTimeMark(&now);
14236         fprintf(debugFP, "%ld >%-6s: %s",
14237                 SubtractTimeMarks(&now, &programStartTime),
14238                 cps->which, message);
14239     }
14240
14241     count = strlen(message);
14242     outCount = OutputToProcess(cps->pr, message, count, &error);
14243     if (outCount < count && !exiting
14244                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14245       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14246       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14247         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14248             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14249                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14250                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14251                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14252             } else {
14253                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14254                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14255                 gameInfo.result = res;
14256             }
14257             gameInfo.resultDetails = StrSave(buf);
14258         }
14259         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14260         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14261     }
14262 }
14263
14264 void
14265 ReceiveFromProgram(isr, closure, message, count, error)
14266      InputSourceRef isr;
14267      VOIDSTAR closure;
14268      char *message;
14269      int count;
14270      int error;
14271 {
14272     char *end_str;
14273     char buf[MSG_SIZ];
14274     ChessProgramState *cps = (ChessProgramState *)closure;
14275
14276     if (isr != cps->isr) return; /* Killed intentionally */
14277     if (count <= 0) {
14278         if (count == 0) {
14279             RemoveInputSource(cps->isr);
14280             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14281                     _(cps->which), cps->program);
14282         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14283                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14284                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14285                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14286                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14287                 } else {
14288                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14289                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14290                     gameInfo.result = res;
14291                 }
14292                 gameInfo.resultDetails = StrSave(buf);
14293             }
14294             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14295             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14296         } else {
14297             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14298                     _(cps->which), cps->program);
14299             RemoveInputSource(cps->isr);
14300
14301             /* [AS] Program is misbehaving badly... kill it */
14302             if( count == -2 ) {
14303                 DestroyChildProcess( cps->pr, 9 );
14304                 cps->pr = NoProc;
14305             }
14306
14307             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14308         }
14309         return;
14310     }
14311
14312     if ((end_str = strchr(message, '\r')) != NULL)
14313       *end_str = NULLCHAR;
14314     if ((end_str = strchr(message, '\n')) != NULL)
14315       *end_str = NULLCHAR;
14316
14317     if (appData.debugMode) {
14318         TimeMark now; int print = 1;
14319         char *quote = ""; char c; int i;
14320
14321         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14322                 char start = message[0];
14323                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14324                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14325                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14326                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14327                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14328                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14329                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14330                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14331                    sscanf(message, "hint: %c", &c)!=1 && 
14332                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14333                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14334                     print = (appData.engineComments >= 2);
14335                 }
14336                 message[0] = start; // restore original message
14337         }
14338         if(print) {
14339                 GetTimeMark(&now);
14340                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14341                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14342                         quote,
14343                         message);
14344         }
14345     }
14346
14347     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14348     if (appData.icsEngineAnalyze) {
14349         if (strstr(message, "whisper") != NULL ||
14350              strstr(message, "kibitz") != NULL ||
14351             strstr(message, "tellics") != NULL) return;
14352     }
14353
14354     HandleMachineMove(message, cps);
14355 }
14356
14357
14358 void
14359 SendTimeControl(cps, mps, tc, inc, sd, st)
14360      ChessProgramState *cps;
14361      int mps, inc, sd, st;
14362      long tc;
14363 {
14364     char buf[MSG_SIZ];
14365     int seconds;
14366
14367     if( timeControl_2 > 0 ) {
14368         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14369             tc = timeControl_2;
14370         }
14371     }
14372     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14373     inc /= cps->timeOdds;
14374     st  /= cps->timeOdds;
14375
14376     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14377
14378     if (st > 0) {
14379       /* Set exact time per move, normally using st command */
14380       if (cps->stKludge) {
14381         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14382         seconds = st % 60;
14383         if (seconds == 0) {
14384           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14385         } else {
14386           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14387         }
14388       } else {
14389         snprintf(buf, MSG_SIZ, "st %d\n", st);
14390       }
14391     } else {
14392       /* Set conventional or incremental time control, using level command */
14393       if (seconds == 0) {
14394         /* Note old gnuchess bug -- minutes:seconds used to not work.
14395            Fixed in later versions, but still avoid :seconds
14396            when seconds is 0. */
14397         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14398       } else {
14399         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14400                  seconds, inc/1000.);
14401       }
14402     }
14403     SendToProgram(buf, cps);
14404
14405     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14406     /* Orthogonally, limit search to given depth */
14407     if (sd > 0) {
14408       if (cps->sdKludge) {
14409         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14410       } else {
14411         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14412       }
14413       SendToProgram(buf, cps);
14414     }
14415
14416     if(cps->nps >= 0) { /* [HGM] nps */
14417         if(cps->supportsNPS == FALSE)
14418           cps->nps = -1; // don't use if engine explicitly says not supported!
14419         else {
14420           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14421           SendToProgram(buf, cps);
14422         }
14423     }
14424 }
14425
14426 ChessProgramState *WhitePlayer()
14427 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14428 {
14429     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14430        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14431         return &second;
14432     return &first;
14433 }
14434
14435 void
14436 SendTimeRemaining(cps, machineWhite)
14437      ChessProgramState *cps;
14438      int /*boolean*/ machineWhite;
14439 {
14440     char message[MSG_SIZ];
14441     long time, otime;
14442
14443     /* Note: this routine must be called when the clocks are stopped
14444        or when they have *just* been set or switched; otherwise
14445        it will be off by the time since the current tick started.
14446     */
14447     if (machineWhite) {
14448         time = whiteTimeRemaining / 10;
14449         otime = blackTimeRemaining / 10;
14450     } else {
14451         time = blackTimeRemaining / 10;
14452         otime = whiteTimeRemaining / 10;
14453     }
14454     /* [HGM] translate opponent's time by time-odds factor */
14455     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14456     if (appData.debugMode) {
14457         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14458     }
14459
14460     if (time <= 0) time = 1;
14461     if (otime <= 0) otime = 1;
14462
14463     snprintf(message, MSG_SIZ, "time %ld\n", time);
14464     SendToProgram(message, cps);
14465
14466     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14467     SendToProgram(message, cps);
14468 }
14469
14470 int
14471 BoolFeature(p, name, loc, cps)
14472      char **p;
14473      char *name;
14474      int *loc;
14475      ChessProgramState *cps;
14476 {
14477   char buf[MSG_SIZ];
14478   int len = strlen(name);
14479   int val;
14480
14481   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14482     (*p) += len + 1;
14483     sscanf(*p, "%d", &val);
14484     *loc = (val != 0);
14485     while (**p && **p != ' ')
14486       (*p)++;
14487     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488     SendToProgram(buf, cps);
14489     return TRUE;
14490   }
14491   return FALSE;
14492 }
14493
14494 int
14495 IntFeature(p, name, loc, cps)
14496      char **p;
14497      char *name;
14498      int *loc;
14499      ChessProgramState *cps;
14500 {
14501   char buf[MSG_SIZ];
14502   int len = strlen(name);
14503   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14504     (*p) += len + 1;
14505     sscanf(*p, "%d", loc);
14506     while (**p && **p != ' ') (*p)++;
14507     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14508     SendToProgram(buf, cps);
14509     return TRUE;
14510   }
14511   return FALSE;
14512 }
14513
14514 int
14515 StringFeature(p, name, loc, cps)
14516      char **p;
14517      char *name;
14518      char loc[];
14519      ChessProgramState *cps;
14520 {
14521   char buf[MSG_SIZ];
14522   int len = strlen(name);
14523   if (strncmp((*p), name, len) == 0
14524       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14525     (*p) += len + 2;
14526     sscanf(*p, "%[^\"]", loc);
14527     while (**p && **p != '\"') (*p)++;
14528     if (**p == '\"') (*p)++;
14529     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14530     SendToProgram(buf, cps);
14531     return TRUE;
14532   }
14533   return FALSE;
14534 }
14535
14536 int
14537 ParseOption(Option *opt, ChessProgramState *cps)
14538 // [HGM] options: process the string that defines an engine option, and determine
14539 // name, type, default value, and allowed value range
14540 {
14541         char *p, *q, buf[MSG_SIZ];
14542         int n, min = (-1)<<31, max = 1<<31, def;
14543
14544         if(p = strstr(opt->name, " -spin ")) {
14545             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14546             if(max < min) max = min; // enforce consistency
14547             if(def < min) def = min;
14548             if(def > max) def = max;
14549             opt->value = def;
14550             opt->min = min;
14551             opt->max = max;
14552             opt->type = Spin;
14553         } else if((p = strstr(opt->name, " -slider "))) {
14554             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14555             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14556             if(max < min) max = min; // enforce consistency
14557             if(def < min) def = min;
14558             if(def > max) def = max;
14559             opt->value = def;
14560             opt->min = min;
14561             opt->max = max;
14562             opt->type = Spin; // Slider;
14563         } else if((p = strstr(opt->name, " -string "))) {
14564             opt->textValue = p+9;
14565             opt->type = TextBox;
14566         } else if((p = strstr(opt->name, " -file "))) {
14567             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14568             opt->textValue = p+7;
14569             opt->type = FileName; // FileName;
14570         } else if((p = strstr(opt->name, " -path "))) {
14571             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14572             opt->textValue = p+7;
14573             opt->type = PathName; // PathName;
14574         } else if(p = strstr(opt->name, " -check ")) {
14575             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14576             opt->value = (def != 0);
14577             opt->type = CheckBox;
14578         } else if(p = strstr(opt->name, " -combo ")) {
14579             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14580             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14581             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14582             opt->value = n = 0;
14583             while(q = StrStr(q, " /// ")) {
14584                 n++; *q = 0;    // count choices, and null-terminate each of them
14585                 q += 5;
14586                 if(*q == '*') { // remember default, which is marked with * prefix
14587                     q++;
14588                     opt->value = n;
14589                 }
14590                 cps->comboList[cps->comboCnt++] = q;
14591             }
14592             cps->comboList[cps->comboCnt++] = NULL;
14593             opt->max = n + 1;
14594             opt->type = ComboBox;
14595         } else if(p = strstr(opt->name, " -button")) {
14596             opt->type = Button;
14597         } else if(p = strstr(opt->name, " -save")) {
14598             opt->type = SaveButton;
14599         } else return FALSE;
14600         *p = 0; // terminate option name
14601         // now look if the command-line options define a setting for this engine option.
14602         if(cps->optionSettings && cps->optionSettings[0])
14603             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14604         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14605           snprintf(buf, MSG_SIZ, "option %s", p);
14606                 if(p = strstr(buf, ",")) *p = 0;
14607                 if(q = strchr(buf, '=')) switch(opt->type) {
14608                     case ComboBox:
14609                         for(n=0; n<opt->max; n++)
14610                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14611                         break;
14612                     case TextBox:
14613                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14614                         break;
14615                     case Spin:
14616                     case CheckBox:
14617                         opt->value = atoi(q+1);
14618                     default:
14619                         break;
14620                 }
14621                 strcat(buf, "\n");
14622                 SendToProgram(buf, cps);
14623         }
14624         return TRUE;
14625 }
14626
14627 void
14628 FeatureDone(cps, val)
14629      ChessProgramState* cps;
14630      int val;
14631 {
14632   DelayedEventCallback cb = GetDelayedEvent();
14633   if ((cb == InitBackEnd3 && cps == &first) ||
14634       (cb == SettingsMenuIfReady && cps == &second) ||
14635       (cb == LoadEngine) ||
14636       (cb == TwoMachinesEventIfReady)) {
14637     CancelDelayedEvent();
14638     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14639   }
14640   cps->initDone = val;
14641 }
14642
14643 /* Parse feature command from engine */
14644 void
14645 ParseFeatures(args, cps)
14646      char* args;
14647      ChessProgramState *cps;
14648 {
14649   char *p = args;
14650   char *q;
14651   int val;
14652   char buf[MSG_SIZ];
14653
14654   for (;;) {
14655     while (*p == ' ') p++;
14656     if (*p == NULLCHAR) return;
14657
14658     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14659     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14660     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14661     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14662     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14663     if (BoolFeature(&p, "reuse", &val, cps)) {
14664       /* Engine can disable reuse, but can't enable it if user said no */
14665       if (!val) cps->reuse = FALSE;
14666       continue;
14667     }
14668     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14669     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14670       if (gameMode == TwoMachinesPlay) {
14671         DisplayTwoMachinesTitle();
14672       } else {
14673         DisplayTitle("");
14674       }
14675       continue;
14676     }
14677     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14678     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14679     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14680     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14681     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14682     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14683     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14684     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14685     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14686     if (IntFeature(&p, "done", &val, cps)) {
14687       FeatureDone(cps, val);
14688       continue;
14689     }
14690     /* Added by Tord: */
14691     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14692     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14693     /* End of additions by Tord */
14694
14695     /* [HGM] added features: */
14696     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14697     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14698     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14699     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14700     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14701     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14702     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14703         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14704           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14705             SendToProgram(buf, cps);
14706             continue;
14707         }
14708         if(cps->nrOptions >= MAX_OPTIONS) {
14709             cps->nrOptions--;
14710             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14711             DisplayError(buf, 0);
14712         }
14713         continue;
14714     }
14715     /* End of additions by HGM */
14716
14717     /* unknown feature: complain and skip */
14718     q = p;
14719     while (*q && *q != '=') q++;
14720     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14721     SendToProgram(buf, cps);
14722     p = q;
14723     if (*p == '=') {
14724       p++;
14725       if (*p == '\"') {
14726         p++;
14727         while (*p && *p != '\"') p++;
14728         if (*p == '\"') p++;
14729       } else {
14730         while (*p && *p != ' ') p++;
14731       }
14732     }
14733   }
14734
14735 }
14736
14737 void
14738 PeriodicUpdatesEvent(newState)
14739      int newState;
14740 {
14741     if (newState == appData.periodicUpdates)
14742       return;
14743
14744     appData.periodicUpdates=newState;
14745
14746     /* Display type changes, so update it now */
14747 //    DisplayAnalysis();
14748
14749     /* Get the ball rolling again... */
14750     if (newState) {
14751         AnalysisPeriodicEvent(1);
14752         StartAnalysisClock();
14753     }
14754 }
14755
14756 void
14757 PonderNextMoveEvent(newState)
14758      int newState;
14759 {
14760     if (newState == appData.ponderNextMove) return;
14761     if (gameMode == EditPosition) EditPositionDone(TRUE);
14762     if (newState) {
14763         SendToProgram("hard\n", &first);
14764         if (gameMode == TwoMachinesPlay) {
14765             SendToProgram("hard\n", &second);
14766         }
14767     } else {
14768         SendToProgram("easy\n", &first);
14769         thinkOutput[0] = NULLCHAR;
14770         if (gameMode == TwoMachinesPlay) {
14771             SendToProgram("easy\n", &second);
14772         }
14773     }
14774     appData.ponderNextMove = newState;
14775 }
14776
14777 void
14778 NewSettingEvent(option, feature, command, value)
14779      char *command;
14780      int option, value, *feature;
14781 {
14782     char buf[MSG_SIZ];
14783
14784     if (gameMode == EditPosition) EditPositionDone(TRUE);
14785     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14786     if(feature == NULL || *feature) SendToProgram(buf, &first);
14787     if (gameMode == TwoMachinesPlay) {
14788         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14789     }
14790 }
14791
14792 void
14793 ShowThinkingEvent()
14794 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14795 {
14796     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14797     int newState = appData.showThinking
14798         // [HGM] thinking: other features now need thinking output as well
14799         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14800
14801     if (oldState == newState) return;
14802     oldState = newState;
14803     if (gameMode == EditPosition) EditPositionDone(TRUE);
14804     if (oldState) {
14805         SendToProgram("post\n", &first);
14806         if (gameMode == TwoMachinesPlay) {
14807             SendToProgram("post\n", &second);
14808         }
14809     } else {
14810         SendToProgram("nopost\n", &first);
14811         thinkOutput[0] = NULLCHAR;
14812         if (gameMode == TwoMachinesPlay) {
14813             SendToProgram("nopost\n", &second);
14814         }
14815     }
14816 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14817 }
14818
14819 void
14820 AskQuestionEvent(title, question, replyPrefix, which)
14821      char *title; char *question; char *replyPrefix; char *which;
14822 {
14823   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14824   if (pr == NoProc) return;
14825   AskQuestion(title, question, replyPrefix, pr);
14826 }
14827
14828 void
14829 TypeInEvent(char firstChar)
14830 {
14831     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14832         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14833         gameMode == AnalyzeMode || gameMode == EditGame || \r
14834         gameMode == EditPosition || gameMode == IcsExamining ||\r
14835         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14836         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14837                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14838                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14839         gameMode == Training) PopUpMoveDialog(firstChar);
14840 }
14841
14842 void
14843 TypeInDoneEvent(char *move)
14844 {
14845         Board board;
14846         int n, fromX, fromY, toX, toY;
14847         char promoChar;
14848         ChessMove moveType;\r
14849
14850         // [HGM] FENedit\r
14851         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14852                 EditPositionPasteFEN(move);\r
14853                 return;\r
14854         }\r
14855         // [HGM] movenum: allow move number to be typed in any mode\r
14856         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14857           ToNrEvent(2*n-1);\r
14858           return;\r
14859         }\r
14860
14861       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14862         gameMode != Training) {\r
14863         DisplayMoveError(_("Displayed move is not current"));\r
14864       } else {\r
14865         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14866           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14867         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14868         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14869           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14870           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14871         } else {\r
14872           DisplayMoveError(_("Could not parse move"));\r
14873         }
14874       }\r
14875 }\r
14876
14877 void
14878 DisplayMove(moveNumber)
14879      int moveNumber;
14880 {
14881     char message[MSG_SIZ];
14882     char res[MSG_SIZ];
14883     char cpThinkOutput[MSG_SIZ];
14884
14885     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14886
14887     if (moveNumber == forwardMostMove - 1 ||
14888         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14889
14890         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14891
14892         if (strchr(cpThinkOutput, '\n')) {
14893             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14894         }
14895     } else {
14896         *cpThinkOutput = NULLCHAR;
14897     }
14898
14899     /* [AS] Hide thinking from human user */
14900     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14901         *cpThinkOutput = NULLCHAR;
14902         if( thinkOutput[0] != NULLCHAR ) {
14903             int i;
14904
14905             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14906                 cpThinkOutput[i] = '.';
14907             }
14908             cpThinkOutput[i] = NULLCHAR;
14909             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14910         }
14911     }
14912
14913     if (moveNumber == forwardMostMove - 1 &&
14914         gameInfo.resultDetails != NULL) {
14915         if (gameInfo.resultDetails[0] == NULLCHAR) {
14916           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14917         } else {
14918           snprintf(res, MSG_SIZ, " {%s} %s",
14919                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14920         }
14921     } else {
14922         res[0] = NULLCHAR;
14923     }
14924
14925     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14926         DisplayMessage(res, cpThinkOutput);
14927     } else {
14928       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14929                 WhiteOnMove(moveNumber) ? " " : ".. ",
14930                 parseList[moveNumber], res);
14931         DisplayMessage(message, cpThinkOutput);
14932     }
14933 }
14934
14935 void
14936 DisplayComment(moveNumber, text)
14937      int moveNumber;
14938      char *text;
14939 {
14940     char title[MSG_SIZ];
14941     char buf[8000]; // comment can be long!
14942     int score, depth;
14943
14944     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14945       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14946     } else {
14947       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14948               WhiteOnMove(moveNumber) ? " " : ".. ",
14949               parseList[moveNumber]);
14950     }
14951     // [HGM] PV info: display PV info together with (or as) comment
14952     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14953       if(text == NULL) text = "";
14954       score = pvInfoList[moveNumber].score;
14955       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14956               depth, (pvInfoList[moveNumber].time+50)/100, text);
14957       text = buf;
14958     }
14959     if (text != NULL && (appData.autoDisplayComment || commentUp))
14960         CommentPopUp(title, text);
14961 }
14962
14963 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14964  * might be busy thinking or pondering.  It can be omitted if your
14965  * gnuchess is configured to stop thinking immediately on any user
14966  * input.  However, that gnuchess feature depends on the FIONREAD
14967  * ioctl, which does not work properly on some flavors of Unix.
14968  */
14969 void
14970 Attention(cps)
14971      ChessProgramState *cps;
14972 {
14973 #if ATTENTION
14974     if (!cps->useSigint) return;
14975     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14976     switch (gameMode) {
14977       case MachinePlaysWhite:
14978       case MachinePlaysBlack:
14979       case TwoMachinesPlay:
14980       case IcsPlayingWhite:
14981       case IcsPlayingBlack:
14982       case AnalyzeMode:
14983       case AnalyzeFile:
14984         /* Skip if we know it isn't thinking */
14985         if (!cps->maybeThinking) return;
14986         if (appData.debugMode)
14987           fprintf(debugFP, "Interrupting %s\n", cps->which);
14988         InterruptChildProcess(cps->pr);
14989         cps->maybeThinking = FALSE;
14990         break;
14991       default:
14992         break;
14993     }
14994 #endif /*ATTENTION*/
14995 }
14996
14997 int
14998 CheckFlags()
14999 {
15000     if (whiteTimeRemaining <= 0) {
15001         if (!whiteFlag) {
15002             whiteFlag = TRUE;
15003             if (appData.icsActive) {
15004                 if (appData.autoCallFlag &&
15005                     gameMode == IcsPlayingBlack && !blackFlag) {
15006                   SendToICS(ics_prefix);
15007                   SendToICS("flag\n");
15008                 }
15009             } else {
15010                 if (blackFlag) {
15011                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15012                 } else {
15013                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15014                     if (appData.autoCallFlag) {
15015                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15016                         return TRUE;
15017                     }
15018                 }
15019             }
15020         }
15021     }
15022     if (blackTimeRemaining <= 0) {
15023         if (!blackFlag) {
15024             blackFlag = TRUE;
15025             if (appData.icsActive) {
15026                 if (appData.autoCallFlag &&
15027                     gameMode == IcsPlayingWhite && !whiteFlag) {
15028                   SendToICS(ics_prefix);
15029                   SendToICS("flag\n");
15030                 }
15031             } else {
15032                 if (whiteFlag) {
15033                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15034                 } else {
15035                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15036                     if (appData.autoCallFlag) {
15037                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15038                         return TRUE;
15039                     }
15040                 }
15041             }
15042         }
15043     }
15044     return FALSE;
15045 }
15046
15047 void
15048 CheckTimeControl()
15049 {
15050     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15051         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15052
15053     /*
15054      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15055      */
15056     if ( !WhiteOnMove(forwardMostMove) ) {
15057         /* White made time control */
15058         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15059         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15060         /* [HGM] time odds: correct new time quota for time odds! */
15061                                             / WhitePlayer()->timeOdds;
15062         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15063     } else {
15064         lastBlack -= blackTimeRemaining;
15065         /* Black made time control */
15066         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15067                                             / WhitePlayer()->other->timeOdds;
15068         lastWhite = whiteTimeRemaining;
15069     }
15070 }
15071
15072 void
15073 DisplayBothClocks()
15074 {
15075     int wom = gameMode == EditPosition ?
15076       !blackPlaysFirst : WhiteOnMove(currentMove);
15077     DisplayWhiteClock(whiteTimeRemaining, wom);
15078     DisplayBlackClock(blackTimeRemaining, !wom);
15079 }
15080
15081
15082 /* Timekeeping seems to be a portability nightmare.  I think everyone
15083    has ftime(), but I'm really not sure, so I'm including some ifdefs
15084    to use other calls if you don't.  Clocks will be less accurate if
15085    you have neither ftime nor gettimeofday.
15086 */
15087
15088 /* VS 2008 requires the #include outside of the function */
15089 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15090 #include <sys/timeb.h>
15091 #endif
15092
15093 /* Get the current time as a TimeMark */
15094 void
15095 GetTimeMark(tm)
15096      TimeMark *tm;
15097 {
15098 #if HAVE_GETTIMEOFDAY
15099
15100     struct timeval timeVal;
15101     struct timezone timeZone;
15102
15103     gettimeofday(&timeVal, &timeZone);
15104     tm->sec = (long) timeVal.tv_sec;
15105     tm->ms = (int) (timeVal.tv_usec / 1000L);
15106
15107 #else /*!HAVE_GETTIMEOFDAY*/
15108 #if HAVE_FTIME
15109
15110 // include <sys/timeb.h> / moved to just above start of function
15111     struct timeb timeB;
15112
15113     ftime(&timeB);
15114     tm->sec = (long) timeB.time;
15115     tm->ms = (int) timeB.millitm;
15116
15117 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15118     tm->sec = (long) time(NULL);
15119     tm->ms = 0;
15120 #endif
15121 #endif
15122 }
15123
15124 /* Return the difference in milliseconds between two
15125    time marks.  We assume the difference will fit in a long!
15126 */
15127 long
15128 SubtractTimeMarks(tm2, tm1)
15129      TimeMark *tm2, *tm1;
15130 {
15131     return 1000L*(tm2->sec - tm1->sec) +
15132            (long) (tm2->ms - tm1->ms);
15133 }
15134
15135
15136 /*
15137  * Code to manage the game clocks.
15138  *
15139  * In tournament play, black starts the clock and then white makes a move.
15140  * We give the human user a slight advantage if he is playing white---the
15141  * clocks don't run until he makes his first move, so it takes zero time.
15142  * Also, we don't account for network lag, so we could get out of sync
15143  * with GNU Chess's clock -- but then, referees are always right.
15144  */
15145
15146 static TimeMark tickStartTM;
15147 static long intendedTickLength;
15148
15149 long
15150 NextTickLength(timeRemaining)
15151      long timeRemaining;
15152 {
15153     long nominalTickLength, nextTickLength;
15154
15155     if (timeRemaining > 0L && timeRemaining <= 10000L)
15156       nominalTickLength = 100L;
15157     else
15158       nominalTickLength = 1000L;
15159     nextTickLength = timeRemaining % nominalTickLength;
15160     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15161
15162     return nextTickLength;
15163 }
15164
15165 /* Adjust clock one minute up or down */
15166 void
15167 AdjustClock(Boolean which, int dir)
15168 {
15169     if(which) blackTimeRemaining += 60000*dir;
15170     else      whiteTimeRemaining += 60000*dir;
15171     DisplayBothClocks();
15172 }
15173
15174 /* Stop clocks and reset to a fresh time control */
15175 void
15176 ResetClocks()
15177 {
15178     (void) StopClockTimer();
15179     if (appData.icsActive) {
15180         whiteTimeRemaining = blackTimeRemaining = 0;
15181     } else if (searchTime) {
15182         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15183         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15184     } else { /* [HGM] correct new time quote for time odds */
15185         whiteTC = blackTC = fullTimeControlString;
15186         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15187         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15188     }
15189     if (whiteFlag || blackFlag) {
15190         DisplayTitle("");
15191         whiteFlag = blackFlag = FALSE;
15192     }
15193     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15194     DisplayBothClocks();
15195 }
15196
15197 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15198
15199 /* Decrement running clock by amount of time that has passed */
15200 void
15201 DecrementClocks()
15202 {
15203     long timeRemaining;
15204     long lastTickLength, fudge;
15205     TimeMark now;
15206
15207     if (!appData.clockMode) return;
15208     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15209
15210     GetTimeMark(&now);
15211
15212     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15213
15214     /* Fudge if we woke up a little too soon */
15215     fudge = intendedTickLength - lastTickLength;
15216     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15217
15218     if (WhiteOnMove(forwardMostMove)) {
15219         if(whiteNPS >= 0) lastTickLength = 0;
15220         timeRemaining = whiteTimeRemaining -= lastTickLength;
15221         if(timeRemaining < 0 && !appData.icsActive) {
15222             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15223             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15224                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15225                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15226             }
15227         }
15228         DisplayWhiteClock(whiteTimeRemaining - fudge,
15229                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15230     } else {
15231         if(blackNPS >= 0) lastTickLength = 0;
15232         timeRemaining = blackTimeRemaining -= lastTickLength;
15233         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15234             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15235             if(suddenDeath) {
15236                 blackStartMove = forwardMostMove;
15237                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15238             }
15239         }
15240         DisplayBlackClock(blackTimeRemaining - fudge,
15241                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15242     }
15243     if (CheckFlags()) return;
15244
15245     tickStartTM = now;
15246     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15247     StartClockTimer(intendedTickLength);
15248
15249     /* if the time remaining has fallen below the alarm threshold, sound the
15250      * alarm. if the alarm has sounded and (due to a takeback or time control
15251      * with increment) the time remaining has increased to a level above the
15252      * threshold, reset the alarm so it can sound again.
15253      */
15254
15255     if (appData.icsActive && appData.icsAlarm) {
15256
15257         /* make sure we are dealing with the user's clock */
15258         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15259                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15260            )) return;
15261
15262         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15263             alarmSounded = FALSE;
15264         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15265             PlayAlarmSound();
15266             alarmSounded = TRUE;
15267         }
15268     }
15269 }
15270
15271
15272 /* A player has just moved, so stop the previously running
15273    clock and (if in clock mode) start the other one.
15274    We redisplay both clocks in case we're in ICS mode, because
15275    ICS gives us an update to both clocks after every move.
15276    Note that this routine is called *after* forwardMostMove
15277    is updated, so the last fractional tick must be subtracted
15278    from the color that is *not* on move now.
15279 */
15280 void
15281 SwitchClocks(int newMoveNr)
15282 {
15283     long lastTickLength;
15284     TimeMark now;
15285     int flagged = FALSE;
15286
15287     GetTimeMark(&now);
15288
15289     if (StopClockTimer() && appData.clockMode) {
15290         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15291         if (!WhiteOnMove(forwardMostMove)) {
15292             if(blackNPS >= 0) lastTickLength = 0;
15293             blackTimeRemaining -= lastTickLength;
15294            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15295 //         if(pvInfoList[forwardMostMove].time == -1)
15296                  pvInfoList[forwardMostMove].time =               // use GUI time
15297                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15298         } else {
15299            if(whiteNPS >= 0) lastTickLength = 0;
15300            whiteTimeRemaining -= lastTickLength;
15301            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15302 //         if(pvInfoList[forwardMostMove].time == -1)
15303                  pvInfoList[forwardMostMove].time =
15304                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15305         }
15306         flagged = CheckFlags();
15307     }
15308     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15309     CheckTimeControl();
15310
15311     if (flagged || !appData.clockMode) return;
15312
15313     switch (gameMode) {
15314       case MachinePlaysBlack:
15315       case MachinePlaysWhite:
15316       case BeginningOfGame:
15317         if (pausing) return;
15318         break;
15319
15320       case EditGame:
15321       case PlayFromGameFile:
15322       case IcsExamining:
15323         return;
15324
15325       default:
15326         break;
15327     }
15328
15329     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15330         if(WhiteOnMove(forwardMostMove))
15331              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15332         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15333     }
15334
15335     tickStartTM = now;
15336     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15337       whiteTimeRemaining : blackTimeRemaining);
15338     StartClockTimer(intendedTickLength);
15339 }
15340
15341
15342 /* Stop both clocks */
15343 void
15344 StopClocks()
15345 {
15346     long lastTickLength;
15347     TimeMark now;
15348
15349     if (!StopClockTimer()) return;
15350     if (!appData.clockMode) return;
15351
15352     GetTimeMark(&now);
15353
15354     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15355     if (WhiteOnMove(forwardMostMove)) {
15356         if(whiteNPS >= 0) lastTickLength = 0;
15357         whiteTimeRemaining -= lastTickLength;
15358         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15359     } else {
15360         if(blackNPS >= 0) lastTickLength = 0;
15361         blackTimeRemaining -= lastTickLength;
15362         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15363     }
15364     CheckFlags();
15365 }
15366
15367 /* Start clock of player on move.  Time may have been reset, so
15368    if clock is already running, stop and restart it. */
15369 void
15370 StartClocks()
15371 {
15372     (void) StopClockTimer(); /* in case it was running already */
15373     DisplayBothClocks();
15374     if (CheckFlags()) return;
15375
15376     if (!appData.clockMode) return;
15377     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15378
15379     GetTimeMark(&tickStartTM);
15380     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15381       whiteTimeRemaining : blackTimeRemaining);
15382
15383    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15384     whiteNPS = blackNPS = -1;
15385     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15386        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15387         whiteNPS = first.nps;
15388     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15389        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15390         blackNPS = first.nps;
15391     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15392         whiteNPS = second.nps;
15393     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15394         blackNPS = second.nps;
15395     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15396
15397     StartClockTimer(intendedTickLength);
15398 }
15399
15400 char *
15401 TimeString(ms)
15402      long ms;
15403 {
15404     long second, minute, hour, day;
15405     char *sign = "";
15406     static char buf[32];
15407
15408     if (ms > 0 && ms <= 9900) {
15409       /* convert milliseconds to tenths, rounding up */
15410       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15411
15412       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15413       return buf;
15414     }
15415
15416     /* convert milliseconds to seconds, rounding up */
15417     /* use floating point to avoid strangeness of integer division
15418        with negative dividends on many machines */
15419     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15420
15421     if (second < 0) {
15422         sign = "-";
15423         second = -second;
15424     }
15425
15426     day = second / (60 * 60 * 24);
15427     second = second % (60 * 60 * 24);
15428     hour = second / (60 * 60);
15429     second = second % (60 * 60);
15430     minute = second / 60;
15431     second = second % 60;
15432
15433     if (day > 0)
15434       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15435               sign, day, hour, minute, second);
15436     else if (hour > 0)
15437       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15438     else
15439       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15440
15441     return buf;
15442 }
15443
15444
15445 /*
15446  * This is necessary because some C libraries aren't ANSI C compliant yet.
15447  */
15448 char *
15449 StrStr(string, match)
15450      char *string, *match;
15451 {
15452     int i, length;
15453
15454     length = strlen(match);
15455
15456     for (i = strlen(string) - length; i >= 0; i--, string++)
15457       if (!strncmp(match, string, length))
15458         return string;
15459
15460     return NULL;
15461 }
15462
15463 char *
15464 StrCaseStr(string, match)
15465      char *string, *match;
15466 {
15467     int i, j, length;
15468
15469     length = strlen(match);
15470
15471     for (i = strlen(string) - length; i >= 0; i--, string++) {
15472         for (j = 0; j < length; j++) {
15473             if (ToLower(match[j]) != ToLower(string[j]))
15474               break;
15475         }
15476         if (j == length) return string;
15477     }
15478
15479     return NULL;
15480 }
15481
15482 #ifndef _amigados
15483 int
15484 StrCaseCmp(s1, s2)
15485      char *s1, *s2;
15486 {
15487     char c1, c2;
15488
15489     for (;;) {
15490         c1 = ToLower(*s1++);
15491         c2 = ToLower(*s2++);
15492         if (c1 > c2) return 1;
15493         if (c1 < c2) return -1;
15494         if (c1 == NULLCHAR) return 0;
15495     }
15496 }
15497
15498
15499 int
15500 ToLower(c)
15501      int c;
15502 {
15503     return isupper(c) ? tolower(c) : c;
15504 }
15505
15506
15507 int
15508 ToUpper(c)
15509      int c;
15510 {
15511     return islower(c) ? toupper(c) : c;
15512 }
15513 #endif /* !_amigados    */
15514
15515 char *
15516 StrSave(s)
15517      char *s;
15518 {
15519   char *ret;
15520
15521   if ((ret = (char *) malloc(strlen(s) + 1)))
15522     {
15523       safeStrCpy(ret, s, strlen(s)+1);
15524     }
15525   return ret;
15526 }
15527
15528 char *
15529 StrSavePtr(s, savePtr)
15530      char *s, **savePtr;
15531 {
15532     if (*savePtr) {
15533         free(*savePtr);
15534     }
15535     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15536       safeStrCpy(*savePtr, s, strlen(s)+1);
15537     }
15538     return(*savePtr);
15539 }
15540
15541 char *
15542 PGNDate()
15543 {
15544     time_t clock;
15545     struct tm *tm;
15546     char buf[MSG_SIZ];
15547
15548     clock = time((time_t *)NULL);
15549     tm = localtime(&clock);
15550     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15551             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15552     return StrSave(buf);
15553 }
15554
15555
15556 char *
15557 PositionToFEN(move, overrideCastling)
15558      int move;
15559      char *overrideCastling;
15560 {
15561     int i, j, fromX, fromY, toX, toY;
15562     int whiteToPlay;
15563     char buf[128];
15564     char *p, *q;
15565     int emptycount;
15566     ChessSquare piece;
15567
15568     whiteToPlay = (gameMode == EditPosition) ?
15569       !blackPlaysFirst : (move % 2 == 0);
15570     p = buf;
15571
15572     /* Piece placement data */
15573     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15574         emptycount = 0;
15575         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15576             if (boards[move][i][j] == EmptySquare) {
15577                 emptycount++;
15578             } else { ChessSquare piece = boards[move][i][j];
15579                 if (emptycount > 0) {
15580                     if(emptycount<10) /* [HGM] can be >= 10 */
15581                         *p++ = '0' + emptycount;
15582                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15583                     emptycount = 0;
15584                 }
15585                 if(PieceToChar(piece) == '+') {
15586                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15587                     *p++ = '+';
15588                     piece = (ChessSquare)(DEMOTED piece);
15589                 }
15590                 *p++ = PieceToChar(piece);
15591                 if(p[-1] == '~') {
15592                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15593                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15594                     *p++ = '~';
15595                 }
15596             }
15597         }
15598         if (emptycount > 0) {
15599             if(emptycount<10) /* [HGM] can be >= 10 */
15600                 *p++ = '0' + emptycount;
15601             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15602             emptycount = 0;
15603         }
15604         *p++ = '/';
15605     }
15606     *(p - 1) = ' ';
15607
15608     /* [HGM] print Crazyhouse or Shogi holdings */
15609     if( gameInfo.holdingsWidth ) {
15610         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15611         q = p;
15612         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15613             piece = boards[move][i][BOARD_WIDTH-1];
15614             if( piece != EmptySquare )
15615               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15616                   *p++ = PieceToChar(piece);
15617         }
15618         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15619             piece = boards[move][BOARD_HEIGHT-i-1][0];
15620             if( piece != EmptySquare )
15621               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15622                   *p++ = PieceToChar(piece);
15623         }
15624
15625         if( q == p ) *p++ = '-';
15626         *p++ = ']';
15627         *p++ = ' ';
15628     }
15629
15630     /* Active color */
15631     *p++ = whiteToPlay ? 'w' : 'b';
15632     *p++ = ' ';
15633
15634   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15635     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15636   } else {
15637   if(nrCastlingRights) {
15638      q = p;
15639      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15640        /* [HGM] write directly from rights */
15641            if(boards[move][CASTLING][2] != NoRights &&
15642               boards[move][CASTLING][0] != NoRights   )
15643                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15644            if(boards[move][CASTLING][2] != NoRights &&
15645               boards[move][CASTLING][1] != NoRights   )
15646                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15647            if(boards[move][CASTLING][5] != NoRights &&
15648               boards[move][CASTLING][3] != NoRights   )
15649                 *p++ = boards[move][CASTLING][3] + AAA;
15650            if(boards[move][CASTLING][5] != NoRights &&
15651               boards[move][CASTLING][4] != NoRights   )
15652                 *p++ = boards[move][CASTLING][4] + AAA;
15653      } else {
15654
15655         /* [HGM] write true castling rights */
15656         if( nrCastlingRights == 6 ) {
15657             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15658                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15659             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15660                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15661             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15662                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15663             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15664                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15665         }
15666      }
15667      if (q == p) *p++ = '-'; /* No castling rights */
15668      *p++ = ' ';
15669   }
15670
15671   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15672      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15673     /* En passant target square */
15674     if (move > backwardMostMove) {
15675         fromX = moveList[move - 1][0] - AAA;
15676         fromY = moveList[move - 1][1] - ONE;
15677         toX = moveList[move - 1][2] - AAA;
15678         toY = moveList[move - 1][3] - ONE;
15679         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15680             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15681             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15682             fromX == toX) {
15683             /* 2-square pawn move just happened */
15684             *p++ = toX + AAA;
15685             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15686         } else {
15687             *p++ = '-';
15688         }
15689     } else if(move == backwardMostMove) {
15690         // [HGM] perhaps we should always do it like this, and forget the above?
15691         if((signed char)boards[move][EP_STATUS] >= 0) {
15692             *p++ = boards[move][EP_STATUS] + AAA;
15693             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15694         } else {
15695             *p++ = '-';
15696         }
15697     } else {
15698         *p++ = '-';
15699     }
15700     *p++ = ' ';
15701   }
15702   }
15703
15704     /* [HGM] find reversible plies */
15705     {   int i = 0, j=move;
15706
15707         if (appData.debugMode) { int k;
15708             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15709             for(k=backwardMostMove; k<=forwardMostMove; k++)
15710                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15711
15712         }
15713
15714         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15715         if( j == backwardMostMove ) i += initialRulePlies;
15716         sprintf(p, "%d ", i);
15717         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15718     }
15719     /* Fullmove number */
15720     sprintf(p, "%d", (move / 2) + 1);
15721
15722     return StrSave(buf);
15723 }
15724
15725 Boolean
15726 ParseFEN(board, blackPlaysFirst, fen)
15727     Board board;
15728      int *blackPlaysFirst;
15729      char *fen;
15730 {
15731     int i, j;
15732     char *p, c;
15733     int emptycount;
15734     ChessSquare piece;
15735
15736     p = fen;
15737
15738     /* [HGM] by default clear Crazyhouse holdings, if present */
15739     if(gameInfo.holdingsWidth) {
15740        for(i=0; i<BOARD_HEIGHT; i++) {
15741            board[i][0]             = EmptySquare; /* black holdings */
15742            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15743            board[i][1]             = (ChessSquare) 0; /* black counts */
15744            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15745        }
15746     }
15747
15748     /* Piece placement data */
15749     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15750         j = 0;
15751         for (;;) {
15752             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15753                 if (*p == '/') p++;
15754                 emptycount = gameInfo.boardWidth - j;
15755                 while (emptycount--)
15756                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15757                 break;
15758 #if(BOARD_FILES >= 10)
15759             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15760                 p++; emptycount=10;
15761                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15762                 while (emptycount--)
15763                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15764 #endif
15765             } else if (isdigit(*p)) {
15766                 emptycount = *p++ - '0';
15767                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15768                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15769                 while (emptycount--)
15770                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15771             } else if (*p == '+' || isalpha(*p)) {
15772                 if (j >= gameInfo.boardWidth) return FALSE;
15773                 if(*p=='+') {
15774                     piece = CharToPiece(*++p);
15775                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15776                     piece = (ChessSquare) (PROMOTED piece ); p++;
15777                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15778                 } else piece = CharToPiece(*p++);
15779
15780                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15781                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15782                     piece = (ChessSquare) (PROMOTED piece);
15783                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15784                     p++;
15785                 }
15786                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15787             } else {
15788                 return FALSE;
15789             }
15790         }
15791     }
15792     while (*p == '/' || *p == ' ') p++;
15793
15794     /* [HGM] look for Crazyhouse holdings here */
15795     while(*p==' ') p++;
15796     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15797         if(*p == '[') p++;
15798         if(*p == '-' ) p++; /* empty holdings */ else {
15799             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15800             /* if we would allow FEN reading to set board size, we would   */
15801             /* have to add holdings and shift the board read so far here   */
15802             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15803                 p++;
15804                 if((int) piece >= (int) BlackPawn ) {
15805                     i = (int)piece - (int)BlackPawn;
15806                     i = PieceToNumber((ChessSquare)i);
15807                     if( i >= gameInfo.holdingsSize ) return FALSE;
15808                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15809                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15810                 } else {
15811                     i = (int)piece - (int)WhitePawn;
15812                     i = PieceToNumber((ChessSquare)i);
15813                     if( i >= gameInfo.holdingsSize ) return FALSE;
15814                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15815                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15816                 }
15817             }
15818         }
15819         if(*p == ']') p++;
15820     }
15821
15822     while(*p == ' ') p++;
15823
15824     /* Active color */
15825     c = *p++;
15826     if(appData.colorNickNames) {
15827       if( c == appData.colorNickNames[0] ) c = 'w'; else
15828       if( c == appData.colorNickNames[1] ) c = 'b';
15829     }
15830     switch (c) {
15831       case 'w':
15832         *blackPlaysFirst = FALSE;
15833         break;
15834       case 'b':
15835         *blackPlaysFirst = TRUE;
15836         break;
15837       default:
15838         return FALSE;
15839     }
15840
15841     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15842     /* return the extra info in global variiables             */
15843
15844     /* set defaults in case FEN is incomplete */
15845     board[EP_STATUS] = EP_UNKNOWN;
15846     for(i=0; i<nrCastlingRights; i++ ) {
15847         board[CASTLING][i] =
15848             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15849     }   /* assume possible unless obviously impossible */
15850     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15851     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15852     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15853                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15854     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15855     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15856     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15857                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15858     FENrulePlies = 0;
15859
15860     while(*p==' ') p++;
15861     if(nrCastlingRights) {
15862       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15863           /* castling indicator present, so default becomes no castlings */
15864           for(i=0; i<nrCastlingRights; i++ ) {
15865                  board[CASTLING][i] = NoRights;
15866           }
15867       }
15868       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15869              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15870              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15871              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15872         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15873
15874         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15875             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15876             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15877         }
15878         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15879             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15880         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15881                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15882         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15883                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15884         switch(c) {
15885           case'K':
15886               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15887               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15888               board[CASTLING][2] = whiteKingFile;
15889               break;
15890           case'Q':
15891               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15892               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15893               board[CASTLING][2] = whiteKingFile;
15894               break;
15895           case'k':
15896               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15897               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15898               board[CASTLING][5] = blackKingFile;
15899               break;
15900           case'q':
15901               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15902               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15903               board[CASTLING][5] = blackKingFile;
15904           case '-':
15905               break;
15906           default: /* FRC castlings */
15907               if(c >= 'a') { /* black rights */
15908                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15909                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15910                   if(i == BOARD_RGHT) break;
15911                   board[CASTLING][5] = i;
15912                   c -= AAA;
15913                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15914                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15915                   if(c > i)
15916                       board[CASTLING][3] = c;
15917                   else
15918                       board[CASTLING][4] = c;
15919               } else { /* white rights */
15920                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15921                     if(board[0][i] == WhiteKing) break;
15922                   if(i == BOARD_RGHT) break;
15923                   board[CASTLING][2] = i;
15924                   c -= AAA - 'a' + 'A';
15925                   if(board[0][c] >= WhiteKing) break;
15926                   if(c > i)
15927                       board[CASTLING][0] = c;
15928                   else
15929                       board[CASTLING][1] = c;
15930               }
15931         }
15932       }
15933       for(i=0; i<nrCastlingRights; i++)
15934         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15935     if (appData.debugMode) {
15936         fprintf(debugFP, "FEN castling rights:");
15937         for(i=0; i<nrCastlingRights; i++)
15938         fprintf(debugFP, " %d", board[CASTLING][i]);
15939         fprintf(debugFP, "\n");
15940     }
15941
15942       while(*p==' ') p++;
15943     }
15944
15945     /* read e.p. field in games that know e.p. capture */
15946     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15947        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15948       if(*p=='-') {
15949         p++; board[EP_STATUS] = EP_NONE;
15950       } else {
15951          char c = *p++ - AAA;
15952
15953          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15954          if(*p >= '0' && *p <='9') p++;
15955          board[EP_STATUS] = c;
15956       }
15957     }
15958
15959
15960     if(sscanf(p, "%d", &i) == 1) {
15961         FENrulePlies = i; /* 50-move ply counter */
15962         /* (The move number is still ignored)    */
15963     }
15964
15965     return TRUE;
15966 }
15967
15968 void
15969 EditPositionPasteFEN(char *fen)
15970 {
15971   if (fen != NULL) {
15972     Board initial_position;
15973
15974     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15975       DisplayError(_("Bad FEN position in clipboard"), 0);
15976       return ;
15977     } else {
15978       int savedBlackPlaysFirst = blackPlaysFirst;
15979       EditPositionEvent();
15980       blackPlaysFirst = savedBlackPlaysFirst;
15981       CopyBoard(boards[0], initial_position);
15982       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15983       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15984       DisplayBothClocks();
15985       DrawPosition(FALSE, boards[currentMove]);
15986     }
15987   }
15988 }
15989
15990 static char cseq[12] = "\\   ";
15991
15992 Boolean set_cont_sequence(char *new_seq)
15993 {
15994     int len;
15995     Boolean ret;
15996
15997     // handle bad attempts to set the sequence
15998         if (!new_seq)
15999                 return 0; // acceptable error - no debug
16000
16001     len = strlen(new_seq);
16002     ret = (len > 0) && (len < sizeof(cseq));
16003     if (ret)
16004       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16005     else if (appData.debugMode)
16006       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16007     return ret;
16008 }
16009
16010 /*
16011     reformat a source message so words don't cross the width boundary.  internal
16012     newlines are not removed.  returns the wrapped size (no null character unless
16013     included in source message).  If dest is NULL, only calculate the size required
16014     for the dest buffer.  lp argument indicats line position upon entry, and it's
16015     passed back upon exit.
16016 */
16017 int wrap(char *dest, char *src, int count, int width, int *lp)
16018 {
16019     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16020
16021     cseq_len = strlen(cseq);
16022     old_line = line = *lp;
16023     ansi = len = clen = 0;
16024
16025     for (i=0; i < count; i++)
16026     {
16027         if (src[i] == '\033')
16028             ansi = 1;
16029
16030         // if we hit the width, back up
16031         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16032         {
16033             // store i & len in case the word is too long
16034             old_i = i, old_len = len;
16035
16036             // find the end of the last word
16037             while (i && src[i] != ' ' && src[i] != '\n')
16038             {
16039                 i--;
16040                 len--;
16041             }
16042
16043             // word too long?  restore i & len before splitting it
16044             if ((old_i-i+clen) >= width)
16045             {
16046                 i = old_i;
16047                 len = old_len;
16048             }
16049
16050             // extra space?
16051             if (i && src[i-1] == ' ')
16052                 len--;
16053
16054             if (src[i] != ' ' && src[i] != '\n')
16055             {
16056                 i--;
16057                 if (len)
16058                     len--;
16059             }
16060
16061             // now append the newline and continuation sequence
16062             if (dest)
16063                 dest[len] = '\n';
16064             len++;
16065             if (dest)
16066                 strncpy(dest+len, cseq, cseq_len);
16067             len += cseq_len;
16068             line = cseq_len;
16069             clen = cseq_len;
16070             continue;
16071         }
16072
16073         if (dest)
16074             dest[len] = src[i];
16075         len++;
16076         if (!ansi)
16077             line++;
16078         if (src[i] == '\n')
16079             line = 0;
16080         if (src[i] == 'm')
16081             ansi = 0;
16082     }
16083     if (dest && appData.debugMode)
16084     {
16085         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16086             count, width, line, len, *lp);
16087         show_bytes(debugFP, src, count);
16088         fprintf(debugFP, "\ndest: ");
16089         show_bytes(debugFP, dest, len);
16090         fprintf(debugFP, "\n");
16091     }
16092     *lp = dest ? line : old_line;
16093
16094     return len;
16095 }
16096
16097 // [HGM] vari: routines for shelving variations
16098
16099 void
16100 PushInner(int firstMove, int lastMove)
16101 {
16102         int i, j, nrMoves = lastMove - firstMove;
16103
16104         // push current tail of game on stack
16105         savedResult[storedGames] = gameInfo.result;
16106         savedDetails[storedGames] = gameInfo.resultDetails;
16107         gameInfo.resultDetails = NULL;
16108         savedFirst[storedGames] = firstMove;
16109         savedLast [storedGames] = lastMove;
16110         savedFramePtr[storedGames] = framePtr;
16111         framePtr -= nrMoves; // reserve space for the boards
16112         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16113             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16114             for(j=0; j<MOVE_LEN; j++)
16115                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16116             for(j=0; j<2*MOVE_LEN; j++)
16117                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16118             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16119             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16120             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16121             pvInfoList[firstMove+i-1].depth = 0;
16122             commentList[framePtr+i] = commentList[firstMove+i];
16123             commentList[firstMove+i] = NULL;
16124         }
16125
16126         storedGames++;
16127         forwardMostMove = firstMove; // truncate game so we can start variation
16128 }
16129
16130 void
16131 PushTail(int firstMove, int lastMove)
16132 {
16133         if(appData.icsActive) { // only in local mode
16134                 forwardMostMove = currentMove; // mimic old ICS behavior
16135                 return;
16136         }
16137         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16138
16139         PushInner(firstMove, lastMove);
16140         if(storedGames == 1) GreyRevert(FALSE);
16141 }
16142
16143 void
16144 PopInner(Boolean annotate)
16145 {
16146         int i, j, nrMoves;
16147         char buf[8000], moveBuf[20];
16148
16149         storedGames--;
16150         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16151         nrMoves = savedLast[storedGames] - currentMove;
16152         if(annotate) {
16153                 int cnt = 10;
16154                 if(!WhiteOnMove(currentMove))
16155                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16156                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16157                 for(i=currentMove; i<forwardMostMove; i++) {
16158                         if(WhiteOnMove(i))
16159                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16160                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16161                         strcat(buf, moveBuf);
16162                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16163                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16164                 }
16165                 strcat(buf, ")");
16166         }
16167         for(i=1; i<=nrMoves; i++) { // copy last variation back
16168             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16169             for(j=0; j<MOVE_LEN; j++)
16170                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16171             for(j=0; j<2*MOVE_LEN; j++)
16172                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16173             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16174             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16175             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16176             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16177             commentList[currentMove+i] = commentList[framePtr+i];
16178             commentList[framePtr+i] = NULL;
16179         }
16180         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16181         framePtr = savedFramePtr[storedGames];
16182         gameInfo.result = savedResult[storedGames];
16183         if(gameInfo.resultDetails != NULL) {
16184             free(gameInfo.resultDetails);
16185       }
16186         gameInfo.resultDetails = savedDetails[storedGames];
16187         forwardMostMove = currentMove + nrMoves;
16188 }
16189
16190 Boolean
16191 PopTail(Boolean annotate)
16192 {
16193         if(appData.icsActive) return FALSE; // only in local mode
16194         if(!storedGames) return FALSE; // sanity
16195         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16196
16197         PopInner(annotate);
16198
16199         if(storedGames == 0) GreyRevert(TRUE);
16200         return TRUE;
16201 }
16202
16203 void
16204 CleanupTail()
16205 {       // remove all shelved variations
16206         int i;
16207         for(i=0; i<storedGames; i++) {
16208             if(savedDetails[i])
16209                 free(savedDetails[i]);
16210             savedDetails[i] = NULL;
16211         }
16212         for(i=framePtr; i<MAX_MOVES; i++) {
16213                 if(commentList[i]) free(commentList[i]);
16214                 commentList[i] = NULL;
16215         }
16216         framePtr = MAX_MOVES-1;
16217         storedGames = 0;
16218 }
16219
16220 void
16221 LoadVariation(int index, char *text)
16222 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16223         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16224         int level = 0, move;
16225
16226         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16227         // first find outermost bracketing variation
16228         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16229             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16230                 if(*p == '{') wait = '}'; else
16231                 if(*p == '[') wait = ']'; else
16232                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16233                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16234             }
16235             if(*p == wait) wait = NULLCHAR; // closing ]} found
16236             p++;
16237         }
16238         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16239         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16240         end[1] = NULLCHAR; // clip off comment beyond variation
16241         ToNrEvent(currentMove-1);
16242         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16243         // kludge: use ParsePV() to append variation to game
16244         move = currentMove;
16245         ParsePV(start, TRUE);
16246         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16247         ClearPremoveHighlights();
16248         CommentPopDown();
16249         ToNrEvent(currentMove+1);
16250 }
16251