Fix PV walking in analysis mode
[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
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
256 int endPV = -1;
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
260 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
264 Boolean partnerUp;
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
276 int chattingPartner;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
282
283 /* States for ics_getting_history */
284 #define H_FALSE 0
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
290
291 /* whosays values for GameEnds */
292 #define GE_ICS 0
293 #define GE_ENGINE 1
294 #define GE_PLAYER 2
295 #define GE_FILE 3
296 #define GE_XBOARD 4
297 #define GE_ENGINE1 5
298 #define GE_ENGINE2 6
299
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
302
303 /* Different types of move when calling RegisterMove */
304 #define CMAIL_MOVE   0
305 #define CMAIL_RESIGN 1
306 #define CMAIL_DRAW   2
307 #define CMAIL_ACCEPT 3
308
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
313
314 /* Telnet protocol constants */
315 #define TN_WILL 0373
316 #define TN_WONT 0374
317 #define TN_DO   0375
318 #define TN_DONT 0376
319 #define TN_IAC  0377
320 #define TN_ECHO 0001
321 #define TN_SGA  0003
322 #define TN_PORT 23
323
324 char*
325 safeStrCpy( char *dst, const char *src, size_t count )
326 { // [HGM] made safe
327   int i;
328   assert( dst != NULL );
329   assert( src != NULL );
330   assert( count > 0 );
331
332   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333   if(  i == count && dst[count-1] != NULLCHAR)
334     {
335       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336       if(appData.debugMode)
337       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
338     }
339
340   return dst;
341 }
342
343 /* Some compiler can't cast u64 to double
344  * This function do the job for us:
345
346  * We use the highest bit for cast, this only
347  * works if the highest bit is not
348  * in use (This should not happen)
349  *
350  * We used this for all compiler
351  */
352 double
353 u64ToDouble(u64 value)
354 {
355   double r;
356   u64 tmp = value & u64Const(0x7fffffffffffffff);
357   r = (double)(s64)tmp;
358   if (value & u64Const(0x8000000000000000))
359        r +=  9.2233720368547758080e18; /* 2^63 */
360  return r;
361 }
362
363 /* Fake up flags for now, as we aren't keeping track of castling
364    availability yet. [HGM] Change of logic: the flag now only
365    indicates the type of castlings allowed by the rule of the game.
366    The actual rights themselves are maintained in the array
367    castlingRights, as part of the game history, and are not probed
368    by this function.
369  */
370 int
371 PosFlags(index)
372 {
373   int flags = F_ALL_CASTLE_OK;
374   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375   switch (gameInfo.variant) {
376   case VariantSuicide:
377     flags &= ~F_ALL_CASTLE_OK;
378   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379     flags |= F_IGNORE_CHECK;
380   case VariantLosers:
381     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
382     break;
383   case VariantAtomic:
384     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
385     break;
386   case VariantKriegspiel:
387     flags |= F_KRIEGSPIEL_CAPTURE;
388     break;
389   case VariantCapaRandom:
390   case VariantFischeRandom:
391     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392   case VariantNoCastle:
393   case VariantShatranj:
394   case VariantCourier:
395   case VariantMakruk:
396     flags &= ~F_ALL_CASTLE_OK;
397     break;
398   default:
399     break;
400   }
401   return flags;
402 }
403
404 FILE *gameFileFP, *debugFP;
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 int shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 };
570
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 };
577
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 };
584
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 };
591
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
613         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
615         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating(str)
648   char *str;
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine(ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions(ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738 "first",
739 "second"
740 };
741
742 void
743 InitEngine(ChessProgramState *cps, int n)
744 {   // [HGM] all engine initialiation put in a function that does one engine
745
746     ClearOptions(cps);
747
748     cps->which = engineNames[n];
749     cps->maybeThinking = FALSE;
750     cps->pr = NoProc;
751     cps->isr = NULL;
752     cps->sendTime = 2;
753     cps->sendDrawOffers = 1;
754
755     cps->program = appData.chessProgram[n];
756     cps->host = appData.host[n];
757     cps->dir = appData.directory[n];
758     cps->initString = appData.engInitString[n];
759     cps->computerString = appData.computerString[n];
760     cps->useSigint  = TRUE;
761     cps->useSigterm = TRUE;
762     cps->reuse = appData.reuse[n];
763     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
764     cps->useSetboard = FALSE;
765     cps->useSAN = FALSE;
766     cps->usePing = FALSE;
767     cps->lastPing = 0;
768     cps->lastPong = 0;
769     cps->usePlayother = FALSE;
770     cps->useColors = TRUE;
771     cps->useUsermove = FALSE;
772     cps->sendICS = FALSE;
773     cps->sendName = appData.icsActive;
774     cps->sdKludge = FALSE;
775     cps->stKludge = FALSE;
776     TidyProgramName(cps->program, cps->host, cps->tidy);
777     cps->matchWins = 0;
778     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
779     cps->analysisSupport = 2; /* detect */
780     cps->analyzing = FALSE;
781     cps->initDone = FALSE;
782
783     /* New features added by Tord: */
784     cps->useFEN960 = FALSE;
785     cps->useOOCastle = TRUE;
786     /* End of new features added by Tord. */
787     cps->fenOverride  = appData.fenOverride[n];
788
789     /* [HGM] time odds: set factor for each machine */
790     cps->timeOdds  = appData.timeOdds[n];
791
792     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
793     cps->accumulateTC = appData.accumulateTC[n];
794     cps->maxNrOfSessions = 1;
795
796     /* [HGM] debug */
797     cps->debug = FALSE;
798     cps->supportsNPS = UNKNOWN;
799
800     /* [HGM] options */
801     cps->optionSettings  = appData.engOptions[n];
802
803     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
804     cps->isUCI = appData.isUCI[n]; /* [AS] */
805     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
806
807     if (appData.protocolVersion[n] > PROTOVER
808         || appData.protocolVersion[n] < 1)
809       {
810         char buf[MSG_SIZ];
811         int len;
812
813         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
814                        appData.protocolVersion[n]);
815         if( (len > MSG_SIZ) && appData.debugMode )
816           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
817
818         DisplayFatalError(buf, 0, 2);
819       }
820     else
821       {
822         cps->protocolVersion = appData.protocolVersion[n];
823       }
824
825     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
826 }
827
828 ChessProgramState *savCps;
829
830 void
831 LoadEngine()
832 {
833     int i;
834     if(WaitForEngine(savCps, LoadEngine)) return;
835     CommonEngineInit(); // recalculate time odds
836     if(gameInfo.variant != StringToVariant(appData.variant)) {
837         // we changed variant when loading the engine; this forces us to reset
838         Reset(TRUE, savCps != &first);
839         EditGameEvent(); // for consistency with other path, as Reset changes mode
840     }
841     InitChessProgram(savCps, FALSE);
842     SendToProgram("force\n", savCps);
843     DisplayMessage("", "");
844     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
845     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
846     ThawUI();
847     SetGNUMode();
848 }
849
850 void
851 ReplaceEngine(ChessProgramState *cps, int n)
852 {
853     EditGameEvent();
854     UnloadEngine(cps);
855     appData.noChessProgram = FALSE;
856     appData.clockMode = TRUE;
857     InitEngine(cps, n);
858     if(n) return; // only startup first engine immediately; second can wait
859     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
860     LoadEngine();
861 }
862
863 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
864 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
865
866 void
867 Load(ChessProgramState *cps, int i)
868 {
869     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
870     if(engineLine[0]) { // an engine was selected from the combo box
871         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
872         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
873         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
874         ParseArgsFromString(buf);
875         SwapEngines(i);
876         ReplaceEngine(cps, i);
877         return;
878     }
879     p = engineName;
880     while(q = strchr(p, SLASH)) p = q+1;
881     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
882     if(engineDir[0] != NULLCHAR)
883         appData.directory[i] = engineDir;
884     else if(p != engineName) { // derive directory from engine path, when not given
885         p[-1] = 0;
886         appData.directory[i] = strdup(engineName);
887         p[-1] = SLASH;
888     } else appData.directory[i] = ".";
889     if(params[0]) {
890         snprintf(command, MSG_SIZ, "%s %s", p, params);
891         p = command;
892     }
893     appData.chessProgram[i] = strdup(p);
894     appData.isUCI[i] = isUCI;
895     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
896     appData.hasOwnBookUCI[i] = hasBook;
897     if(addToList) {
898         int len;
899         q = firstChessProgramNames;
900         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
901         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
902                         v1 ? " -firstProtocolVersion 1" : "",
903                         hasBook ? "" : " -fNoOwnBookUCI",
904                         isUCI ? " -fUCI" : "",
905                         storeVariant ? " -variant " : "",
906                         storeVariant ? VariantName(gameInfo.variant) : "");
907         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
908         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
909         if(q)   free(q);
910     }
911     ReplaceEngine(cps, i);
912 }
913
914 void
915 InitBackEnd1()
916 {
917     int matched, min, sec;
918
919     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
920     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
921
922     GetTimeMark(&programStartTime);
923     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
924     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
925
926     ClearProgramStats();
927     programStats.ok_to_send = 1;
928     programStats.seen_stat = 0;
929
930     /*
931      * Initialize game list
932      */
933     ListNew(&gameList);
934
935
936     /*
937      * Internet chess server status
938      */
939     if (appData.icsActive) {
940         appData.matchMode = FALSE;
941         appData.matchGames = 0;
942 #if ZIPPY
943         appData.noChessProgram = !appData.zippyPlay;
944 #else
945         appData.zippyPlay = FALSE;
946         appData.zippyTalk = FALSE;
947         appData.noChessProgram = TRUE;
948 #endif
949         if (*appData.icsHelper != NULLCHAR) {
950             appData.useTelnet = TRUE;
951             appData.telnetProgram = appData.icsHelper;
952         }
953     } else {
954         appData.zippyTalk = appData.zippyPlay = FALSE;
955     }
956
957     /* [AS] Initialize pv info list [HGM] and game state */
958     {
959         int i, j;
960
961         for( i=0; i<=framePtr; i++ ) {
962             pvInfoList[i].depth = -1;
963             boards[i][EP_STATUS] = EP_NONE;
964             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
965         }
966     }
967
968     /*
969      * Parse timeControl resource
970      */
971     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
972                           appData.movesPerSession)) {
973         char buf[MSG_SIZ];
974         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
975         DisplayFatalError(buf, 0, 2);
976     }
977
978     /*
979      * Parse searchTime resource
980      */
981     if (*appData.searchTime != NULLCHAR) {
982         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
983         if (matched == 1) {
984             searchTime = min * 60;
985         } else if (matched == 2) {
986             searchTime = min * 60 + sec;
987         } else {
988             char buf[MSG_SIZ];
989             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
990             DisplayFatalError(buf, 0, 2);
991         }
992     }
993
994     /* [AS] Adjudication threshold */
995     adjudicateLossThreshold = appData.adjudicateLossThreshold;
996
997     InitEngine(&first, 0);
998     InitEngine(&second, 1);
999     CommonEngineInit();
1000
1001     if (appData.icsActive) {
1002         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1003     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1004         appData.clockMode = FALSE;
1005         first.sendTime = second.sendTime = 0;
1006     }
1007
1008 #if ZIPPY
1009     /* Override some settings from environment variables, for backward
1010        compatibility.  Unfortunately it's not feasible to have the env
1011        vars just set defaults, at least in xboard.  Ugh.
1012     */
1013     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1014       ZippyInit();
1015     }
1016 #endif
1017
1018     if (!appData.icsActive) {
1019       char buf[MSG_SIZ];
1020       int len;
1021
1022       /* Check for variants that are supported only in ICS mode,
1023          or not at all.  Some that are accepted here nevertheless
1024          have bugs; see comments below.
1025       */
1026       VariantClass variant = StringToVariant(appData.variant);
1027       switch (variant) {
1028       case VariantBughouse:     /* need four players and two boards */
1029       case VariantKriegspiel:   /* need to hide pieces and move details */
1030         /* case VariantFischeRandom: (Fabien: moved below) */
1031         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1032         if( (len > MSG_SIZ) && appData.debugMode )
1033           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1034
1035         DisplayFatalError(buf, 0, 2);
1036         return;
1037
1038       case VariantUnknown:
1039       case VariantLoadable:
1040       case Variant29:
1041       case Variant30:
1042       case Variant31:
1043       case Variant32:
1044       case Variant33:
1045       case Variant34:
1046       case Variant35:
1047       case Variant36:
1048       default:
1049         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1050         if( (len > MSG_SIZ) && appData.debugMode )
1051           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1052
1053         DisplayFatalError(buf, 0, 2);
1054         return;
1055
1056       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1057       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1058       case VariantGothic:     /* [HGM] should work */
1059       case VariantCapablanca: /* [HGM] should work */
1060       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1061       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1062       case VariantKnightmate: /* [HGM] should work */
1063       case VariantCylinder:   /* [HGM] untested */
1064       case VariantFalcon:     /* [HGM] untested */
1065       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1066                                  offboard interposition not understood */
1067       case VariantNormal:     /* definitely works! */
1068       case VariantWildCastle: /* pieces not automatically shuffled */
1069       case VariantNoCastle:   /* pieces not automatically shuffled */
1070       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1071       case VariantLosers:     /* should work except for win condition,
1072                                  and doesn't know captures are mandatory */
1073       case VariantSuicide:    /* should work except for win condition,
1074                                  and doesn't know captures are mandatory */
1075       case VariantGiveaway:   /* should work except for win condition,
1076                                  and doesn't know captures are mandatory */
1077       case VariantTwoKings:   /* should work */
1078       case VariantAtomic:     /* should work except for win condition */
1079       case Variant3Check:     /* should work except for win condition */
1080       case VariantShatranj:   /* should work except for all win conditions */
1081       case VariantMakruk:     /* should work except for daw countdown */
1082       case VariantBerolina:   /* might work if TestLegality is off */
1083       case VariantCapaRandom: /* should work */
1084       case VariantJanus:      /* should work */
1085       case VariantSuper:      /* experimental */
1086       case VariantGreat:      /* experimental, requires legality testing to be off */
1087       case VariantSChess:     /* S-Chess, should work */
1088       case VariantSpartan:    /* should work */
1089         break;
1090       }
1091     }
1092
1093 }
1094
1095 int NextIntegerFromString( char ** str, long * value )
1096 {
1097     int result = -1;
1098     char * s = *str;
1099
1100     while( *s == ' ' || *s == '\t' ) {
1101         s++;
1102     }
1103
1104     *value = 0;
1105
1106     if( *s >= '0' && *s <= '9' ) {
1107         while( *s >= '0' && *s <= '9' ) {
1108             *value = *value * 10 + (*s - '0');
1109             s++;
1110         }
1111
1112         result = 0;
1113     }
1114
1115     *str = s;
1116
1117     return result;
1118 }
1119
1120 int NextTimeControlFromString( char ** str, long * value )
1121 {
1122     long temp;
1123     int result = NextIntegerFromString( str, &temp );
1124
1125     if( result == 0 ) {
1126         *value = temp * 60; /* Minutes */
1127         if( **str == ':' ) {
1128             (*str)++;
1129             result = NextIntegerFromString( str, &temp );
1130             *value += temp; /* Seconds */
1131         }
1132     }
1133
1134     return result;
1135 }
1136
1137 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1138 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1139     int result = -1, type = 0; long temp, temp2;
1140
1141     if(**str != ':') return -1; // old params remain in force!
1142     (*str)++;
1143     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1144     if( NextIntegerFromString( str, &temp ) ) return -1;
1145     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1146
1147     if(**str != '/') {
1148         /* time only: incremental or sudden-death time control */
1149         if(**str == '+') { /* increment follows; read it */
1150             (*str)++;
1151             if(**str == '!') type = *(*str)++; // Bronstein TC
1152             if(result = NextIntegerFromString( str, &temp2)) return -1;
1153             *inc = temp2 * 1000;
1154             if(**str == '.') { // read fraction of increment
1155                 char *start = ++(*str);
1156                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1157                 temp2 *= 1000;
1158                 while(start++ < *str) temp2 /= 10;
1159                 *inc += temp2;
1160             }
1161         } else *inc = 0;
1162         *moves = 0; *tc = temp * 1000; *incType = type;
1163         return 0;
1164     }
1165
1166     (*str)++; /* classical time control */
1167     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1168
1169     if(result == 0) {
1170         *moves = temp;
1171         *tc    = temp2 * 1000;
1172         *inc   = 0;
1173         *incType = type;
1174     }
1175     return result;
1176 }
1177
1178 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1179 {   /* [HGM] get time to add from the multi-session time-control string */
1180     int incType, moves=1; /* kludge to force reading of first session */
1181     long time, increment;
1182     char *s = tcString;
1183
1184     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1185     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1186     do {
1187         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1188         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1189         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1190         if(movenr == -1) return time;    /* last move before new session     */
1191         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1192         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1193         if(!moves) return increment;     /* current session is incremental   */
1194         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1195     } while(movenr >= -1);               /* try again for next session       */
1196
1197     return 0; // no new time quota on this move
1198 }
1199
1200 int
1201 ParseTimeControl(tc, ti, mps)
1202      char *tc;
1203      float ti;
1204      int mps;
1205 {
1206   long tc1;
1207   long tc2;
1208   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1209   int min, sec=0;
1210
1211   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1212   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1213       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1214   if(ti > 0) {
1215
1216     if(mps)
1217       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1218     else 
1219       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1220   } else {
1221     if(mps)
1222       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1223     else 
1224       snprintf(buf, MSG_SIZ, ":%s", mytc);
1225   }
1226   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1227   
1228   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1229     return FALSE;
1230   }
1231
1232   if( *tc == '/' ) {
1233     /* Parse second time control */
1234     tc++;
1235
1236     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1237       return FALSE;
1238     }
1239
1240     if( tc2 == 0 ) {
1241       return FALSE;
1242     }
1243
1244     timeControl_2 = tc2 * 1000;
1245   }
1246   else {
1247     timeControl_2 = 0;
1248   }
1249
1250   if( tc1 == 0 ) {
1251     return FALSE;
1252   }
1253
1254   timeControl = tc1 * 1000;
1255
1256   if (ti >= 0) {
1257     timeIncrement = ti * 1000;  /* convert to ms */
1258     movesPerSession = 0;
1259   } else {
1260     timeIncrement = 0;
1261     movesPerSession = mps;
1262   }
1263   return TRUE;
1264 }
1265
1266 void
1267 InitBackEnd2()
1268 {
1269     if (appData.debugMode) {
1270         fprintf(debugFP, "%s\n", programVersion);
1271     }
1272
1273     set_cont_sequence(appData.wrapContSeq);
1274     if (appData.matchGames > 0) {
1275         appData.matchMode = TRUE;
1276     } else if (appData.matchMode) {
1277         appData.matchGames = 1;
1278     }
1279     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1280         appData.matchGames = appData.sameColorGames;
1281     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1282         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1283         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1284     }
1285     Reset(TRUE, FALSE);
1286     if (appData.noChessProgram || first.protocolVersion == 1) {
1287       InitBackEnd3();
1288     } else {
1289       /* kludge: allow timeout for initial "feature" commands */
1290       FreezeUI();
1291       DisplayMessage("", _("Starting chess program"));
1292       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1293     }
1294 }
1295
1296 int
1297 CalculateIndex(int index, int gameNr)
1298 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1299     int res;
1300     if(index > 0) return index; // fixed nmber
1301     if(index == 0) return 1;
1302     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1303     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1304     return res;
1305 }
1306
1307 int
1308 LoadGameOrPosition(int gameNr)
1309 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1310     if (*appData.loadGameFile != NULLCHAR) {
1311         if (!LoadGameFromFile(appData.loadGameFile,
1312                 CalculateIndex(appData.loadGameIndex, gameNr),
1313                               appData.loadGameFile, FALSE)) {
1314             DisplayFatalError(_("Bad game file"), 0, 1);
1315             return 0;
1316         }
1317     } else if (*appData.loadPositionFile != NULLCHAR) {
1318         if (!LoadPositionFromFile(appData.loadPositionFile,
1319                 CalculateIndex(appData.loadPositionIndex, gameNr),
1320                                   appData.loadPositionFile)) {
1321             DisplayFatalError(_("Bad position file"), 0, 1);
1322             return 0;
1323         }
1324     }
1325     return 1;
1326 }
1327
1328 void
1329 ReserveGame(int gameNr, char resChar)
1330 {
1331     FILE *tf = fopen(appData.tourneyFile, "r+");
1332     char *p, *q, c, buf[MSG_SIZ];
1333     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1334     safeStrCpy(buf, lastMsg, MSG_SIZ);
1335     DisplayMessage(_("Pick new game"), "");
1336     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1337     ParseArgsFromFile(tf);
1338     p = q = appData.results;
1339     if(appData.debugMode) {
1340       char *r = appData.participants;
1341       fprintf(debugFP, "results = '%s'\n", p);
1342       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1343       fprintf(debugFP, "\n");
1344     }
1345     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1346     nextGame = q - p;
1347     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1348     safeStrCpy(q, p, strlen(p) + 2);
1349     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1350     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1351     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1352         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1353         q[nextGame] = '*';
1354     }
1355     fseek(tf, -(strlen(p)+4), SEEK_END);
1356     c = fgetc(tf);
1357     if(c != '"') // depending on DOS or Unix line endings we can be one off
1358          fseek(tf, -(strlen(p)+2), SEEK_END);
1359     else fseek(tf, -(strlen(p)+3), SEEK_END);
1360     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1361     DisplayMessage(buf, "");
1362     free(p); appData.results = q;
1363     if(nextGame <= appData.matchGames && resChar != ' ' &&
1364        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1365         UnloadEngine(&first);  // next game belongs to other pairing;
1366         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1367     }
1368 }
1369
1370 void
1371 MatchEvent(int mode)
1372 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1373         int dummy;
1374         if(matchMode) { // already in match mode: switch it off
1375             appData.matchGames = matchGame; // kludge to let match terminate after next game.
1376             ModeHighlight(); // kludgey way to remove checkmark...
1377             return;
1378         }
1379         if(gameMode != BeginningOfGame) {
1380             DisplayError(_("You can only start a match from the initial position."), 0);
1381             return;
1382         }
1383         appData.matchGames = appData.defaultMatchGames;
1384         /* Set up machine vs. machine match */
1385         nextGame = 0;
1386         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1387         if(appData.tourneyFile[0]) {
1388             ReserveGame(-1, 0);
1389             if(nextGame > appData.matchGames) {
1390                 char buf[MSG_SIZ];
1391                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1392                 DisplayError(buf, 0);
1393                 appData.tourneyFile[0] = 0;
1394                 return;
1395             }
1396         } else
1397         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1398             DisplayFatalError(_("Can't have a match with no chess programs"),
1399                               0, 2);
1400             return;
1401         }
1402         matchMode = mode;
1403         matchGame = roundNr = 1;
1404         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1405         NextMatchGame();
1406 }
1407
1408 void
1409 InitBackEnd3 P((void))
1410 {
1411     GameMode initialMode;
1412     char buf[MSG_SIZ];
1413     int err, len;
1414
1415     InitChessProgram(&first, startedFromSetupPosition);
1416
1417     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1418         free(programVersion);
1419         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1420         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1421     }
1422
1423     if (appData.icsActive) {
1424 #ifdef WIN32
1425         /* [DM] Make a console window if needed [HGM] merged ifs */
1426         ConsoleCreate();
1427 #endif
1428         err = establish();
1429         if (err != 0)
1430           {
1431             if (*appData.icsCommPort != NULLCHAR)
1432               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1433                              appData.icsCommPort);
1434             else
1435               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1436                         appData.icsHost, appData.icsPort);
1437
1438             if( (len > MSG_SIZ) && appData.debugMode )
1439               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1440
1441             DisplayFatalError(buf, err, 1);
1442             return;
1443         }
1444         SetICSMode();
1445         telnetISR =
1446           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1447         fromUserISR =
1448           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1449         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1450             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1451     } else if (appData.noChessProgram) {
1452         SetNCPMode();
1453     } else {
1454         SetGNUMode();
1455     }
1456
1457     if (*appData.cmailGameName != NULLCHAR) {
1458         SetCmailMode();
1459         OpenLoopback(&cmailPR);
1460         cmailISR =
1461           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1462     }
1463
1464     ThawUI();
1465     DisplayMessage("", "");
1466     if (StrCaseCmp(appData.initialMode, "") == 0) {
1467       initialMode = BeginningOfGame;
1468       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1469         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1470         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1471         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1472         ModeHighlight();
1473       }
1474     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1475       initialMode = TwoMachinesPlay;
1476     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1477       initialMode = AnalyzeFile;
1478     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1479       initialMode = AnalyzeMode;
1480     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1481       initialMode = MachinePlaysWhite;
1482     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1483       initialMode = MachinePlaysBlack;
1484     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1485       initialMode = EditGame;
1486     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1487       initialMode = EditPosition;
1488     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1489       initialMode = Training;
1490     } else {
1491       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1492       if( (len > MSG_SIZ) && appData.debugMode )
1493         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1494
1495       DisplayFatalError(buf, 0, 2);
1496       return;
1497     }
1498
1499     if (appData.matchMode) {
1500         if(appData.tourneyFile[0]) { // start tourney from command line
1501             FILE *f;
1502             if(f = fopen(appData.tourneyFile, "r")) {
1503                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1504                 fclose(f);
1505             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1506         }
1507         MatchEvent(TRUE);
1508     } else if (*appData.cmailGameName != NULLCHAR) {
1509         /* Set up cmail mode */
1510         ReloadCmailMsgEvent(TRUE);
1511     } else {
1512         /* Set up other modes */
1513         if (initialMode == AnalyzeFile) {
1514           if (*appData.loadGameFile == NULLCHAR) {
1515             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1516             return;
1517           }
1518         }
1519         if (*appData.loadGameFile != NULLCHAR) {
1520             (void) LoadGameFromFile(appData.loadGameFile,
1521                                     appData.loadGameIndex,
1522                                     appData.loadGameFile, TRUE);
1523         } else if (*appData.loadPositionFile != NULLCHAR) {
1524             (void) LoadPositionFromFile(appData.loadPositionFile,
1525                                         appData.loadPositionIndex,
1526                                         appData.loadPositionFile);
1527             /* [HGM] try to make self-starting even after FEN load */
1528             /* to allow automatic setup of fairy variants with wtm */
1529             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1530                 gameMode = BeginningOfGame;
1531                 setboardSpoiledMachineBlack = 1;
1532             }
1533             /* [HGM] loadPos: make that every new game uses the setup */
1534             /* from file as long as we do not switch variant          */
1535             if(!blackPlaysFirst) {
1536                 startedFromPositionFile = TRUE;
1537                 CopyBoard(filePosition, boards[0]);
1538             }
1539         }
1540         if (initialMode == AnalyzeMode) {
1541           if (appData.noChessProgram) {
1542             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1543             return;
1544           }
1545           if (appData.icsActive) {
1546             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1547             return;
1548           }
1549           AnalyzeModeEvent();
1550         } else if (initialMode == AnalyzeFile) {
1551           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1552           ShowThinkingEvent();
1553           AnalyzeFileEvent();
1554           AnalysisPeriodicEvent(1);
1555         } else if (initialMode == MachinePlaysWhite) {
1556           if (appData.noChessProgram) {
1557             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1558                               0, 2);
1559             return;
1560           }
1561           if (appData.icsActive) {
1562             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1563                               0, 2);
1564             return;
1565           }
1566           MachineWhiteEvent();
1567         } else if (initialMode == MachinePlaysBlack) {
1568           if (appData.noChessProgram) {
1569             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1570                               0, 2);
1571             return;
1572           }
1573           if (appData.icsActive) {
1574             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1575                               0, 2);
1576             return;
1577           }
1578           MachineBlackEvent();
1579         } else if (initialMode == TwoMachinesPlay) {
1580           if (appData.noChessProgram) {
1581             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1582                               0, 2);
1583             return;
1584           }
1585           if (appData.icsActive) {
1586             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1587                               0, 2);
1588             return;
1589           }
1590           TwoMachinesEvent();
1591         } else if (initialMode == EditGame) {
1592           EditGameEvent();
1593         } else if (initialMode == EditPosition) {
1594           EditPositionEvent();
1595         } else if (initialMode == Training) {
1596           if (*appData.loadGameFile == NULLCHAR) {
1597             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1598             return;
1599           }
1600           TrainingEvent();
1601         }
1602     }
1603 }
1604
1605 /*
1606  * Establish will establish a contact to a remote host.port.
1607  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1608  *  used to talk to the host.
1609  * Returns 0 if okay, error code if not.
1610  */
1611 int
1612 establish()
1613 {
1614     char buf[MSG_SIZ];
1615
1616     if (*appData.icsCommPort != NULLCHAR) {
1617         /* Talk to the host through a serial comm port */
1618         return OpenCommPort(appData.icsCommPort, &icsPR);
1619
1620     } else if (*appData.gateway != NULLCHAR) {
1621         if (*appData.remoteShell == NULLCHAR) {
1622             /* Use the rcmd protocol to run telnet program on a gateway host */
1623             snprintf(buf, sizeof(buf), "%s %s %s",
1624                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1625             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1626
1627         } else {
1628             /* Use the rsh program to run telnet program on a gateway host */
1629             if (*appData.remoteUser == NULLCHAR) {
1630                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1631                         appData.gateway, appData.telnetProgram,
1632                         appData.icsHost, appData.icsPort);
1633             } else {
1634                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1635                         appData.remoteShell, appData.gateway,
1636                         appData.remoteUser, appData.telnetProgram,
1637                         appData.icsHost, appData.icsPort);
1638             }
1639             return StartChildProcess(buf, "", &icsPR);
1640
1641         }
1642     } else if (appData.useTelnet) {
1643         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1644
1645     } else {
1646         /* TCP socket interface differs somewhat between
1647            Unix and NT; handle details in the front end.
1648            */
1649         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1650     }
1651 }
1652
1653 void EscapeExpand(char *p, char *q)
1654 {       // [HGM] initstring: routine to shape up string arguments
1655         while(*p++ = *q++) if(p[-1] == '\\')
1656             switch(*q++) {
1657                 case 'n': p[-1] = '\n'; break;
1658                 case 'r': p[-1] = '\r'; break;
1659                 case 't': p[-1] = '\t'; break;
1660                 case '\\': p[-1] = '\\'; break;
1661                 case 0: *p = 0; return;
1662                 default: p[-1] = q[-1]; break;
1663             }
1664 }
1665
1666 void
1667 show_bytes(fp, buf, count)
1668      FILE *fp;
1669      char *buf;
1670      int count;
1671 {
1672     while (count--) {
1673         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1674             fprintf(fp, "\\%03o", *buf & 0xff);
1675         } else {
1676             putc(*buf, fp);
1677         }
1678         buf++;
1679     }
1680     fflush(fp);
1681 }
1682
1683 /* Returns an errno value */
1684 int
1685 OutputMaybeTelnet(pr, message, count, outError)
1686      ProcRef pr;
1687      char *message;
1688      int count;
1689      int *outError;
1690 {
1691     char buf[8192], *p, *q, *buflim;
1692     int left, newcount, outcount;
1693
1694     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1695         *appData.gateway != NULLCHAR) {
1696         if (appData.debugMode) {
1697             fprintf(debugFP, ">ICS: ");
1698             show_bytes(debugFP, message, count);
1699             fprintf(debugFP, "\n");
1700         }
1701         return OutputToProcess(pr, message, count, outError);
1702     }
1703
1704     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1705     p = message;
1706     q = buf;
1707     left = count;
1708     newcount = 0;
1709     while (left) {
1710         if (q >= buflim) {
1711             if (appData.debugMode) {
1712                 fprintf(debugFP, ">ICS: ");
1713                 show_bytes(debugFP, buf, newcount);
1714                 fprintf(debugFP, "\n");
1715             }
1716             outcount = OutputToProcess(pr, buf, newcount, outError);
1717             if (outcount < newcount) return -1; /* to be sure */
1718             q = buf;
1719             newcount = 0;
1720         }
1721         if (*p == '\n') {
1722             *q++ = '\r';
1723             newcount++;
1724         } else if (((unsigned char) *p) == TN_IAC) {
1725             *q++ = (char) TN_IAC;
1726             newcount ++;
1727         }
1728         *q++ = *p++;
1729         newcount++;
1730         left--;
1731     }
1732     if (appData.debugMode) {
1733         fprintf(debugFP, ">ICS: ");
1734         show_bytes(debugFP, buf, newcount);
1735         fprintf(debugFP, "\n");
1736     }
1737     outcount = OutputToProcess(pr, buf, newcount, outError);
1738     if (outcount < newcount) return -1; /* to be sure */
1739     return count;
1740 }
1741
1742 void
1743 read_from_player(isr, closure, message, count, error)
1744      InputSourceRef isr;
1745      VOIDSTAR closure;
1746      char *message;
1747      int count;
1748      int error;
1749 {
1750     int outError, outCount;
1751     static int gotEof = 0;
1752
1753     /* Pass data read from player on to ICS */
1754     if (count > 0) {
1755         gotEof = 0;
1756         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1757         if (outCount < count) {
1758             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1759         }
1760     } else if (count < 0) {
1761         RemoveInputSource(isr);
1762         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1763     } else if (gotEof++ > 0) {
1764         RemoveInputSource(isr);
1765         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1766     }
1767 }
1768
1769 void
1770 KeepAlive()
1771 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1772     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1773     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1774     SendToICS("date\n");
1775     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1776 }
1777
1778 /* added routine for printf style output to ics */
1779 void ics_printf(char *format, ...)
1780 {
1781     char buffer[MSG_SIZ];
1782     va_list args;
1783
1784     va_start(args, format);
1785     vsnprintf(buffer, sizeof(buffer), format, args);
1786     buffer[sizeof(buffer)-1] = '\0';
1787     SendToICS(buffer);
1788     va_end(args);
1789 }
1790
1791 void
1792 SendToICS(s)
1793      char *s;
1794 {
1795     int count, outCount, outError;
1796
1797     if (icsPR == NULL) return;
1798
1799     count = strlen(s);
1800     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1801     if (outCount < count) {
1802         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1803     }
1804 }
1805
1806 /* This is used for sending logon scripts to the ICS. Sending
1807    without a delay causes problems when using timestamp on ICC
1808    (at least on my machine). */
1809 void
1810 SendToICSDelayed(s,msdelay)
1811      char *s;
1812      long msdelay;
1813 {
1814     int count, outCount, outError;
1815
1816     if (icsPR == NULL) return;
1817
1818     count = strlen(s);
1819     if (appData.debugMode) {
1820         fprintf(debugFP, ">ICS: ");
1821         show_bytes(debugFP, s, count);
1822         fprintf(debugFP, "\n");
1823     }
1824     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1825                                       msdelay);
1826     if (outCount < count) {
1827         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1828     }
1829 }
1830
1831
1832 /* Remove all highlighting escape sequences in s
1833    Also deletes any suffix starting with '('
1834    */
1835 char *
1836 StripHighlightAndTitle(s)
1837      char *s;
1838 {
1839     static char retbuf[MSG_SIZ];
1840     char *p = retbuf;
1841
1842     while (*s != NULLCHAR) {
1843         while (*s == '\033') {
1844             while (*s != NULLCHAR && !isalpha(*s)) s++;
1845             if (*s != NULLCHAR) s++;
1846         }
1847         while (*s != NULLCHAR && *s != '\033') {
1848             if (*s == '(' || *s == '[') {
1849                 *p = NULLCHAR;
1850                 return retbuf;
1851             }
1852             *p++ = *s++;
1853         }
1854     }
1855     *p = NULLCHAR;
1856     return retbuf;
1857 }
1858
1859 /* Remove all highlighting escape sequences in s */
1860 char *
1861 StripHighlight(s)
1862      char *s;
1863 {
1864     static char retbuf[MSG_SIZ];
1865     char *p = retbuf;
1866
1867     while (*s != NULLCHAR) {
1868         while (*s == '\033') {
1869             while (*s != NULLCHAR && !isalpha(*s)) s++;
1870             if (*s != NULLCHAR) s++;
1871         }
1872         while (*s != NULLCHAR && *s != '\033') {
1873             *p++ = *s++;
1874         }
1875     }
1876     *p = NULLCHAR;
1877     return retbuf;
1878 }
1879
1880 char *variantNames[] = VARIANT_NAMES;
1881 char *
1882 VariantName(v)
1883      VariantClass v;
1884 {
1885     return variantNames[v];
1886 }
1887
1888
1889 /* Identify a variant from the strings the chess servers use or the
1890    PGN Variant tag names we use. */
1891 VariantClass
1892 StringToVariant(e)
1893      char *e;
1894 {
1895     char *p;
1896     int wnum = -1;
1897     VariantClass v = VariantNormal;
1898     int i, found = FALSE;
1899     char buf[MSG_SIZ];
1900     int len;
1901
1902     if (!e) return v;
1903
1904     /* [HGM] skip over optional board-size prefixes */
1905     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1906         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1907         while( *e++ != '_');
1908     }
1909
1910     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1911         v = VariantNormal;
1912         found = TRUE;
1913     } else
1914     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1915       if (StrCaseStr(e, variantNames[i])) {
1916         v = (VariantClass) i;
1917         found = TRUE;
1918         break;
1919       }
1920     }
1921
1922     if (!found) {
1923       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1924           || StrCaseStr(e, "wild/fr")
1925           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1926         v = VariantFischeRandom;
1927       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1928                  (i = 1, p = StrCaseStr(e, "w"))) {
1929         p += i;
1930         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1931         if (isdigit(*p)) {
1932           wnum = atoi(p);
1933         } else {
1934           wnum = -1;
1935         }
1936         switch (wnum) {
1937         case 0: /* FICS only, actually */
1938         case 1:
1939           /* Castling legal even if K starts on d-file */
1940           v = VariantWildCastle;
1941           break;
1942         case 2:
1943         case 3:
1944         case 4:
1945           /* Castling illegal even if K & R happen to start in
1946              normal positions. */
1947           v = VariantNoCastle;
1948           break;
1949         case 5:
1950         case 7:
1951         case 8:
1952         case 10:
1953         case 11:
1954         case 12:
1955         case 13:
1956         case 14:
1957         case 15:
1958         case 18:
1959         case 19:
1960           /* Castling legal iff K & R start in normal positions */
1961           v = VariantNormal;
1962           break;
1963         case 6:
1964         case 20:
1965         case 21:
1966           /* Special wilds for position setup; unclear what to do here */
1967           v = VariantLoadable;
1968           break;
1969         case 9:
1970           /* Bizarre ICC game */
1971           v = VariantTwoKings;
1972           break;
1973         case 16:
1974           v = VariantKriegspiel;
1975           break;
1976         case 17:
1977           v = VariantLosers;
1978           break;
1979         case 22:
1980           v = VariantFischeRandom;
1981           break;
1982         case 23:
1983           v = VariantCrazyhouse;
1984           break;
1985         case 24:
1986           v = VariantBughouse;
1987           break;
1988         case 25:
1989           v = Variant3Check;
1990           break;
1991         case 26:
1992           /* Not quite the same as FICS suicide! */
1993           v = VariantGiveaway;
1994           break;
1995         case 27:
1996           v = VariantAtomic;
1997           break;
1998         case 28:
1999           v = VariantShatranj;
2000           break;
2001
2002         /* Temporary names for future ICC types.  The name *will* change in
2003            the next xboard/WinBoard release after ICC defines it. */
2004         case 29:
2005           v = Variant29;
2006           break;
2007         case 30:
2008           v = Variant30;
2009           break;
2010         case 31:
2011           v = Variant31;
2012           break;
2013         case 32:
2014           v = Variant32;
2015           break;
2016         case 33:
2017           v = Variant33;
2018           break;
2019         case 34:
2020           v = Variant34;
2021           break;
2022         case 35:
2023           v = Variant35;
2024           break;
2025         case 36:
2026           v = Variant36;
2027           break;
2028         case 37:
2029           v = VariantShogi;
2030           break;
2031         case 38:
2032           v = VariantXiangqi;
2033           break;
2034         case 39:
2035           v = VariantCourier;
2036           break;
2037         case 40:
2038           v = VariantGothic;
2039           break;
2040         case 41:
2041           v = VariantCapablanca;
2042           break;
2043         case 42:
2044           v = VariantKnightmate;
2045           break;
2046         case 43:
2047           v = VariantFairy;
2048           break;
2049         case 44:
2050           v = VariantCylinder;
2051           break;
2052         case 45:
2053           v = VariantFalcon;
2054           break;
2055         case 46:
2056           v = VariantCapaRandom;
2057           break;
2058         case 47:
2059           v = VariantBerolina;
2060           break;
2061         case 48:
2062           v = VariantJanus;
2063           break;
2064         case 49:
2065           v = VariantSuper;
2066           break;
2067         case 50:
2068           v = VariantGreat;
2069           break;
2070         case -1:
2071           /* Found "wild" or "w" in the string but no number;
2072              must assume it's normal chess. */
2073           v = VariantNormal;
2074           break;
2075         default:
2076           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2077           if( (len > MSG_SIZ) && appData.debugMode )
2078             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2079
2080           DisplayError(buf, 0);
2081           v = VariantUnknown;
2082           break;
2083         }
2084       }
2085     }
2086     if (appData.debugMode) {
2087       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2088               e, wnum, VariantName(v));
2089     }
2090     return v;
2091 }
2092
2093 static int leftover_start = 0, leftover_len = 0;
2094 char star_match[STAR_MATCH_N][MSG_SIZ];
2095
2096 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2097    advance *index beyond it, and set leftover_start to the new value of
2098    *index; else return FALSE.  If pattern contains the character '*', it
2099    matches any sequence of characters not containing '\r', '\n', or the
2100    character following the '*' (if any), and the matched sequence(s) are
2101    copied into star_match.
2102    */
2103 int
2104 looking_at(buf, index, pattern)
2105      char *buf;
2106      int *index;
2107      char *pattern;
2108 {
2109     char *bufp = &buf[*index], *patternp = pattern;
2110     int star_count = 0;
2111     char *matchp = star_match[0];
2112
2113     for (;;) {
2114         if (*patternp == NULLCHAR) {
2115             *index = leftover_start = bufp - buf;
2116             *matchp = NULLCHAR;
2117             return TRUE;
2118         }
2119         if (*bufp == NULLCHAR) return FALSE;
2120         if (*patternp == '*') {
2121             if (*bufp == *(patternp + 1)) {
2122                 *matchp = NULLCHAR;
2123                 matchp = star_match[++star_count];
2124                 patternp += 2;
2125                 bufp++;
2126                 continue;
2127             } else if (*bufp == '\n' || *bufp == '\r') {
2128                 patternp++;
2129                 if (*patternp == NULLCHAR)
2130                   continue;
2131                 else
2132                   return FALSE;
2133             } else {
2134                 *matchp++ = *bufp++;
2135                 continue;
2136             }
2137         }
2138         if (*patternp != *bufp) return FALSE;
2139         patternp++;
2140         bufp++;
2141     }
2142 }
2143
2144 void
2145 SendToPlayer(data, length)
2146      char *data;
2147      int length;
2148 {
2149     int error, outCount;
2150     outCount = OutputToProcess(NoProc, data, length, &error);
2151     if (outCount < length) {
2152         DisplayFatalError(_("Error writing to display"), error, 1);
2153     }
2154 }
2155
2156 void
2157 PackHolding(packed, holding)
2158      char packed[];
2159      char *holding;
2160 {
2161     char *p = holding;
2162     char *q = packed;
2163     int runlength = 0;
2164     int curr = 9999;
2165     do {
2166         if (*p == curr) {
2167             runlength++;
2168         } else {
2169             switch (runlength) {
2170               case 0:
2171                 break;
2172               case 1:
2173                 *q++ = curr;
2174                 break;
2175               case 2:
2176                 *q++ = curr;
2177                 *q++ = curr;
2178                 break;
2179               default:
2180                 sprintf(q, "%d", runlength);
2181                 while (*q) q++;
2182                 *q++ = curr;
2183                 break;
2184             }
2185             runlength = 1;
2186             curr = *p;
2187         }
2188     } while (*p++);
2189     *q = NULLCHAR;
2190 }
2191
2192 /* Telnet protocol requests from the front end */
2193 void
2194 TelnetRequest(ddww, option)
2195      unsigned char ddww, option;
2196 {
2197     unsigned char msg[3];
2198     int outCount, outError;
2199
2200     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2201
2202     if (appData.debugMode) {
2203         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2204         switch (ddww) {
2205           case TN_DO:
2206             ddwwStr = "DO";
2207             break;
2208           case TN_DONT:
2209             ddwwStr = "DONT";
2210             break;
2211           case TN_WILL:
2212             ddwwStr = "WILL";
2213             break;
2214           case TN_WONT:
2215             ddwwStr = "WONT";
2216             break;
2217           default:
2218             ddwwStr = buf1;
2219             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2220             break;
2221         }
2222         switch (option) {
2223           case TN_ECHO:
2224             optionStr = "ECHO";
2225             break;
2226           default:
2227             optionStr = buf2;
2228             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2229             break;
2230         }
2231         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2232     }
2233     msg[0] = TN_IAC;
2234     msg[1] = ddww;
2235     msg[2] = option;
2236     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2237     if (outCount < 3) {
2238         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2239     }
2240 }
2241
2242 void
2243 DoEcho()
2244 {
2245     if (!appData.icsActive) return;
2246     TelnetRequest(TN_DO, TN_ECHO);
2247 }
2248
2249 void
2250 DontEcho()
2251 {
2252     if (!appData.icsActive) return;
2253     TelnetRequest(TN_DONT, TN_ECHO);
2254 }
2255
2256 void
2257 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2258 {
2259     /* put the holdings sent to us by the server on the board holdings area */
2260     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2261     char p;
2262     ChessSquare piece;
2263
2264     if(gameInfo.holdingsWidth < 2)  return;
2265     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2266         return; // prevent overwriting by pre-board holdings
2267
2268     if( (int)lowestPiece >= BlackPawn ) {
2269         holdingsColumn = 0;
2270         countsColumn = 1;
2271         holdingsStartRow = BOARD_HEIGHT-1;
2272         direction = -1;
2273     } else {
2274         holdingsColumn = BOARD_WIDTH-1;
2275         countsColumn = BOARD_WIDTH-2;
2276         holdingsStartRow = 0;
2277         direction = 1;
2278     }
2279
2280     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2281         board[i][holdingsColumn] = EmptySquare;
2282         board[i][countsColumn]   = (ChessSquare) 0;
2283     }
2284     while( (p=*holdings++) != NULLCHAR ) {
2285         piece = CharToPiece( ToUpper(p) );
2286         if(piece == EmptySquare) continue;
2287         /*j = (int) piece - (int) WhitePawn;*/
2288         j = PieceToNumber(piece);
2289         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2290         if(j < 0) continue;               /* should not happen */
2291         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2292         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2293         board[holdingsStartRow+j*direction][countsColumn]++;
2294     }
2295 }
2296
2297
2298 void
2299 VariantSwitch(Board board, VariantClass newVariant)
2300 {
2301    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2302    static Board oldBoard;
2303
2304    startedFromPositionFile = FALSE;
2305    if(gameInfo.variant == newVariant) return;
2306
2307    /* [HGM] This routine is called each time an assignment is made to
2308     * gameInfo.variant during a game, to make sure the board sizes
2309     * are set to match the new variant. If that means adding or deleting
2310     * holdings, we shift the playing board accordingly
2311     * This kludge is needed because in ICS observe mode, we get boards
2312     * of an ongoing game without knowing the variant, and learn about the
2313     * latter only later. This can be because of the move list we requested,
2314     * in which case the game history is refilled from the beginning anyway,
2315     * but also when receiving holdings of a crazyhouse game. In the latter
2316     * case we want to add those holdings to the already received position.
2317     */
2318
2319
2320    if (appData.debugMode) {
2321      fprintf(debugFP, "Switch board from %s to %s\n",
2322              VariantName(gameInfo.variant), VariantName(newVariant));
2323      setbuf(debugFP, NULL);
2324    }
2325    shuffleOpenings = 0;       /* [HGM] shuffle */
2326    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2327    switch(newVariant)
2328      {
2329      case VariantShogi:
2330        newWidth = 9;  newHeight = 9;
2331        gameInfo.holdingsSize = 7;
2332      case VariantBughouse:
2333      case VariantCrazyhouse:
2334        newHoldingsWidth = 2; break;
2335      case VariantGreat:
2336        newWidth = 10;
2337      case VariantSuper:
2338        newHoldingsWidth = 2;
2339        gameInfo.holdingsSize = 8;
2340        break;
2341      case VariantGothic:
2342      case VariantCapablanca:
2343      case VariantCapaRandom:
2344        newWidth = 10;
2345      default:
2346        newHoldingsWidth = gameInfo.holdingsSize = 0;
2347      };
2348
2349    if(newWidth  != gameInfo.boardWidth  ||
2350       newHeight != gameInfo.boardHeight ||
2351       newHoldingsWidth != gameInfo.holdingsWidth ) {
2352
2353      /* shift position to new playing area, if needed */
2354      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2355        for(i=0; i<BOARD_HEIGHT; i++)
2356          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2357            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2358              board[i][j];
2359        for(i=0; i<newHeight; i++) {
2360          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2361          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2362        }
2363      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2364        for(i=0; i<BOARD_HEIGHT; i++)
2365          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2366            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2367              board[i][j];
2368      }
2369      gameInfo.boardWidth  = newWidth;
2370      gameInfo.boardHeight = newHeight;
2371      gameInfo.holdingsWidth = newHoldingsWidth;
2372      gameInfo.variant = newVariant;
2373      InitDrawingSizes(-2, 0);
2374    } else gameInfo.variant = newVariant;
2375    CopyBoard(oldBoard, board);   // remember correctly formatted board
2376      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2377    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2378 }
2379
2380 static int loggedOn = FALSE;
2381
2382 /*-- Game start info cache: --*/
2383 int gs_gamenum;
2384 char gs_kind[MSG_SIZ];
2385 static char player1Name[128] = "";
2386 static char player2Name[128] = "";
2387 static char cont_seq[] = "\n\\   ";
2388 static int player1Rating = -1;
2389 static int player2Rating = -1;
2390 /*----------------------------*/
2391
2392 ColorClass curColor = ColorNormal;
2393 int suppressKibitz = 0;
2394
2395 // [HGM] seekgraph
2396 Boolean soughtPending = FALSE;
2397 Boolean seekGraphUp;
2398 #define MAX_SEEK_ADS 200
2399 #define SQUARE 0x80
2400 char *seekAdList[MAX_SEEK_ADS];
2401 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2402 float tcList[MAX_SEEK_ADS];
2403 char colorList[MAX_SEEK_ADS];
2404 int nrOfSeekAds = 0;
2405 int minRating = 1010, maxRating = 2800;
2406 int hMargin = 10, vMargin = 20, h, w;
2407 extern int squareSize, lineGap;
2408
2409 void
2410 PlotSeekAd(int i)
2411 {
2412         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2413         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2414         if(r < minRating+100 && r >=0 ) r = minRating+100;
2415         if(r > maxRating) r = maxRating;
2416         if(tc < 1.) tc = 1.;
2417         if(tc > 95.) tc = 95.;
2418         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2419         y = ((double)r - minRating)/(maxRating - minRating)
2420             * (h-vMargin-squareSize/8-1) + vMargin;
2421         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2422         if(strstr(seekAdList[i], " u ")) color = 1;
2423         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2424            !strstr(seekAdList[i], "bullet") &&
2425            !strstr(seekAdList[i], "blitz") &&
2426            !strstr(seekAdList[i], "standard") ) color = 2;
2427         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2428         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2429 }
2430
2431 void
2432 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2433 {
2434         char buf[MSG_SIZ], *ext = "";
2435         VariantClass v = StringToVariant(type);
2436         if(strstr(type, "wild")) {
2437             ext = type + 4; // append wild number
2438             if(v == VariantFischeRandom) type = "chess960"; else
2439             if(v == VariantLoadable) type = "setup"; else
2440             type = VariantName(v);
2441         }
2442         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2443         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2444             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2445             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2446             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2447             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2448             seekNrList[nrOfSeekAds] = nr;
2449             zList[nrOfSeekAds] = 0;
2450             seekAdList[nrOfSeekAds++] = StrSave(buf);
2451             if(plot) PlotSeekAd(nrOfSeekAds-1);
2452         }
2453 }
2454
2455 void
2456 EraseSeekDot(int i)
2457 {
2458     int x = xList[i], y = yList[i], d=squareSize/4, k;
2459     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2460     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2461     // now replot every dot that overlapped
2462     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2463         int xx = xList[k], yy = yList[k];
2464         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2465             DrawSeekDot(xx, yy, colorList[k]);
2466     }
2467 }
2468
2469 void
2470 RemoveSeekAd(int nr)
2471 {
2472         int i;
2473         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2474             EraseSeekDot(i);
2475             if(seekAdList[i]) free(seekAdList[i]);
2476             seekAdList[i] = seekAdList[--nrOfSeekAds];
2477             seekNrList[i] = seekNrList[nrOfSeekAds];
2478             ratingList[i] = ratingList[nrOfSeekAds];
2479             colorList[i]  = colorList[nrOfSeekAds];
2480             tcList[i] = tcList[nrOfSeekAds];
2481             xList[i]  = xList[nrOfSeekAds];
2482             yList[i]  = yList[nrOfSeekAds];
2483             zList[i]  = zList[nrOfSeekAds];
2484             seekAdList[nrOfSeekAds] = NULL;
2485             break;
2486         }
2487 }
2488
2489 Boolean
2490 MatchSoughtLine(char *line)
2491 {
2492     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2493     int nr, base, inc, u=0; char dummy;
2494
2495     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2496        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2497        (u=1) &&
2498        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2499         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2500         // match: compact and save the line
2501         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2502         return TRUE;
2503     }
2504     return FALSE;
2505 }
2506
2507 int
2508 DrawSeekGraph()
2509 {
2510     int i;
2511     if(!seekGraphUp) return FALSE;
2512     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2513     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2514
2515     DrawSeekBackground(0, 0, w, h);
2516     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2517     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2518     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2519         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2520         yy = h-1-yy;
2521         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2522         if(i%500 == 0) {
2523             char buf[MSG_SIZ];
2524             snprintf(buf, MSG_SIZ, "%d", i);
2525             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2526         }
2527     }
2528     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2529     for(i=1; i<100; i+=(i<10?1:5)) {
2530         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2531         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2532         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2533             char buf[MSG_SIZ];
2534             snprintf(buf, MSG_SIZ, "%d", i);
2535             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2536         }
2537     }
2538     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2539     return TRUE;
2540 }
2541
2542 int SeekGraphClick(ClickType click, int x, int y, int moving)
2543 {
2544     static int lastDown = 0, displayed = 0, lastSecond;
2545     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2546         if(click == Release || moving) return FALSE;
2547         nrOfSeekAds = 0;
2548         soughtPending = TRUE;
2549         SendToICS(ics_prefix);
2550         SendToICS("sought\n"); // should this be "sought all"?
2551     } else { // issue challenge based on clicked ad
2552         int dist = 10000; int i, closest = 0, second = 0;
2553         for(i=0; i<nrOfSeekAds; i++) {
2554             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2555             if(d < dist) { dist = d; closest = i; }
2556             second += (d - zList[i] < 120); // count in-range ads
2557             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2558         }
2559         if(dist < 120) {
2560             char buf[MSG_SIZ];
2561             second = (second > 1);
2562             if(displayed != closest || second != lastSecond) {
2563                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2564                 lastSecond = second; displayed = closest;
2565             }
2566             if(click == Press) {
2567                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2568                 lastDown = closest;
2569                 return TRUE;
2570             } // on press 'hit', only show info
2571             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2572             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2573             SendToICS(ics_prefix);
2574             SendToICS(buf);
2575             return TRUE; // let incoming board of started game pop down the graph
2576         } else if(click == Release) { // release 'miss' is ignored
2577             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2578             if(moving == 2) { // right up-click
2579                 nrOfSeekAds = 0; // refresh graph
2580                 soughtPending = TRUE;
2581                 SendToICS(ics_prefix);
2582                 SendToICS("sought\n"); // should this be "sought all"?
2583             }
2584             return TRUE;
2585         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2586         // press miss or release hit 'pop down' seek graph
2587         seekGraphUp = FALSE;
2588         DrawPosition(TRUE, NULL);
2589     }
2590     return TRUE;
2591 }
2592
2593 void
2594 read_from_ics(isr, closure, data, count, error)
2595      InputSourceRef isr;
2596      VOIDSTAR closure;
2597      char *data;
2598      int count;
2599      int error;
2600 {
2601 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2602 #define STARTED_NONE 0
2603 #define STARTED_MOVES 1
2604 #define STARTED_BOARD 2
2605 #define STARTED_OBSERVE 3
2606 #define STARTED_HOLDINGS 4
2607 #define STARTED_CHATTER 5
2608 #define STARTED_COMMENT 6
2609 #define STARTED_MOVES_NOHIDE 7
2610
2611     static int started = STARTED_NONE;
2612     static char parse[20000];
2613     static int parse_pos = 0;
2614     static char buf[BUF_SIZE + 1];
2615     static int firstTime = TRUE, intfSet = FALSE;
2616     static ColorClass prevColor = ColorNormal;
2617     static int savingComment = FALSE;
2618     static int cmatch = 0; // continuation sequence match
2619     char *bp;
2620     char str[MSG_SIZ];
2621     int i, oldi;
2622     int buf_len;
2623     int next_out;
2624     int tkind;
2625     int backup;    /* [DM] For zippy color lines */
2626     char *p;
2627     char talker[MSG_SIZ]; // [HGM] chat
2628     int channel;
2629
2630     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2631
2632     if (appData.debugMode) {
2633       if (!error) {
2634         fprintf(debugFP, "<ICS: ");
2635         show_bytes(debugFP, data, count);
2636         fprintf(debugFP, "\n");
2637       }
2638     }
2639
2640     if (appData.debugMode) { int f = forwardMostMove;
2641         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2642                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2643                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2644     }
2645     if (count > 0) {
2646         /* If last read ended with a partial line that we couldn't parse,
2647            prepend it to the new read and try again. */
2648         if (leftover_len > 0) {
2649             for (i=0; i<leftover_len; i++)
2650               buf[i] = buf[leftover_start + i];
2651         }
2652
2653     /* copy new characters into the buffer */
2654     bp = buf + leftover_len;
2655     buf_len=leftover_len;
2656     for (i=0; i<count; i++)
2657     {
2658         // ignore these
2659         if (data[i] == '\r')
2660             continue;
2661
2662         // join lines split by ICS?
2663         if (!appData.noJoin)
2664         {
2665             /*
2666                 Joining just consists of finding matches against the
2667                 continuation sequence, and discarding that sequence
2668                 if found instead of copying it.  So, until a match
2669                 fails, there's nothing to do since it might be the
2670                 complete sequence, and thus, something we don't want
2671                 copied.
2672             */
2673             if (data[i] == cont_seq[cmatch])
2674             {
2675                 cmatch++;
2676                 if (cmatch == strlen(cont_seq))
2677                 {
2678                     cmatch = 0; // complete match.  just reset the counter
2679
2680                     /*
2681                         it's possible for the ICS to not include the space
2682                         at the end of the last word, making our [correct]
2683                         join operation fuse two separate words.  the server
2684                         does this when the space occurs at the width setting.
2685                     */
2686                     if (!buf_len || buf[buf_len-1] != ' ')
2687                     {
2688                         *bp++ = ' ';
2689                         buf_len++;
2690                     }
2691                 }
2692                 continue;
2693             }
2694             else if (cmatch)
2695             {
2696                 /*
2697                     match failed, so we have to copy what matched before
2698                     falling through and copying this character.  In reality,
2699                     this will only ever be just the newline character, but
2700                     it doesn't hurt to be precise.
2701                 */
2702                 strncpy(bp, cont_seq, cmatch);
2703                 bp += cmatch;
2704                 buf_len += cmatch;
2705                 cmatch = 0;
2706             }
2707         }
2708
2709         // copy this char
2710         *bp++ = data[i];
2711         buf_len++;
2712     }
2713
2714         buf[buf_len] = NULLCHAR;
2715 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2716         next_out = 0;
2717         leftover_start = 0;
2718
2719         i = 0;
2720         while (i < buf_len) {
2721             /* Deal with part of the TELNET option negotiation
2722                protocol.  We refuse to do anything beyond the
2723                defaults, except that we allow the WILL ECHO option,
2724                which ICS uses to turn off password echoing when we are
2725                directly connected to it.  We reject this option
2726                if localLineEditing mode is on (always on in xboard)
2727                and we are talking to port 23, which might be a real
2728                telnet server that will try to keep WILL ECHO on permanently.
2729              */
2730             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2731                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2732                 unsigned char option;
2733                 oldi = i;
2734                 switch ((unsigned char) buf[++i]) {
2735                   case TN_WILL:
2736                     if (appData.debugMode)
2737                       fprintf(debugFP, "\n<WILL ");
2738                     switch (option = (unsigned char) buf[++i]) {
2739                       case TN_ECHO:
2740                         if (appData.debugMode)
2741                           fprintf(debugFP, "ECHO ");
2742                         /* Reply only if this is a change, according
2743                            to the protocol rules. */
2744                         if (remoteEchoOption) break;
2745                         if (appData.localLineEditing &&
2746                             atoi(appData.icsPort) == TN_PORT) {
2747                             TelnetRequest(TN_DONT, TN_ECHO);
2748                         } else {
2749                             EchoOff();
2750                             TelnetRequest(TN_DO, TN_ECHO);
2751                             remoteEchoOption = TRUE;
2752                         }
2753                         break;
2754                       default:
2755                         if (appData.debugMode)
2756                           fprintf(debugFP, "%d ", option);
2757                         /* Whatever this is, we don't want it. */
2758                         TelnetRequest(TN_DONT, option);
2759                         break;
2760                     }
2761                     break;
2762                   case TN_WONT:
2763                     if (appData.debugMode)
2764                       fprintf(debugFP, "\n<WONT ");
2765                     switch (option = (unsigned char) buf[++i]) {
2766                       case TN_ECHO:
2767                         if (appData.debugMode)
2768                           fprintf(debugFP, "ECHO ");
2769                         /* Reply only if this is a change, according
2770                            to the protocol rules. */
2771                         if (!remoteEchoOption) break;
2772                         EchoOn();
2773                         TelnetRequest(TN_DONT, TN_ECHO);
2774                         remoteEchoOption = FALSE;
2775                         break;
2776                       default:
2777                         if (appData.debugMode)
2778                           fprintf(debugFP, "%d ", (unsigned char) option);
2779                         /* Whatever this is, it must already be turned
2780                            off, because we never agree to turn on
2781                            anything non-default, so according to the
2782                            protocol rules, we don't reply. */
2783                         break;
2784                     }
2785                     break;
2786                   case TN_DO:
2787                     if (appData.debugMode)
2788                       fprintf(debugFP, "\n<DO ");
2789                     switch (option = (unsigned char) buf[++i]) {
2790                       default:
2791                         /* Whatever this is, we refuse to do it. */
2792                         if (appData.debugMode)
2793                           fprintf(debugFP, "%d ", option);
2794                         TelnetRequest(TN_WONT, option);
2795                         break;
2796                     }
2797                     break;
2798                   case TN_DONT:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<DONT ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       default:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "%d ", option);
2805                         /* Whatever this is, we are already not doing
2806                            it, because we never agree to do anything
2807                            non-default, so according to the protocol
2808                            rules, we don't reply. */
2809                         break;
2810                     }
2811                     break;
2812                   case TN_IAC:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<IAC ");
2815                     /* Doubled IAC; pass it through */
2816                     i--;
2817                     break;
2818                   default:
2819                     if (appData.debugMode)
2820                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2821                     /* Drop all other telnet commands on the floor */
2822                     break;
2823                 }
2824                 if (oldi > next_out)
2825                   SendToPlayer(&buf[next_out], oldi - next_out);
2826                 if (++i > next_out)
2827                   next_out = i;
2828                 continue;
2829             }
2830
2831             /* OK, this at least will *usually* work */
2832             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2833                 loggedOn = TRUE;
2834             }
2835
2836             if (loggedOn && !intfSet) {
2837                 if (ics_type == ICS_ICC) {
2838                   snprintf(str, MSG_SIZ,
2839                           "/set-quietly interface %s\n/set-quietly style 12\n",
2840                           programVersion);
2841                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2842                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2843                 } else if (ics_type == ICS_CHESSNET) {
2844                   snprintf(str, MSG_SIZ, "/style 12\n");
2845                 } else {
2846                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2847                   strcat(str, programVersion);
2848                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2849                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2850                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2851 #ifdef WIN32
2852                   strcat(str, "$iset nohighlight 1\n");
2853 #endif
2854                   strcat(str, "$iset lock 1\n$style 12\n");
2855                 }
2856                 SendToICS(str);
2857                 NotifyFrontendLogin();
2858                 intfSet = TRUE;
2859             }
2860
2861             if (started == STARTED_COMMENT) {
2862                 /* Accumulate characters in comment */
2863                 parse[parse_pos++] = buf[i];
2864                 if (buf[i] == '\n') {
2865                     parse[parse_pos] = NULLCHAR;
2866                     if(chattingPartner>=0) {
2867                         char mess[MSG_SIZ];
2868                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2869                         OutputChatMessage(chattingPartner, mess);
2870                         chattingPartner = -1;
2871                         next_out = i+1; // [HGM] suppress printing in ICS window
2872                     } else
2873                     if(!suppressKibitz) // [HGM] kibitz
2874                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2875                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2876                         int nrDigit = 0, nrAlph = 0, j;
2877                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2878                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2879                         parse[parse_pos] = NULLCHAR;
2880                         // try to be smart: if it does not look like search info, it should go to
2881                         // ICS interaction window after all, not to engine-output window.
2882                         for(j=0; j<parse_pos; j++) { // count letters and digits
2883                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2884                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2885                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2886                         }
2887                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2888                             int depth=0; float score;
2889                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2890                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2891                                 pvInfoList[forwardMostMove-1].depth = depth;
2892                                 pvInfoList[forwardMostMove-1].score = 100*score;
2893                             }
2894                             OutputKibitz(suppressKibitz, parse);
2895                         } else {
2896                             char tmp[MSG_SIZ];
2897                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2898                             SendToPlayer(tmp, strlen(tmp));
2899                         }
2900                         next_out = i+1; // [HGM] suppress printing in ICS window
2901                     }
2902                     started = STARTED_NONE;
2903                 } else {
2904                     /* Don't match patterns against characters in comment */
2905                     i++;
2906                     continue;
2907                 }
2908             }
2909             if (started == STARTED_CHATTER) {
2910                 if (buf[i] != '\n') {
2911                     /* Don't match patterns against characters in chatter */
2912                     i++;
2913                     continue;
2914                 }
2915                 started = STARTED_NONE;
2916                 if(suppressKibitz) next_out = i+1;
2917             }
2918
2919             /* Kludge to deal with rcmd protocol */
2920             if (firstTime && looking_at(buf, &i, "\001*")) {
2921                 DisplayFatalError(&buf[1], 0, 1);
2922                 continue;
2923             } else {
2924                 firstTime = FALSE;
2925             }
2926
2927             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2928                 ics_type = ICS_ICC;
2929                 ics_prefix = "/";
2930                 if (appData.debugMode)
2931                   fprintf(debugFP, "ics_type %d\n", ics_type);
2932                 continue;
2933             }
2934             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2935                 ics_type = ICS_FICS;
2936                 ics_prefix = "$";
2937                 if (appData.debugMode)
2938                   fprintf(debugFP, "ics_type %d\n", ics_type);
2939                 continue;
2940             }
2941             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2942                 ics_type = ICS_CHESSNET;
2943                 ics_prefix = "/";
2944                 if (appData.debugMode)
2945                   fprintf(debugFP, "ics_type %d\n", ics_type);
2946                 continue;
2947             }
2948
2949             if (!loggedOn &&
2950                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2951                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2952                  looking_at(buf, &i, "will be \"*\""))) {
2953               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2954               continue;
2955             }
2956
2957             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2958               char buf[MSG_SIZ];
2959               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2960               DisplayIcsInteractionTitle(buf);
2961               have_set_title = TRUE;
2962             }
2963
2964             /* skip finger notes */
2965             if (started == STARTED_NONE &&
2966                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2967                  (buf[i] == '1' && buf[i+1] == '0')) &&
2968                 buf[i+2] == ':' && buf[i+3] == ' ') {
2969               started = STARTED_CHATTER;
2970               i += 3;
2971               continue;
2972             }
2973
2974             oldi = i;
2975             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2976             if(appData.seekGraph) {
2977                 if(soughtPending && MatchSoughtLine(buf+i)) {
2978                     i = strstr(buf+i, "rated") - buf;
2979                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2980                     next_out = leftover_start = i;
2981                     started = STARTED_CHATTER;
2982                     suppressKibitz = TRUE;
2983                     continue;
2984                 }
2985                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2986                         && looking_at(buf, &i, "* ads displayed")) {
2987                     soughtPending = FALSE;
2988                     seekGraphUp = TRUE;
2989                     DrawSeekGraph();
2990                     continue;
2991                 }
2992                 if(appData.autoRefresh) {
2993                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2994                         int s = (ics_type == ICS_ICC); // ICC format differs
2995                         if(seekGraphUp)
2996                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2997                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2998                         looking_at(buf, &i, "*% "); // eat prompt
2999                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3000                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3001                         next_out = i; // suppress
3002                         continue;
3003                     }
3004                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3005                         char *p = star_match[0];
3006                         while(*p) {
3007                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3008                             while(*p && *p++ != ' '); // next
3009                         }
3010                         looking_at(buf, &i, "*% "); // eat prompt
3011                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3012                         next_out = i;
3013                         continue;
3014                     }
3015                 }
3016             }
3017
3018             /* skip formula vars */
3019             if (started == STARTED_NONE &&
3020                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3021               started = STARTED_CHATTER;
3022               i += 3;
3023               continue;
3024             }
3025
3026             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3027             if (appData.autoKibitz && started == STARTED_NONE &&
3028                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3029                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3030                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3031                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3032                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3033                         suppressKibitz = TRUE;
3034                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3035                         next_out = i;
3036                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3037                                 && (gameMode == IcsPlayingWhite)) ||
3038                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3039                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3040                             started = STARTED_CHATTER; // own kibitz we simply discard
3041                         else {
3042                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3043                             parse_pos = 0; parse[0] = NULLCHAR;
3044                             savingComment = TRUE;
3045                             suppressKibitz = gameMode != IcsObserving ? 2 :
3046                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3047                         }
3048                         continue;
3049                 } else
3050                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3051                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3052                          && atoi(star_match[0])) {
3053                     // suppress the acknowledgements of our own autoKibitz
3054                     char *p;
3055                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3057                     SendToPlayer(star_match[0], strlen(star_match[0]));
3058                     if(looking_at(buf, &i, "*% ")) // eat prompt
3059                         suppressKibitz = FALSE;
3060                     next_out = i;
3061                     continue;
3062                 }
3063             } // [HGM] kibitz: end of patch
3064
3065             // [HGM] chat: intercept tells by users for which we have an open chat window
3066             channel = -1;
3067             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3068                                            looking_at(buf, &i, "* whispers:") ||
3069                                            looking_at(buf, &i, "* kibitzes:") ||
3070                                            looking_at(buf, &i, "* shouts:") ||
3071                                            looking_at(buf, &i, "* c-shouts:") ||
3072                                            looking_at(buf, &i, "--> * ") ||
3073                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3074                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3075                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3076                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3077                 int p;
3078                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3079                 chattingPartner = -1;
3080
3081                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3082                 for(p=0; p<MAX_CHAT; p++) {
3083                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3084                     talker[0] = '['; strcat(talker, "] ");
3085                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3086                     chattingPartner = p; break;
3087                     }
3088                 } else
3089                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3090                 for(p=0; p<MAX_CHAT; p++) {
3091                     if(!strcmp("kibitzes", chatPartner[p])) {
3092                         talker[0] = '['; strcat(talker, "] ");
3093                         chattingPartner = p; break;
3094                     }
3095                 } else
3096                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3097                 for(p=0; p<MAX_CHAT; p++) {
3098                     if(!strcmp("whispers", chatPartner[p])) {
3099                         talker[0] = '['; strcat(talker, "] ");
3100                         chattingPartner = p; break;
3101                     }
3102                 } else
3103                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3104                   if(buf[i-8] == '-' && buf[i-3] == 't')
3105                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3106                     if(!strcmp("c-shouts", chatPartner[p])) {
3107                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3108                         chattingPartner = p; break;
3109                     }
3110                   }
3111                   if(chattingPartner < 0)
3112                   for(p=0; p<MAX_CHAT; p++) {
3113                     if(!strcmp("shouts", chatPartner[p])) {
3114                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3115                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3116                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3117                         chattingPartner = p; break;
3118                     }
3119                   }
3120                 }
3121                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3122                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3123                     talker[0] = 0; Colorize(ColorTell, FALSE);
3124                     chattingPartner = p; break;
3125                 }
3126                 if(chattingPartner<0) i = oldi; else {
3127                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3128                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3129                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3130                     started = STARTED_COMMENT;
3131                     parse_pos = 0; parse[0] = NULLCHAR;
3132                     savingComment = 3 + chattingPartner; // counts as TRUE
3133                     suppressKibitz = TRUE;
3134                     continue;
3135                 }
3136             } // [HGM] chat: end of patch
3137
3138           backup = i;
3139             if (appData.zippyTalk || appData.zippyPlay) {
3140                 /* [DM] Backup address for color zippy lines */
3141 #if ZIPPY
3142                if (loggedOn == TRUE)
3143                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3144                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3145 #endif
3146             } // [DM] 'else { ' deleted
3147                 if (
3148                     /* Regular tells and says */
3149                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3150                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3151                     looking_at(buf, &i, "* says: ") ||
3152                     /* Don't color "message" or "messages" output */
3153                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3154                     looking_at(buf, &i, "*. * at *:*: ") ||
3155                     looking_at(buf, &i, "--* (*:*): ") ||
3156                     /* Message notifications (same color as tells) */
3157                     looking_at(buf, &i, "* has left a message ") ||
3158                     looking_at(buf, &i, "* just sent you a message:\n") ||
3159                     /* Whispers and kibitzes */
3160                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3161                     looking_at(buf, &i, "* kibitzes: ") ||
3162                     /* Channel tells */
3163                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3164
3165                   if (tkind == 1 && strchr(star_match[0], ':')) {
3166                       /* Avoid "tells you:" spoofs in channels */
3167                      tkind = 3;
3168                   }
3169                   if (star_match[0][0] == NULLCHAR ||
3170                       strchr(star_match[0], ' ') ||
3171                       (tkind == 3 && strchr(star_match[1], ' '))) {
3172                     /* Reject bogus matches */
3173                     i = oldi;
3174                   } else {
3175                     if (appData.colorize) {
3176                       if (oldi > next_out) {
3177                         SendToPlayer(&buf[next_out], oldi - next_out);
3178                         next_out = oldi;
3179                       }
3180                       switch (tkind) {
3181                       case 1:
3182                         Colorize(ColorTell, FALSE);
3183                         curColor = ColorTell;
3184                         break;
3185                       case 2:
3186                         Colorize(ColorKibitz, FALSE);
3187                         curColor = ColorKibitz;
3188                         break;
3189                       case 3:
3190                         p = strrchr(star_match[1], '(');
3191                         if (p == NULL) {
3192                           p = star_match[1];
3193                         } else {
3194                           p++;
3195                         }
3196                         if (atoi(p) == 1) {
3197                           Colorize(ColorChannel1, FALSE);
3198                           curColor = ColorChannel1;
3199                         } else {
3200                           Colorize(ColorChannel, FALSE);
3201                           curColor = ColorChannel;
3202                         }
3203                         break;
3204                       case 5:
3205                         curColor = ColorNormal;
3206                         break;
3207                       }
3208                     }
3209                     if (started == STARTED_NONE && appData.autoComment &&
3210                         (gameMode == IcsObserving ||
3211                          gameMode == IcsPlayingWhite ||
3212                          gameMode == IcsPlayingBlack)) {
3213                       parse_pos = i - oldi;
3214                       memcpy(parse, &buf[oldi], parse_pos);
3215                       parse[parse_pos] = NULLCHAR;
3216                       started = STARTED_COMMENT;
3217                       savingComment = TRUE;
3218                     } else {
3219                       started = STARTED_CHATTER;
3220                       savingComment = FALSE;
3221                     }
3222                     loggedOn = TRUE;
3223                     continue;
3224                   }
3225                 }
3226
3227                 if (looking_at(buf, &i, "* s-shouts: ") ||
3228                     looking_at(buf, &i, "* c-shouts: ")) {
3229                     if (appData.colorize) {
3230                         if (oldi > next_out) {
3231                             SendToPlayer(&buf[next_out], oldi - next_out);
3232                             next_out = oldi;
3233                         }
3234                         Colorize(ColorSShout, FALSE);
3235                         curColor = ColorSShout;
3236                     }
3237                     loggedOn = TRUE;
3238                     started = STARTED_CHATTER;
3239                     continue;
3240                 }
3241
3242                 if (looking_at(buf, &i, "--->")) {
3243                     loggedOn = TRUE;
3244                     continue;
3245                 }
3246
3247                 if (looking_at(buf, &i, "* shouts: ") ||
3248                     looking_at(buf, &i, "--> ")) {
3249                     if (appData.colorize) {
3250                         if (oldi > next_out) {
3251                             SendToPlayer(&buf[next_out], oldi - next_out);
3252                             next_out = oldi;
3253                         }
3254                         Colorize(ColorShout, FALSE);
3255                         curColor = ColorShout;
3256                     }
3257                     loggedOn = TRUE;
3258                     started = STARTED_CHATTER;
3259                     continue;
3260                 }
3261
3262                 if (looking_at( buf, &i, "Challenge:")) {
3263                     if (appData.colorize) {
3264                         if (oldi > next_out) {
3265                             SendToPlayer(&buf[next_out], oldi - next_out);
3266                             next_out = oldi;
3267                         }
3268                         Colorize(ColorChallenge, FALSE);
3269                         curColor = ColorChallenge;
3270                     }
3271                     loggedOn = TRUE;
3272                     continue;
3273                 }
3274
3275                 if (looking_at(buf, &i, "* offers you") ||
3276                     looking_at(buf, &i, "* offers to be") ||
3277                     looking_at(buf, &i, "* would like to") ||
3278                     looking_at(buf, &i, "* requests to") ||
3279                     looking_at(buf, &i, "Your opponent offers") ||
3280                     looking_at(buf, &i, "Your opponent requests")) {
3281
3282                     if (appData.colorize) {
3283                         if (oldi > next_out) {
3284                             SendToPlayer(&buf[next_out], oldi - next_out);
3285                             next_out = oldi;
3286                         }
3287                         Colorize(ColorRequest, FALSE);
3288                         curColor = ColorRequest;
3289                     }
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "* (*) seeking")) {
3294                     if (appData.colorize) {
3295                         if (oldi > next_out) {
3296                             SendToPlayer(&buf[next_out], oldi - next_out);
3297                             next_out = oldi;
3298                         }
3299                         Colorize(ColorSeek, FALSE);
3300                         curColor = ColorSeek;
3301                     }
3302                     continue;
3303             }
3304
3305           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3306
3307             if (looking_at(buf, &i, "\\   ")) {
3308                 if (prevColor != ColorNormal) {
3309                     if (oldi > next_out) {
3310                         SendToPlayer(&buf[next_out], oldi - next_out);
3311                         next_out = oldi;
3312                     }
3313                     Colorize(prevColor, TRUE);
3314                     curColor = prevColor;
3315                 }
3316                 if (savingComment) {
3317                     parse_pos = i - oldi;
3318                     memcpy(parse, &buf[oldi], parse_pos);
3319                     parse[parse_pos] = NULLCHAR;
3320                     started = STARTED_COMMENT;
3321                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3322                         chattingPartner = savingComment - 3; // kludge to remember the box
3323                 } else {
3324                     started = STARTED_CHATTER;
3325                 }
3326                 continue;
3327             }
3328
3329             if (looking_at(buf, &i, "Black Strength :") ||
3330                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3331                 looking_at(buf, &i, "<10>") ||
3332                 looking_at(buf, &i, "#@#")) {
3333                 /* Wrong board style */
3334                 loggedOn = TRUE;
3335                 SendToICS(ics_prefix);
3336                 SendToICS("set style 12\n");
3337                 SendToICS(ics_prefix);
3338                 SendToICS("refresh\n");
3339                 continue;
3340             }
3341
3342             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3343                 ICSInitScript();
3344                 have_sent_ICS_logon = 1;
3345                 continue;
3346             }
3347
3348             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3349                 (looking_at(buf, &i, "\n<12> ") ||
3350                  looking_at(buf, &i, "<12> "))) {
3351                 loggedOn = TRUE;
3352                 if (oldi > next_out) {
3353                     SendToPlayer(&buf[next_out], oldi - next_out);
3354                 }
3355                 next_out = i;
3356                 started = STARTED_BOARD;
3357                 parse_pos = 0;
3358                 continue;
3359             }
3360
3361             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3362                 looking_at(buf, &i, "<b1> ")) {
3363                 if (oldi > next_out) {
3364                     SendToPlayer(&buf[next_out], oldi - next_out);
3365                 }
3366                 next_out = i;
3367                 started = STARTED_HOLDINGS;
3368                 parse_pos = 0;
3369                 continue;
3370             }
3371
3372             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3373                 loggedOn = TRUE;
3374                 /* Header for a move list -- first line */
3375
3376                 switch (ics_getting_history) {
3377                   case H_FALSE:
3378                     switch (gameMode) {
3379                       case IcsIdle:
3380                       case BeginningOfGame:
3381                         /* User typed "moves" or "oldmoves" while we
3382                            were idle.  Pretend we asked for these
3383                            moves and soak them up so user can step
3384                            through them and/or save them.
3385                            */
3386                         Reset(FALSE, TRUE);
3387                         gameMode = IcsObserving;
3388                         ModeHighlight();
3389                         ics_gamenum = -1;
3390                         ics_getting_history = H_GOT_UNREQ_HEADER;
3391                         break;
3392                       case EditGame: /*?*/
3393                       case EditPosition: /*?*/
3394                         /* Should above feature work in these modes too? */
3395                         /* For now it doesn't */
3396                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3397                         break;
3398                       default:
3399                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3400                         break;
3401                     }
3402                     break;
3403                   case H_REQUESTED:
3404                     /* Is this the right one? */
3405                     if (gameInfo.white && gameInfo.black &&
3406                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3407                         strcmp(gameInfo.black, star_match[2]) == 0) {
3408                         /* All is well */
3409                         ics_getting_history = H_GOT_REQ_HEADER;
3410                     }
3411                     break;
3412                   case H_GOT_REQ_HEADER:
3413                   case H_GOT_UNREQ_HEADER:
3414                   case H_GOT_UNWANTED_HEADER:
3415                   case H_GETTING_MOVES:
3416                     /* Should not happen */
3417                     DisplayError(_("Error gathering move list: two headers"), 0);
3418                     ics_getting_history = H_FALSE;
3419                     break;
3420                 }
3421
3422                 /* Save player ratings into gameInfo if needed */
3423                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3424                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3425                     (gameInfo.whiteRating == -1 ||
3426                      gameInfo.blackRating == -1)) {
3427
3428                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3429                     gameInfo.blackRating = string_to_rating(star_match[3]);
3430                     if (appData.debugMode)
3431                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3432                               gameInfo.whiteRating, gameInfo.blackRating);
3433                 }
3434                 continue;
3435             }
3436
3437             if (looking_at(buf, &i,
3438               "* * match, initial time: * minute*, increment: * second")) {
3439                 /* Header for a move list -- second line */
3440                 /* Initial board will follow if this is a wild game */
3441                 if (gameInfo.event != NULL) free(gameInfo.event);
3442                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3443                 gameInfo.event = StrSave(str);
3444                 /* [HGM] we switched variant. Translate boards if needed. */
3445                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3446                 continue;
3447             }
3448
3449             if (looking_at(buf, &i, "Move  ")) {
3450                 /* Beginning of a move list */
3451                 switch (ics_getting_history) {
3452                   case H_FALSE:
3453                     /* Normally should not happen */
3454                     /* Maybe user hit reset while we were parsing */
3455                     break;
3456                   case H_REQUESTED:
3457                     /* Happens if we are ignoring a move list that is not
3458                      * the one we just requested.  Common if the user
3459                      * tries to observe two games without turning off
3460                      * getMoveList */
3461                     break;
3462                   case H_GETTING_MOVES:
3463                     /* Should not happen */
3464                     DisplayError(_("Error gathering move list: nested"), 0);
3465                     ics_getting_history = H_FALSE;
3466                     break;
3467                   case H_GOT_REQ_HEADER:
3468                     ics_getting_history = H_GETTING_MOVES;
3469                     started = STARTED_MOVES;
3470                     parse_pos = 0;
3471                     if (oldi > next_out) {
3472                         SendToPlayer(&buf[next_out], oldi - next_out);
3473                     }
3474                     break;
3475                   case H_GOT_UNREQ_HEADER:
3476                     ics_getting_history = H_GETTING_MOVES;
3477                     started = STARTED_MOVES_NOHIDE;
3478                     parse_pos = 0;
3479                     break;
3480                   case H_GOT_UNWANTED_HEADER:
3481                     ics_getting_history = H_FALSE;
3482                     break;
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i, "% ") ||
3488                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3489                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3490                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3491                     soughtPending = FALSE;
3492                     seekGraphUp = TRUE;
3493                     DrawSeekGraph();
3494                 }
3495                 if(suppressKibitz) next_out = i;
3496                 savingComment = FALSE;
3497                 suppressKibitz = 0;
3498                 switch (started) {
3499                   case STARTED_MOVES:
3500                   case STARTED_MOVES_NOHIDE:
3501                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3502                     parse[parse_pos + i - oldi] = NULLCHAR;
3503                     ParseGameHistory(parse);
3504 #if ZIPPY
3505                     if (appData.zippyPlay && first.initDone) {
3506                         FeedMovesToProgram(&first, forwardMostMove);
3507                         if (gameMode == IcsPlayingWhite) {
3508                             if (WhiteOnMove(forwardMostMove)) {
3509                                 if (first.sendTime) {
3510                                   if (first.useColors) {
3511                                     SendToProgram("black\n", &first);
3512                                   }
3513                                   SendTimeRemaining(&first, TRUE);
3514                                 }
3515                                 if (first.useColors) {
3516                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3517                                 }
3518                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3519                                 first.maybeThinking = TRUE;
3520                             } else {
3521                                 if (first.usePlayother) {
3522                                   if (first.sendTime) {
3523                                     SendTimeRemaining(&first, TRUE);
3524                                   }
3525                                   SendToProgram("playother\n", &first);
3526                                   firstMove = FALSE;
3527                                 } else {
3528                                   firstMove = TRUE;
3529                                 }
3530                             }
3531                         } else if (gameMode == IcsPlayingBlack) {
3532                             if (!WhiteOnMove(forwardMostMove)) {
3533                                 if (first.sendTime) {
3534                                   if (first.useColors) {
3535                                     SendToProgram("white\n", &first);
3536                                   }
3537                                   SendTimeRemaining(&first, FALSE);
3538                                 }
3539                                 if (first.useColors) {
3540                                   SendToProgram("black\n", &first);
3541                                 }
3542                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3543                                 first.maybeThinking = TRUE;
3544                             } else {
3545                                 if (first.usePlayother) {
3546                                   if (first.sendTime) {
3547                                     SendTimeRemaining(&first, FALSE);
3548                                   }
3549                                   SendToProgram("playother\n", &first);
3550                                   firstMove = FALSE;
3551                                 } else {
3552                                   firstMove = TRUE;
3553                                 }
3554                             }
3555                         }
3556                     }
3557 #endif
3558                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3559                         /* Moves came from oldmoves or moves command
3560                            while we weren't doing anything else.
3561                            */
3562                         currentMove = forwardMostMove;
3563                         ClearHighlights();/*!!could figure this out*/
3564                         flipView = appData.flipView;
3565                         DrawPosition(TRUE, boards[currentMove]);
3566                         DisplayBothClocks();
3567                         snprintf(str, MSG_SIZ, "%s vs. %s",
3568                                 gameInfo.white, gameInfo.black);
3569                         DisplayTitle(str);
3570                         gameMode = IcsIdle;
3571                     } else {
3572                         /* Moves were history of an active game */
3573                         if (gameInfo.resultDetails != NULL) {
3574                             free(gameInfo.resultDetails);
3575                             gameInfo.resultDetails = NULL;
3576                         }
3577                     }
3578                     HistorySet(parseList, backwardMostMove,
3579                                forwardMostMove, currentMove-1);
3580                     DisplayMove(currentMove - 1);
3581                     if (started == STARTED_MOVES) next_out = i;
3582                     started = STARTED_NONE;
3583                     ics_getting_history = H_FALSE;
3584                     break;
3585
3586                   case STARTED_OBSERVE:
3587                     started = STARTED_NONE;
3588                     SendToICS(ics_prefix);
3589                     SendToICS("refresh\n");
3590                     break;
3591
3592                   default:
3593                     break;
3594                 }
3595                 if(bookHit) { // [HGM] book: simulate book reply
3596                     static char bookMove[MSG_SIZ]; // a bit generous?
3597
3598                     programStats.nodes = programStats.depth = programStats.time =
3599                     programStats.score = programStats.got_only_move = 0;
3600                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3601
3602                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3603                     strcat(bookMove, bookHit);
3604                     HandleMachineMove(bookMove, &first);
3605                 }
3606                 continue;
3607             }
3608
3609             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3610                  started == STARTED_HOLDINGS ||
3611                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3612                 /* Accumulate characters in move list or board */
3613                 parse[parse_pos++] = buf[i];
3614             }
3615
3616             /* Start of game messages.  Mostly we detect start of game
3617                when the first board image arrives.  On some versions
3618                of the ICS, though, we need to do a "refresh" after starting
3619                to observe in order to get the current board right away. */
3620             if (looking_at(buf, &i, "Adding game * to observation list")) {
3621                 started = STARTED_OBSERVE;
3622                 continue;
3623             }
3624
3625             /* Handle auto-observe */
3626             if (appData.autoObserve &&
3627                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3628                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3629                 char *player;
3630                 /* Choose the player that was highlighted, if any. */
3631                 if (star_match[0][0] == '\033' ||
3632                     star_match[1][0] != '\033') {
3633                     player = star_match[0];
3634                 } else {
3635                     player = star_match[2];
3636                 }
3637                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3638                         ics_prefix, StripHighlightAndTitle(player));
3639                 SendToICS(str);
3640
3641                 /* Save ratings from notify string */
3642                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3643                 player1Rating = string_to_rating(star_match[1]);
3644                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3645                 player2Rating = string_to_rating(star_match[3]);
3646
3647                 if (appData.debugMode)
3648                   fprintf(debugFP,
3649                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3650                           player1Name, player1Rating,
3651                           player2Name, player2Rating);
3652
3653                 continue;
3654             }
3655
3656             /* Deal with automatic examine mode after a game,
3657                and with IcsObserving -> IcsExamining transition */
3658             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3659                 looking_at(buf, &i, "has made you an examiner of game *")) {
3660
3661                 int gamenum = atoi(star_match[0]);
3662                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3663                     gamenum == ics_gamenum) {
3664                     /* We were already playing or observing this game;
3665                        no need to refetch history */
3666                     gameMode = IcsExamining;
3667                     if (pausing) {
3668                         pauseExamForwardMostMove = forwardMostMove;
3669                     } else if (currentMove < forwardMostMove) {
3670                         ForwardInner(forwardMostMove);
3671                     }
3672                 } else {
3673                     /* I don't think this case really can happen */
3674                     SendToICS(ics_prefix);
3675                     SendToICS("refresh\n");
3676                 }
3677                 continue;
3678             }
3679
3680             /* Error messages */
3681 //          if (ics_user_moved) {
3682             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3683                 if (looking_at(buf, &i, "Illegal move") ||
3684                     looking_at(buf, &i, "Not a legal move") ||
3685                     looking_at(buf, &i, "Your king is in check") ||
3686                     looking_at(buf, &i, "It isn't your turn") ||
3687                     looking_at(buf, &i, "It is not your move")) {
3688                     /* Illegal move */
3689                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3690                         currentMove = forwardMostMove-1;
3691                         DisplayMove(currentMove - 1); /* before DMError */
3692                         DrawPosition(FALSE, boards[currentMove]);
3693                         SwitchClocks(forwardMostMove-1); // [HGM] race
3694                         DisplayBothClocks();
3695                     }
3696                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3697                     ics_user_moved = 0;
3698                     continue;
3699                 }
3700             }
3701
3702             if (looking_at(buf, &i, "still have time") ||
3703                 looking_at(buf, &i, "not out of time") ||
3704                 looking_at(buf, &i, "either player is out of time") ||
3705                 looking_at(buf, &i, "has timeseal; checking")) {
3706                 /* We must have called his flag a little too soon */
3707                 whiteFlag = blackFlag = FALSE;
3708                 continue;
3709             }
3710
3711             if (looking_at(buf, &i, "added * seconds to") ||
3712                 looking_at(buf, &i, "seconds were added to")) {
3713                 /* Update the clocks */
3714                 SendToICS(ics_prefix);
3715                 SendToICS("refresh\n");
3716                 continue;
3717             }
3718
3719             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3720                 ics_clock_paused = TRUE;
3721                 StopClocks();
3722                 continue;
3723             }
3724
3725             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3726                 ics_clock_paused = FALSE;
3727                 StartClocks();
3728                 continue;
3729             }
3730
3731             /* Grab player ratings from the Creating: message.
3732                Note we have to check for the special case when
3733                the ICS inserts things like [white] or [black]. */
3734             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3735                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3736                 /* star_matches:
3737                    0    player 1 name (not necessarily white)
3738                    1    player 1 rating
3739                    2    empty, white, or black (IGNORED)
3740                    3    player 2 name (not necessarily black)
3741                    4    player 2 rating
3742
3743                    The names/ratings are sorted out when the game
3744                    actually starts (below).
3745                 */
3746                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3747                 player1Rating = string_to_rating(star_match[1]);
3748                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3749                 player2Rating = string_to_rating(star_match[4]);
3750
3751                 if (appData.debugMode)
3752                   fprintf(debugFP,
3753                           "Ratings from 'Creating:' %s %d, %s %d\n",
3754                           player1Name, player1Rating,
3755                           player2Name, player2Rating);
3756
3757                 continue;
3758             }
3759
3760             /* Improved generic start/end-of-game messages */
3761             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3762                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3763                 /* If tkind == 0: */
3764                 /* star_match[0] is the game number */
3765                 /*           [1] is the white player's name */
3766                 /*           [2] is the black player's name */
3767                 /* For end-of-game: */
3768                 /*           [3] is the reason for the game end */
3769                 /*           [4] is a PGN end game-token, preceded by " " */
3770                 /* For start-of-game: */
3771                 /*           [3] begins with "Creating" or "Continuing" */
3772                 /*           [4] is " *" or empty (don't care). */
3773                 int gamenum = atoi(star_match[0]);
3774                 char *whitename, *blackname, *why, *endtoken;
3775                 ChessMove endtype = EndOfFile;
3776
3777                 if (tkind == 0) {
3778                   whitename = star_match[1];
3779                   blackname = star_match[2];
3780                   why = star_match[3];
3781                   endtoken = star_match[4];
3782                 } else {
3783                   whitename = star_match[1];
3784                   blackname = star_match[3];
3785                   why = star_match[5];
3786                   endtoken = star_match[6];
3787                 }
3788
3789                 /* Game start messages */
3790                 if (strncmp(why, "Creating ", 9) == 0 ||
3791                     strncmp(why, "Continuing ", 11) == 0) {
3792                     gs_gamenum = gamenum;
3793                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3794                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3795 #if ZIPPY
3796                     if (appData.zippyPlay) {
3797                         ZippyGameStart(whitename, blackname);
3798                     }
3799 #endif /*ZIPPY*/
3800                     partnerBoardValid = FALSE; // [HGM] bughouse
3801                     continue;
3802                 }
3803
3804                 /* Game end messages */
3805                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3806                     ics_gamenum != gamenum) {
3807                     continue;
3808                 }
3809                 while (endtoken[0] == ' ') endtoken++;
3810                 switch (endtoken[0]) {
3811                   case '*':
3812                   default:
3813                     endtype = GameUnfinished;
3814                     break;
3815                   case '0':
3816                     endtype = BlackWins;
3817                     break;
3818                   case '1':
3819                     if (endtoken[1] == '/')
3820                       endtype = GameIsDrawn;
3821                     else
3822                       endtype = WhiteWins;
3823                     break;
3824                 }
3825                 GameEnds(endtype, why, GE_ICS);
3826 #if ZIPPY
3827                 if (appData.zippyPlay && first.initDone) {
3828                     ZippyGameEnd(endtype, why);
3829                     if (first.pr == NULL) {
3830                       /* Start the next process early so that we'll
3831                          be ready for the next challenge */
3832                       StartChessProgram(&first);
3833                     }
3834                     /* Send "new" early, in case this command takes
3835                        a long time to finish, so that we'll be ready
3836                        for the next challenge. */
3837                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3838                     Reset(TRUE, TRUE);
3839                 }
3840 #endif /*ZIPPY*/
3841                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3842                 continue;
3843             }
3844
3845             if (looking_at(buf, &i, "Removing game * from observation") ||
3846                 looking_at(buf, &i, "no longer observing game *") ||
3847                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3848                 if (gameMode == IcsObserving &&
3849                     atoi(star_match[0]) == ics_gamenum)
3850                   {
3851                       /* icsEngineAnalyze */
3852                       if (appData.icsEngineAnalyze) {
3853                             ExitAnalyzeMode();
3854                             ModeHighlight();
3855                       }
3856                       StopClocks();
3857                       gameMode = IcsIdle;
3858                       ics_gamenum = -1;
3859                       ics_user_moved = FALSE;
3860                   }
3861                 continue;
3862             }
3863
3864             if (looking_at(buf, &i, "no longer examining game *")) {
3865                 if (gameMode == IcsExamining &&
3866                     atoi(star_match[0]) == ics_gamenum)
3867                   {
3868                       gameMode = IcsIdle;
3869                       ics_gamenum = -1;
3870                       ics_user_moved = FALSE;
3871                   }
3872                 continue;
3873             }
3874
3875             /* Advance leftover_start past any newlines we find,
3876                so only partial lines can get reparsed */
3877             if (looking_at(buf, &i, "\n")) {
3878                 prevColor = curColor;
3879                 if (curColor != ColorNormal) {
3880                     if (oldi > next_out) {
3881                         SendToPlayer(&buf[next_out], oldi - next_out);
3882                         next_out = oldi;
3883                     }
3884                     Colorize(ColorNormal, FALSE);
3885                     curColor = ColorNormal;
3886                 }
3887                 if (started == STARTED_BOARD) {
3888                     started = STARTED_NONE;
3889                     parse[parse_pos] = NULLCHAR;
3890                     ParseBoard12(parse);
3891                     ics_user_moved = 0;
3892
3893                     /* Send premove here */
3894                     if (appData.premove) {
3895                       char str[MSG_SIZ];
3896                       if (currentMove == 0 &&
3897                           gameMode == IcsPlayingWhite &&
3898                           appData.premoveWhite) {
3899                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3900                         if (appData.debugMode)
3901                           fprintf(debugFP, "Sending premove:\n");
3902                         SendToICS(str);
3903                       } else if (currentMove == 1 &&
3904                                  gameMode == IcsPlayingBlack &&
3905                                  appData.premoveBlack) {
3906                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3907                         if (appData.debugMode)
3908                           fprintf(debugFP, "Sending premove:\n");
3909                         SendToICS(str);
3910                       } else if (gotPremove) {
3911                         gotPremove = 0;
3912                         ClearPremoveHighlights();
3913                         if (appData.debugMode)
3914                           fprintf(debugFP, "Sending premove:\n");
3915                           UserMoveEvent(premoveFromX, premoveFromY,
3916                                         premoveToX, premoveToY,
3917                                         premovePromoChar);
3918                       }
3919                     }
3920
3921                     /* Usually suppress following prompt */
3922                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3923                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3924                         if (looking_at(buf, &i, "*% ")) {
3925                             savingComment = FALSE;
3926                             suppressKibitz = 0;
3927                         }
3928                     }
3929                     next_out = i;
3930                 } else if (started == STARTED_HOLDINGS) {
3931                     int gamenum;
3932                     char new_piece[MSG_SIZ];
3933                     started = STARTED_NONE;
3934                     parse[parse_pos] = NULLCHAR;
3935                     if (appData.debugMode)
3936                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3937                                                         parse, currentMove);
3938                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3939                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3940                         if (gameInfo.variant == VariantNormal) {
3941                           /* [HGM] We seem to switch variant during a game!
3942                            * Presumably no holdings were displayed, so we have
3943                            * to move the position two files to the right to
3944                            * create room for them!
3945                            */
3946                           VariantClass newVariant;
3947                           switch(gameInfo.boardWidth) { // base guess on board width
3948                                 case 9:  newVariant = VariantShogi; break;
3949                                 case 10: newVariant = VariantGreat; break;
3950                                 default: newVariant = VariantCrazyhouse; break;
3951                           }
3952                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3953                           /* Get a move list just to see the header, which
3954                              will tell us whether this is really bug or zh */
3955                           if (ics_getting_history == H_FALSE) {
3956                             ics_getting_history = H_REQUESTED;
3957                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3958                             SendToICS(str);
3959                           }
3960                         }
3961                         new_piece[0] = NULLCHAR;
3962                         sscanf(parse, "game %d white [%s black [%s <- %s",
3963                                &gamenum, white_holding, black_holding,
3964                                new_piece);
3965                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3966                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3967                         /* [HGM] copy holdings to board holdings area */
3968                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3969                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3970                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3971 #if ZIPPY
3972                         if (appData.zippyPlay && first.initDone) {
3973                             ZippyHoldings(white_holding, black_holding,
3974                                           new_piece);
3975                         }
3976 #endif /*ZIPPY*/
3977                         if (tinyLayout || smallLayout) {
3978                             char wh[16], bh[16];
3979                             PackHolding(wh, white_holding);
3980                             PackHolding(bh, black_holding);
3981                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3982                                     gameInfo.white, gameInfo.black);
3983                         } else {
3984                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3985                                     gameInfo.white, white_holding,
3986                                     gameInfo.black, black_holding);
3987                         }
3988                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3989                         DrawPosition(FALSE, boards[currentMove]);
3990                         DisplayTitle(str);
3991                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3992                         sscanf(parse, "game %d white [%s black [%s <- %s",
3993                                &gamenum, white_holding, black_holding,
3994                                new_piece);
3995                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3996                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3997                         /* [HGM] copy holdings to partner-board holdings area */
3998                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3999                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4000                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4001                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4002                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4003                       }
4004                     }
4005                     /* Suppress following prompt */
4006                     if (looking_at(buf, &i, "*% ")) {
4007                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4008                         savingComment = FALSE;
4009                         suppressKibitz = 0;
4010                     }
4011                     next_out = i;
4012                 }
4013                 continue;
4014             }
4015
4016             i++;                /* skip unparsed character and loop back */
4017         }
4018
4019         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4020 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4021 //          SendToPlayer(&buf[next_out], i - next_out);
4022             started != STARTED_HOLDINGS && leftover_start > next_out) {
4023             SendToPlayer(&buf[next_out], leftover_start - next_out);
4024             next_out = i;
4025         }
4026
4027         leftover_len = buf_len - leftover_start;
4028         /* if buffer ends with something we couldn't parse,
4029            reparse it after appending the next read */
4030
4031     } else if (count == 0) {
4032         RemoveInputSource(isr);
4033         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4034     } else {
4035         DisplayFatalError(_("Error reading from ICS"), error, 1);
4036     }
4037 }
4038
4039
4040 /* Board style 12 looks like this:
4041
4042    <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
4043
4044  * The "<12> " is stripped before it gets to this routine.  The two
4045  * trailing 0's (flip state and clock ticking) are later addition, and
4046  * some chess servers may not have them, or may have only the first.
4047  * Additional trailing fields may be added in the future.
4048  */
4049
4050 #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"
4051
4052 #define RELATION_OBSERVING_PLAYED    0
4053 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4054 #define RELATION_PLAYING_MYMOVE      1
4055 #define RELATION_PLAYING_NOTMYMOVE  -1
4056 #define RELATION_EXAMINING           2
4057 #define RELATION_ISOLATED_BOARD     -3
4058 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4059
4060 void
4061 ParseBoard12(string)
4062      char *string;
4063 {
4064     GameMode newGameMode;
4065     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4066     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4067     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4068     char to_play, board_chars[200];
4069     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4070     char black[32], white[32];
4071     Board board;
4072     int prevMove = currentMove;
4073     int ticking = 2;
4074     ChessMove moveType;
4075     int fromX, fromY, toX, toY;
4076     char promoChar;
4077     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4078     char *bookHit = NULL; // [HGM] book
4079     Boolean weird = FALSE, reqFlag = FALSE;
4080
4081     fromX = fromY = toX = toY = -1;
4082
4083     newGame = FALSE;
4084
4085     if (appData.debugMode)
4086       fprintf(debugFP, _("Parsing board: %s\n"), string);
4087
4088     move_str[0] = NULLCHAR;
4089     elapsed_time[0] = NULLCHAR;
4090     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4091         int  i = 0, j;
4092         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4093             if(string[i] == ' ') { ranks++; files = 0; }
4094             else files++;
4095             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4096             i++;
4097         }
4098         for(j = 0; j <i; j++) board_chars[j] = string[j];
4099         board_chars[i] = '\0';
4100         string += i + 1;
4101     }
4102     n = sscanf(string, PATTERN, &to_play, &double_push,
4103                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4104                &gamenum, white, black, &relation, &basetime, &increment,
4105                &white_stren, &black_stren, &white_time, &black_time,
4106                &moveNum, str, elapsed_time, move_str, &ics_flip,
4107                &ticking);
4108
4109     if (n < 21) {
4110         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4111         DisplayError(str, 0);
4112         return;
4113     }
4114
4115     /* Convert the move number to internal form */
4116     moveNum = (moveNum - 1) * 2;
4117     if (to_play == 'B') moveNum++;
4118     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4119       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4120                         0, 1);
4121       return;
4122     }
4123
4124     switch (relation) {
4125       case RELATION_OBSERVING_PLAYED:
4126       case RELATION_OBSERVING_STATIC:
4127         if (gamenum == -1) {
4128             /* Old ICC buglet */
4129             relation = RELATION_OBSERVING_STATIC;
4130         }
4131         newGameMode = IcsObserving;
4132         break;
4133       case RELATION_PLAYING_MYMOVE:
4134       case RELATION_PLAYING_NOTMYMOVE:
4135         newGameMode =
4136           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4137             IcsPlayingWhite : IcsPlayingBlack;
4138         break;
4139       case RELATION_EXAMINING:
4140         newGameMode = IcsExamining;
4141         break;
4142       case RELATION_ISOLATED_BOARD:
4143       default:
4144         /* Just display this board.  If user was doing something else,
4145            we will forget about it until the next board comes. */
4146         newGameMode = IcsIdle;
4147         break;
4148       case RELATION_STARTING_POSITION:
4149         newGameMode = gameMode;
4150         break;
4151     }
4152
4153     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4154          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4155       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4156       char *toSqr;
4157       for (k = 0; k < ranks; k++) {
4158         for (j = 0; j < files; j++)
4159           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4160         if(gameInfo.holdingsWidth > 1) {
4161              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4162              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4163         }
4164       }
4165       CopyBoard(partnerBoard, board);
4166       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4167         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4168         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4169       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4170       if(toSqr = strchr(str, '-')) {
4171         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4172         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4173       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4174       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4175       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4176       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4177       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4178       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4179                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4180       DisplayMessage(partnerStatus, "");
4181         partnerBoardValid = TRUE;
4182       return;
4183     }
4184
4185     /* Modify behavior for initial board display on move listing
4186        of wild games.
4187        */
4188     switch (ics_getting_history) {
4189       case H_FALSE:
4190       case H_REQUESTED:
4191         break;
4192       case H_GOT_REQ_HEADER:
4193       case H_GOT_UNREQ_HEADER:
4194         /* This is the initial position of the current game */
4195         gamenum = ics_gamenum;
4196         moveNum = 0;            /* old ICS bug workaround */
4197         if (to_play == 'B') {
4198           startedFromSetupPosition = TRUE;
4199           blackPlaysFirst = TRUE;
4200           moveNum = 1;
4201           if (forwardMostMove == 0) forwardMostMove = 1;
4202           if (backwardMostMove == 0) backwardMostMove = 1;
4203           if (currentMove == 0) currentMove = 1;
4204         }
4205         newGameMode = gameMode;
4206         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4207         break;
4208       case H_GOT_UNWANTED_HEADER:
4209         /* This is an initial board that we don't want */
4210         return;
4211       case H_GETTING_MOVES:
4212         /* Should not happen */
4213         DisplayError(_("Error gathering move list: extra board"), 0);
4214         ics_getting_history = H_FALSE;
4215         return;
4216     }
4217
4218    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4219                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4220      /* [HGM] We seem to have switched variant unexpectedly
4221       * Try to guess new variant from board size
4222       */
4223           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4224           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4225           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4226           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4227           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4228           if(!weird) newVariant = VariantNormal;
4229           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4230           /* Get a move list just to see the header, which
4231              will tell us whether this is really bug or zh */
4232           if (ics_getting_history == H_FALSE) {
4233             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4234             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4235             SendToICS(str);
4236           }
4237     }
4238
4239     /* Take action if this is the first board of a new game, or of a
4240        different game than is currently being displayed.  */
4241     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4242         relation == RELATION_ISOLATED_BOARD) {
4243
4244         /* Forget the old game and get the history (if any) of the new one */
4245         if (gameMode != BeginningOfGame) {
4246           Reset(TRUE, TRUE);
4247         }
4248         newGame = TRUE;
4249         if (appData.autoRaiseBoard) BoardToTop();
4250         prevMove = -3;
4251         if (gamenum == -1) {
4252             newGameMode = IcsIdle;
4253         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4254                    appData.getMoveList && !reqFlag) {
4255             /* Need to get game history */
4256             ics_getting_history = H_REQUESTED;
4257             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4258             SendToICS(str);
4259         }
4260
4261         /* Initially flip the board to have black on the bottom if playing
4262            black or if the ICS flip flag is set, but let the user change
4263            it with the Flip View button. */
4264         flipView = appData.autoFlipView ?
4265           (newGameMode == IcsPlayingBlack) || ics_flip :
4266           appData.flipView;
4267
4268         /* Done with values from previous mode; copy in new ones */
4269         gameMode = newGameMode;
4270         ModeHighlight();
4271         ics_gamenum = gamenum;
4272         if (gamenum == gs_gamenum) {
4273             int klen = strlen(gs_kind);
4274             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4275             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4276             gameInfo.event = StrSave(str);
4277         } else {
4278             gameInfo.event = StrSave("ICS game");
4279         }
4280         gameInfo.site = StrSave(appData.icsHost);
4281         gameInfo.date = PGNDate();
4282         gameInfo.round = StrSave("-");
4283         gameInfo.white = StrSave(white);
4284         gameInfo.black = StrSave(black);
4285         timeControl = basetime * 60 * 1000;
4286         timeControl_2 = 0;
4287         timeIncrement = increment * 1000;
4288         movesPerSession = 0;
4289         gameInfo.timeControl = TimeControlTagValue();
4290         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4291   if (appData.debugMode) {
4292     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4293     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4294     setbuf(debugFP, NULL);
4295   }
4296
4297         gameInfo.outOfBook = NULL;
4298
4299         /* Do we have the ratings? */
4300         if (strcmp(player1Name, white) == 0 &&
4301             strcmp(player2Name, black) == 0) {
4302             if (appData.debugMode)
4303               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4304                       player1Rating, player2Rating);
4305             gameInfo.whiteRating = player1Rating;
4306             gameInfo.blackRating = player2Rating;
4307         } else if (strcmp(player2Name, white) == 0 &&
4308                    strcmp(player1Name, black) == 0) {
4309             if (appData.debugMode)
4310               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4311                       player2Rating, player1Rating);
4312             gameInfo.whiteRating = player2Rating;
4313             gameInfo.blackRating = player1Rating;
4314         }
4315         player1Name[0] = player2Name[0] = NULLCHAR;
4316
4317         /* Silence shouts if requested */
4318         if (appData.quietPlay &&
4319             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4320             SendToICS(ics_prefix);
4321             SendToICS("set shout 0\n");
4322         }
4323     }
4324
4325     /* Deal with midgame name changes */
4326     if (!newGame) {
4327         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4328             if (gameInfo.white) free(gameInfo.white);
4329             gameInfo.white = StrSave(white);
4330         }
4331         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4332             if (gameInfo.black) free(gameInfo.black);
4333             gameInfo.black = StrSave(black);
4334         }
4335     }
4336
4337     /* Throw away game result if anything actually changes in examine mode */
4338     if (gameMode == IcsExamining && !newGame) {
4339         gameInfo.result = GameUnfinished;
4340         if (gameInfo.resultDetails != NULL) {
4341             free(gameInfo.resultDetails);
4342             gameInfo.resultDetails = NULL;
4343         }
4344     }
4345
4346     /* In pausing && IcsExamining mode, we ignore boards coming
4347        in if they are in a different variation than we are. */
4348     if (pauseExamInvalid) return;
4349     if (pausing && gameMode == IcsExamining) {
4350         if (moveNum <= pauseExamForwardMostMove) {
4351             pauseExamInvalid = TRUE;
4352             forwardMostMove = pauseExamForwardMostMove;
4353             return;
4354         }
4355     }
4356
4357   if (appData.debugMode) {
4358     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4359   }
4360     /* Parse the board */
4361     for (k = 0; k < ranks; k++) {
4362       for (j = 0; j < files; j++)
4363         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4364       if(gameInfo.holdingsWidth > 1) {
4365            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4366            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4367       }
4368     }
4369     CopyBoard(boards[moveNum], board);
4370     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4371     if (moveNum == 0) {
4372         startedFromSetupPosition =
4373           !CompareBoards(board, initialPosition);
4374         if(startedFromSetupPosition)
4375             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4376     }
4377
4378     /* [HGM] Set castling rights. Take the outermost Rooks,
4379        to make it also work for FRC opening positions. Note that board12
4380        is really defective for later FRC positions, as it has no way to
4381        indicate which Rook can castle if they are on the same side of King.
4382        For the initial position we grant rights to the outermost Rooks,
4383        and remember thos rights, and we then copy them on positions
4384        later in an FRC game. This means WB might not recognize castlings with
4385        Rooks that have moved back to their original position as illegal,
4386        but in ICS mode that is not its job anyway.
4387     */
4388     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4389     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4390
4391         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4392             if(board[0][i] == WhiteRook) j = i;
4393         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4394         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4395             if(board[0][i] == WhiteRook) j = i;
4396         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4397         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4398             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4399         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4400         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4401             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4402         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4403
4404         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4405         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4406             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4407         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4408             if(board[BOARD_HEIGHT-1][k] == bKing)
4409                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4410         if(gameInfo.variant == VariantTwoKings) {
4411             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4412             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4413             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4414         }
4415     } else { int r;
4416         r = boards[moveNum][CASTLING][0] = initialRights[0];
4417         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4418         r = boards[moveNum][CASTLING][1] = initialRights[1];
4419         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4420         r = boards[moveNum][CASTLING][3] = initialRights[3];
4421         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4422         r = boards[moveNum][CASTLING][4] = initialRights[4];
4423         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4424         /* wildcastle kludge: always assume King has rights */
4425         r = boards[moveNum][CASTLING][2] = initialRights[2];
4426         r = boards[moveNum][CASTLING][5] = initialRights[5];
4427     }
4428     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4429     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4430
4431
4432     if (ics_getting_history == H_GOT_REQ_HEADER ||
4433         ics_getting_history == H_GOT_UNREQ_HEADER) {
4434         /* This was an initial position from a move list, not
4435            the current position */
4436         return;
4437     }
4438
4439     /* Update currentMove and known move number limits */
4440     newMove = newGame || moveNum > forwardMostMove;
4441
4442     if (newGame) {
4443         forwardMostMove = backwardMostMove = currentMove = moveNum;
4444         if (gameMode == IcsExamining && moveNum == 0) {
4445           /* Workaround for ICS limitation: we are not told the wild
4446              type when starting to examine a game.  But if we ask for
4447              the move list, the move list header will tell us */
4448             ics_getting_history = H_REQUESTED;
4449             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4450             SendToICS(str);
4451         }
4452     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4453                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4454 #if ZIPPY
4455         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4456         /* [HGM] applied this also to an engine that is silently watching        */
4457         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4458             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4459             gameInfo.variant == currentlyInitializedVariant) {
4460           takeback = forwardMostMove - moveNum;
4461           for (i = 0; i < takeback; i++) {
4462             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4463             SendToProgram("undo\n", &first);
4464           }
4465         }
4466 #endif
4467
4468         forwardMostMove = moveNum;
4469         if (!pausing || currentMove > forwardMostMove)
4470           currentMove = forwardMostMove;
4471     } else {
4472         /* New part of history that is not contiguous with old part */
4473         if (pausing && gameMode == IcsExamining) {
4474             pauseExamInvalid = TRUE;
4475             forwardMostMove = pauseExamForwardMostMove;
4476             return;
4477         }
4478         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4479 #if ZIPPY
4480             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4481                 // [HGM] when we will receive the move list we now request, it will be
4482                 // fed to the engine from the first move on. So if the engine is not
4483                 // in the initial position now, bring it there.
4484                 InitChessProgram(&first, 0);
4485             }
4486 #endif
4487             ics_getting_history = H_REQUESTED;
4488             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4489             SendToICS(str);
4490         }
4491         forwardMostMove = backwardMostMove = currentMove = moveNum;
4492     }
4493
4494     /* Update the clocks */
4495     if (strchr(elapsed_time, '.')) {
4496       /* Time is in ms */
4497       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4498       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4499     } else {
4500       /* Time is in seconds */
4501       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4502       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4503     }
4504
4505
4506 #if ZIPPY
4507     if (appData.zippyPlay && newGame &&
4508         gameMode != IcsObserving && gameMode != IcsIdle &&
4509         gameMode != IcsExamining)
4510       ZippyFirstBoard(moveNum, basetime, increment);
4511 #endif
4512
4513     /* Put the move on the move list, first converting
4514        to canonical algebraic form. */
4515     if (moveNum > 0) {
4516   if (appData.debugMode) {
4517     if (appData.debugMode) { int f = forwardMostMove;
4518         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4519                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4520                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4521     }
4522     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4523     fprintf(debugFP, "moveNum = %d\n", moveNum);
4524     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4525     setbuf(debugFP, NULL);
4526   }
4527         if (moveNum <= backwardMostMove) {
4528             /* We don't know what the board looked like before
4529                this move.  Punt. */
4530           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4531             strcat(parseList[moveNum - 1], " ");
4532             strcat(parseList[moveNum - 1], elapsed_time);
4533             moveList[moveNum - 1][0] = NULLCHAR;
4534         } else if (strcmp(move_str, "none") == 0) {
4535             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4536             /* Again, we don't know what the board looked like;
4537                this is really the start of the game. */
4538             parseList[moveNum - 1][0] = NULLCHAR;
4539             moveList[moveNum - 1][0] = NULLCHAR;
4540             backwardMostMove = moveNum;
4541             startedFromSetupPosition = TRUE;
4542             fromX = fromY = toX = toY = -1;
4543         } else {
4544           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4545           //                 So we parse the long-algebraic move string in stead of the SAN move
4546           int valid; char buf[MSG_SIZ], *prom;
4547
4548           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4549                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4550           // str looks something like "Q/a1-a2"; kill the slash
4551           if(str[1] == '/')
4552             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4553           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4554           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4555                 strcat(buf, prom); // long move lacks promo specification!
4556           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4557                 if(appData.debugMode)
4558                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4559                 safeStrCpy(move_str, buf, MSG_SIZ);
4560           }
4561           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4562                                 &fromX, &fromY, &toX, &toY, &promoChar)
4563                || ParseOneMove(buf, moveNum - 1, &moveType,
4564                                 &fromX, &fromY, &toX, &toY, &promoChar);
4565           // end of long SAN patch
4566           if (valid) {
4567             (void) CoordsToAlgebraic(boards[moveNum - 1],
4568                                      PosFlags(moveNum - 1),
4569                                      fromY, fromX, toY, toX, promoChar,
4570                                      parseList[moveNum-1]);
4571             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4572               case MT_NONE:
4573               case MT_STALEMATE:
4574               default:
4575                 break;
4576               case MT_CHECK:
4577                 if(gameInfo.variant != VariantShogi)
4578                     strcat(parseList[moveNum - 1], "+");
4579                 break;
4580               case MT_CHECKMATE:
4581               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4582                 strcat(parseList[moveNum - 1], "#");
4583                 break;
4584             }
4585             strcat(parseList[moveNum - 1], " ");
4586             strcat(parseList[moveNum - 1], elapsed_time);
4587             /* currentMoveString is set as a side-effect of ParseOneMove */
4588             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4589             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4590             strcat(moveList[moveNum - 1], "\n");
4591
4592             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4593                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4594               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4595                 ChessSquare old, new = boards[moveNum][k][j];
4596                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4597                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4598                   if(old == new) continue;
4599                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4600                   else if(new == WhiteWazir || new == BlackWazir) {
4601                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4602                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4603                       else boards[moveNum][k][j] = old; // preserve type of Gold
4604                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4605                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4606               }
4607           } else {
4608             /* Move from ICS was illegal!?  Punt. */
4609             if (appData.debugMode) {
4610               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4611               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4612             }
4613             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4614             strcat(parseList[moveNum - 1], " ");
4615             strcat(parseList[moveNum - 1], elapsed_time);
4616             moveList[moveNum - 1][0] = NULLCHAR;
4617             fromX = fromY = toX = toY = -1;
4618           }
4619         }
4620   if (appData.debugMode) {
4621     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4622     setbuf(debugFP, NULL);
4623   }
4624
4625 #if ZIPPY
4626         /* Send move to chess program (BEFORE animating it). */
4627         if (appData.zippyPlay && !newGame && newMove &&
4628            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4629
4630             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4631                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4632                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4633                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4634                             move_str);
4635                     DisplayError(str, 0);
4636                 } else {
4637                     if (first.sendTime) {
4638                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4639                     }
4640                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4641                     if (firstMove && !bookHit) {
4642                         firstMove = FALSE;
4643                         if (first.useColors) {
4644                           SendToProgram(gameMode == IcsPlayingWhite ?
4645                                         "white\ngo\n" :
4646                                         "black\ngo\n", &first);
4647                         } else {
4648                           SendToProgram("go\n", &first);
4649                         }
4650                         first.maybeThinking = TRUE;
4651                     }
4652                 }
4653             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4654               if (moveList[moveNum - 1][0] == NULLCHAR) {
4655                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4656                 DisplayError(str, 0);
4657               } else {
4658                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4659                 SendMoveToProgram(moveNum - 1, &first);
4660               }
4661             }
4662         }
4663 #endif
4664     }
4665
4666     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4667         /* If move comes from a remote source, animate it.  If it
4668            isn't remote, it will have already been animated. */
4669         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4670             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4671         }
4672         if (!pausing && appData.highlightLastMove) {
4673             SetHighlights(fromX, fromY, toX, toY);
4674         }
4675     }
4676
4677     /* Start the clocks */
4678     whiteFlag = blackFlag = FALSE;
4679     appData.clockMode = !(basetime == 0 && increment == 0);
4680     if (ticking == 0) {
4681       ics_clock_paused = TRUE;
4682       StopClocks();
4683     } else if (ticking == 1) {
4684       ics_clock_paused = FALSE;
4685     }
4686     if (gameMode == IcsIdle ||
4687         relation == RELATION_OBSERVING_STATIC ||
4688         relation == RELATION_EXAMINING ||
4689         ics_clock_paused)
4690       DisplayBothClocks();
4691     else
4692       StartClocks();
4693
4694     /* Display opponents and material strengths */
4695     if (gameInfo.variant != VariantBughouse &&
4696         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4697         if (tinyLayout || smallLayout) {
4698             if(gameInfo.variant == VariantNormal)
4699               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4700                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4701                     basetime, increment);
4702             else
4703               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4704                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4705                     basetime, increment, (int) gameInfo.variant);
4706         } else {
4707             if(gameInfo.variant == VariantNormal)
4708               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4709                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4710                     basetime, increment);
4711             else
4712               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4713                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4714                     basetime, increment, VariantName(gameInfo.variant));
4715         }
4716         DisplayTitle(str);
4717   if (appData.debugMode) {
4718     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4719   }
4720     }
4721
4722
4723     /* Display the board */
4724     if (!pausing && !appData.noGUI) {
4725
4726       if (appData.premove)
4727           if (!gotPremove ||
4728              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4729              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4730               ClearPremoveHighlights();
4731
4732       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4733         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4734       DrawPosition(j, boards[currentMove]);
4735
4736       DisplayMove(moveNum - 1);
4737       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4738             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4739               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4740         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4741       }
4742     }
4743
4744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4745 #if ZIPPY
4746     if(bookHit) { // [HGM] book: simulate book reply
4747         static char bookMove[MSG_SIZ]; // a bit generous?
4748
4749         programStats.nodes = programStats.depth = programStats.time =
4750         programStats.score = programStats.got_only_move = 0;
4751         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4752
4753         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4754         strcat(bookMove, bookHit);
4755         HandleMachineMove(bookMove, &first);
4756     }
4757 #endif
4758 }
4759
4760 void
4761 GetMoveListEvent()
4762 {
4763     char buf[MSG_SIZ];
4764     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4765         ics_getting_history = H_REQUESTED;
4766         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4767         SendToICS(buf);
4768     }
4769 }
4770
4771 void
4772 AnalysisPeriodicEvent(force)
4773      int force;
4774 {
4775     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4776          && !force) || !appData.periodicUpdates)
4777       return;
4778
4779     /* Send . command to Crafty to collect stats */
4780     SendToProgram(".\n", &first);
4781
4782     /* Don't send another until we get a response (this makes
4783        us stop sending to old Crafty's which don't understand
4784        the "." command (sending illegal cmds resets node count & time,
4785        which looks bad)) */
4786     programStats.ok_to_send = 0;
4787 }
4788
4789 void ics_update_width(new_width)
4790         int new_width;
4791 {
4792         ics_printf("set width %d\n", new_width);
4793 }
4794
4795 void
4796 SendMoveToProgram(moveNum, cps)
4797      int moveNum;
4798      ChessProgramState *cps;
4799 {
4800     char buf[MSG_SIZ];
4801
4802     if (cps->useUsermove) {
4803       SendToProgram("usermove ", cps);
4804     }
4805     if (cps->useSAN) {
4806       char *space;
4807       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4808         int len = space - parseList[moveNum];
4809         memcpy(buf, parseList[moveNum], len);
4810         buf[len++] = '\n';
4811         buf[len] = NULLCHAR;
4812       } else {
4813         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4814       }
4815       SendToProgram(buf, cps);
4816     } else {
4817       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4818         AlphaRank(moveList[moveNum], 4);
4819         SendToProgram(moveList[moveNum], cps);
4820         AlphaRank(moveList[moveNum], 4); // and back
4821       } else
4822       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4823        * the engine. It would be nice to have a better way to identify castle
4824        * moves here. */
4825       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4826                                                                          && cps->useOOCastle) {
4827         int fromX = moveList[moveNum][0] - AAA;
4828         int fromY = moveList[moveNum][1] - ONE;
4829         int toX = moveList[moveNum][2] - AAA;
4830         int toY = moveList[moveNum][3] - ONE;
4831         if((boards[moveNum][fromY][fromX] == WhiteKing
4832             && boards[moveNum][toY][toX] == WhiteRook)
4833            || (boards[moveNum][fromY][fromX] == BlackKing
4834                && boards[moveNum][toY][toX] == BlackRook)) {
4835           if(toX > fromX) SendToProgram("O-O\n", cps);
4836           else SendToProgram("O-O-O\n", cps);
4837         }
4838         else SendToProgram(moveList[moveNum], cps);
4839       }
4840       else SendToProgram(moveList[moveNum], cps);
4841       /* End of additions by Tord */
4842     }
4843
4844     /* [HGM] setting up the opening has brought engine in force mode! */
4845     /*       Send 'go' if we are in a mode where machine should play. */
4846     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4847         (gameMode == TwoMachinesPlay   ||
4848 #if ZIPPY
4849          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4850 #endif
4851          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4852         SendToProgram("go\n", cps);
4853   if (appData.debugMode) {
4854     fprintf(debugFP, "(extra)\n");
4855   }
4856     }
4857     setboardSpoiledMachineBlack = 0;
4858 }
4859
4860 void
4861 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4862      ChessMove moveType;
4863      int fromX, fromY, toX, toY;
4864      char promoChar;
4865 {
4866     char user_move[MSG_SIZ];
4867
4868     switch (moveType) {
4869       default:
4870         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4871                 (int)moveType, fromX, fromY, toX, toY);
4872         DisplayError(user_move + strlen("say "), 0);
4873         break;
4874       case WhiteKingSideCastle:
4875       case BlackKingSideCastle:
4876       case WhiteQueenSideCastleWild:
4877       case BlackQueenSideCastleWild:
4878       /* PUSH Fabien */
4879       case WhiteHSideCastleFR:
4880       case BlackHSideCastleFR:
4881       /* POP Fabien */
4882         snprintf(user_move, MSG_SIZ, "o-o\n");
4883         break;
4884       case WhiteQueenSideCastle:
4885       case BlackQueenSideCastle:
4886       case WhiteKingSideCastleWild:
4887       case BlackKingSideCastleWild:
4888       /* PUSH Fabien */
4889       case WhiteASideCastleFR:
4890       case BlackASideCastleFR:
4891       /* POP Fabien */
4892         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4893         break;
4894       case WhiteNonPromotion:
4895       case BlackNonPromotion:
4896         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4897         break;
4898       case WhitePromotion:
4899       case BlackPromotion:
4900         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4901           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4902                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4903                 PieceToChar(WhiteFerz));
4904         else if(gameInfo.variant == VariantGreat)
4905           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4906                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4907                 PieceToChar(WhiteMan));
4908         else
4909           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4910                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4911                 promoChar);
4912         break;
4913       case WhiteDrop:
4914       case BlackDrop:
4915       drop:
4916         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4917                  ToUpper(PieceToChar((ChessSquare) fromX)),
4918                  AAA + toX, ONE + toY);
4919         break;
4920       case IllegalMove:  /* could be a variant we don't quite understand */
4921         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4922       case NormalMove:
4923       case WhiteCapturesEnPassant:
4924       case BlackCapturesEnPassant:
4925         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4926                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4927         break;
4928     }
4929     SendToICS(user_move);
4930     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4931         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4932 }
4933
4934 void
4935 UploadGameEvent()
4936 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4937     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4938     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4939     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4940         DisplayError("You cannot do this while you are playing or observing", 0);
4941         return;
4942     }
4943     if(gameMode != IcsExamining) { // is this ever not the case?
4944         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4945
4946         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4947           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4948         } else { // on FICS we must first go to general examine mode
4949           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4950         }
4951         if(gameInfo.variant != VariantNormal) {
4952             // try figure out wild number, as xboard names are not always valid on ICS
4953             for(i=1; i<=36; i++) {
4954               snprintf(buf, MSG_SIZ, "wild/%d", i);
4955                 if(StringToVariant(buf) == gameInfo.variant) break;
4956             }
4957             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4958             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4959             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4960         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4961         SendToICS(ics_prefix);
4962         SendToICS(buf);
4963         if(startedFromSetupPosition || backwardMostMove != 0) {
4964           fen = PositionToFEN(backwardMostMove, NULL);
4965           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4966             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4967             SendToICS(buf);
4968           } else { // FICS: everything has to set by separate bsetup commands
4969             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4970             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4971             SendToICS(buf);
4972             if(!WhiteOnMove(backwardMostMove)) {
4973                 SendToICS("bsetup tomove black\n");
4974             }
4975             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4976             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4977             SendToICS(buf);
4978             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4979             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4980             SendToICS(buf);
4981             i = boards[backwardMostMove][EP_STATUS];
4982             if(i >= 0) { // set e.p.
4983               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4984                 SendToICS(buf);
4985             }
4986             bsetup++;
4987           }
4988         }
4989       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4990             SendToICS("bsetup done\n"); // switch to normal examining.
4991     }
4992     for(i = backwardMostMove; i<last; i++) {
4993         char buf[20];
4994         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4995         SendToICS(buf);
4996     }
4997     SendToICS(ics_prefix);
4998     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4999 }
5000
5001 void
5002 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5003      int rf, ff, rt, ft;
5004      char promoChar;
5005      char move[7];
5006 {
5007     if (rf == DROP_RANK) {
5008       sprintf(move, "%c@%c%c\n",
5009                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5010     } else {
5011         if (promoChar == 'x' || promoChar == NULLCHAR) {
5012           sprintf(move, "%c%c%c%c\n",
5013                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5014         } else {
5015             sprintf(move, "%c%c%c%c%c\n",
5016                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5017         }
5018     }
5019 }
5020
5021 void
5022 ProcessICSInitScript(f)
5023      FILE *f;
5024 {
5025     char buf[MSG_SIZ];
5026
5027     while (fgets(buf, MSG_SIZ, f)) {
5028         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5029     }
5030
5031     fclose(f);
5032 }
5033
5034
5035 static int lastX, lastY, selectFlag, dragging;
5036
5037 void
5038 Sweep(int step)
5039 {
5040     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5041     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5042     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5043     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5044     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5045     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5046     do {
5047         promoSweep -= step;
5048         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5049         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5050         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5051         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5052         if(!step) step = 1;
5053     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5054             appData.testLegality && (promoSweep == king ||
5055             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5056     ChangeDragPiece(promoSweep);
5057 }
5058
5059 int PromoScroll(int x, int y)
5060 {
5061   int step = 0;
5062
5063   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5064   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5065   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5066   if(!step) return FALSE;
5067   lastX = x; lastY = y;
5068   if((promoSweep < BlackPawn) == flipView) step = -step;
5069   if(step > 0) selectFlag = 1;
5070   if(!selectFlag) Sweep(step);
5071   return FALSE;
5072 }
5073
5074 void
5075 NextPiece(int step)
5076 {
5077     ChessSquare piece = boards[currentMove][toY][toX];
5078     do {
5079         pieceSweep -= step;
5080         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5081         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5082         if(!step) step = -1;
5083     } while(PieceToChar(pieceSweep) == '.');
5084     boards[currentMove][toY][toX] = pieceSweep;
5085     DrawPosition(FALSE, boards[currentMove]);
5086     boards[currentMove][toY][toX] = piece;
5087 }
5088 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5089 void
5090 AlphaRank(char *move, int n)
5091 {
5092 //    char *p = move, c; int x, y;
5093
5094     if (appData.debugMode) {
5095         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5096     }
5097
5098     if(move[1]=='*' &&
5099        move[2]>='0' && move[2]<='9' &&
5100        move[3]>='a' && move[3]<='x'    ) {
5101         move[1] = '@';
5102         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5103         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5104     } else
5105     if(move[0]>='0' && move[0]<='9' &&
5106        move[1]>='a' && move[1]<='x' &&
5107        move[2]>='0' && move[2]<='9' &&
5108        move[3]>='a' && move[3]<='x'    ) {
5109         /* input move, Shogi -> normal */
5110         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5111         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5112         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5113         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5114     } else
5115     if(move[1]=='@' &&
5116        move[3]>='0' && move[3]<='9' &&
5117        move[2]>='a' && move[2]<='x'    ) {
5118         move[1] = '*';
5119         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5120         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5121     } else
5122     if(
5123        move[0]>='a' && move[0]<='x' &&
5124        move[3]>='0' && move[3]<='9' &&
5125        move[2]>='a' && move[2]<='x'    ) {
5126          /* output move, normal -> Shogi */
5127         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5128         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5129         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5130         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5131         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5132     }
5133     if (appData.debugMode) {
5134         fprintf(debugFP, "   out = '%s'\n", move);
5135     }
5136 }
5137
5138 char yy_textstr[8000];
5139
5140 /* Parser for moves from gnuchess, ICS, or user typein box */
5141 Boolean
5142 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5143      char *move;
5144      int moveNum;
5145      ChessMove *moveType;
5146      int *fromX, *fromY, *toX, *toY;
5147      char *promoChar;
5148 {
5149     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5150
5151     switch (*moveType) {
5152       case WhitePromotion:
5153       case BlackPromotion:
5154       case WhiteNonPromotion:
5155       case BlackNonPromotion:
5156       case NormalMove:
5157       case WhiteCapturesEnPassant:
5158       case BlackCapturesEnPassant:
5159       case WhiteKingSideCastle:
5160       case WhiteQueenSideCastle:
5161       case BlackKingSideCastle:
5162       case BlackQueenSideCastle:
5163       case WhiteKingSideCastleWild:
5164       case WhiteQueenSideCastleWild:
5165       case BlackKingSideCastleWild:
5166       case BlackQueenSideCastleWild:
5167       /* Code added by Tord: */
5168       case WhiteHSideCastleFR:
5169       case WhiteASideCastleFR:
5170       case BlackHSideCastleFR:
5171       case BlackASideCastleFR:
5172       /* End of code added by Tord */
5173       case IllegalMove:         /* bug or odd chess variant */
5174         *fromX = currentMoveString[0] - AAA;
5175         *fromY = currentMoveString[1] - ONE;
5176         *toX = currentMoveString[2] - AAA;
5177         *toY = currentMoveString[3] - ONE;
5178         *promoChar = currentMoveString[4];
5179         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5180             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5183     }
5184             *fromX = *fromY = *toX = *toY = 0;
5185             return FALSE;
5186         }
5187         if (appData.testLegality) {
5188           return (*moveType != IllegalMove);
5189         } else {
5190           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5191                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5192         }
5193
5194       case WhiteDrop:
5195       case BlackDrop:
5196         *fromX = *moveType == WhiteDrop ?
5197           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5198           (int) CharToPiece(ToLower(currentMoveString[0]));
5199         *fromY = DROP_RANK;
5200         *toX = currentMoveString[2] - AAA;
5201         *toY = currentMoveString[3] - ONE;
5202         *promoChar = NULLCHAR;
5203         return TRUE;
5204
5205       case AmbiguousMove:
5206       case ImpossibleMove:
5207       case EndOfFile:
5208       case ElapsedTime:
5209       case Comment:
5210       case PGNTag:
5211       case NAG:
5212       case WhiteWins:
5213       case BlackWins:
5214       case GameIsDrawn:
5215       default:
5216     if (appData.debugMode) {
5217         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5218     }
5219         /* bug? */
5220         *fromX = *fromY = *toX = *toY = 0;
5221         *promoChar = NULLCHAR;
5222         return FALSE;
5223     }
5224 }
5225
5226 Boolean pushed = FALSE;
5227
5228 void
5229 ParsePV(char *pv, Boolean storeComments)
5230 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5231   int fromX, fromY, toX, toY; char promoChar;
5232   ChessMove moveType;
5233   Boolean valid;
5234   int nr = 0;
5235
5236   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5237     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5238     pushed = TRUE;
5239   }
5240   endPV = forwardMostMove;
5241   do {
5242     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5243     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5244     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5245 if(appData.debugMode){
5246 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);
5247 }
5248     if(!valid && nr == 0 &&
5249        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5250         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5251         // Hande case where played move is different from leading PV move
5252         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5253         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5254         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5255         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5256           endPV += 2; // if position different, keep this
5257           moveList[endPV-1][0] = fromX + AAA;
5258           moveList[endPV-1][1] = fromY + ONE;
5259           moveList[endPV-1][2] = toX + AAA;
5260           moveList[endPV-1][3] = toY + ONE;
5261           parseList[endPV-1][0] = NULLCHAR;
5262           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5263         }
5264       }
5265     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5266     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5267     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5268     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5269         valid++; // allow comments in PV
5270         continue;
5271     }
5272     nr++;
5273     if(endPV+1 > framePtr) break; // no space, truncate
5274     if(!valid) break;
5275     endPV++;
5276     CopyBoard(boards[endPV], boards[endPV-1]);
5277     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5278     moveList[endPV-1][0] = fromX + AAA;
5279     moveList[endPV-1][1] = fromY + ONE;
5280     moveList[endPV-1][2] = toX + AAA;
5281     moveList[endPV-1][3] = toY + ONE;
5282     moveList[endPV-1][4] = promoChar;
5283     moveList[endPV-1][5] = NULLCHAR;
5284     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5285     if(storeComments)
5286         CoordsToAlgebraic(boards[endPV - 1],
5287                              PosFlags(endPV - 1),
5288                              fromY, fromX, toY, toX, promoChar,
5289                              parseList[endPV - 1]);
5290     else
5291         parseList[endPV-1][0] = NULLCHAR;
5292   } while(valid);
5293   currentMove = endPV;
5294   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5295   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5296                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5297   DrawPosition(TRUE, boards[currentMove]);
5298 }
5299
5300 Boolean
5301 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5302 {
5303         int startPV;
5304         char *p;
5305
5306         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5307         lastX = x; lastY = y;
5308         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5309         startPV = index;
5310         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5311         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5312         index = startPV;
5313         do{ while(buf[index] && buf[index] != '\n') index++;
5314         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5315         buf[index] = 0;
5316         ParsePV(buf+startPV, FALSE);
5317         *start = startPV; *end = index-1;
5318         return TRUE;
5319 }
5320
5321 Boolean
5322 LoadPV(int x, int y)
5323 { // called on right mouse click to load PV
5324   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5325   lastX = x; lastY = y;
5326   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5327   return TRUE;
5328 }
5329
5330 void
5331 UnLoadPV()
5332 {
5333   if(endPV < 0) return;
5334   endPV = -1;
5335   currentMove = forwardMostMove;
5336   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5337   ClearPremoveHighlights();
5338   DrawPosition(TRUE, boards[currentMove]);
5339 }
5340
5341 void
5342 MovePV(int x, int y, int h)
5343 { // step through PV based on mouse coordinates (called on mouse move)
5344   int margin = h>>3, step = 0;
5345
5346   // we must somehow check if right button is still down (might be released off board!)
5347   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5348   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5349   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5350   if(!step) return;
5351   lastX = x; lastY = y;
5352
5353   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5354   if(endPV < 0) return;
5355   if(y < margin) step = 1; else
5356   if(y > h - margin) step = -1;
5357   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5358   currentMove += step;
5359   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5360   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5361                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5362   DrawPosition(FALSE, boards[currentMove]);
5363 }
5364
5365
5366 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5367 // All positions will have equal probability, but the current method will not provide a unique
5368 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5369 #define DARK 1
5370 #define LITE 2
5371 #define ANY 3
5372
5373 int squaresLeft[4];
5374 int piecesLeft[(int)BlackPawn];
5375 int seed, nrOfShuffles;
5376
5377 void GetPositionNumber()
5378 {       // sets global variable seed
5379         int i;
5380
5381         seed = appData.defaultFrcPosition;
5382         if(seed < 0) { // randomize based on time for negative FRC position numbers
5383                 for(i=0; i<50; i++) seed += random();
5384                 seed = random() ^ random() >> 8 ^ random() << 8;
5385                 if(seed<0) seed = -seed;
5386         }
5387 }
5388
5389 int put(Board board, int pieceType, int rank, int n, int shade)
5390 // put the piece on the (n-1)-th empty squares of the given shade
5391 {
5392         int i;
5393
5394         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5395                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5396                         board[rank][i] = (ChessSquare) pieceType;
5397                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5398                         squaresLeft[ANY]--;
5399                         piecesLeft[pieceType]--;
5400                         return i;
5401                 }
5402         }
5403         return -1;
5404 }
5405
5406
5407 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5408 // calculate where the next piece goes, (any empty square), and put it there
5409 {
5410         int i;
5411
5412         i = seed % squaresLeft[shade];
5413         nrOfShuffles *= squaresLeft[shade];
5414         seed /= squaresLeft[shade];
5415         put(board, pieceType, rank, i, shade);
5416 }
5417
5418 void AddTwoPieces(Board board, int pieceType, int rank)
5419 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5420 {
5421         int i, n=squaresLeft[ANY], j=n-1, k;
5422
5423         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5424         i = seed % k;  // pick one
5425         nrOfShuffles *= k;
5426         seed /= k;
5427         while(i >= j) i -= j--;
5428         j = n - 1 - j; i += j;
5429         put(board, pieceType, rank, j, ANY);
5430         put(board, pieceType, rank, i, ANY);
5431 }
5432
5433 void SetUpShuffle(Board board, int number)
5434 {
5435         int i, p, first=1;
5436
5437         GetPositionNumber(); nrOfShuffles = 1;
5438
5439         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5440         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5441         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5442
5443         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5444
5445         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5446             p = (int) board[0][i];
5447             if(p < (int) BlackPawn) piecesLeft[p] ++;
5448             board[0][i] = EmptySquare;
5449         }
5450
5451         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5452             // shuffles restricted to allow normal castling put KRR first
5453             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5454                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5455             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5456                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5457             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5458                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5459             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5460                 put(board, WhiteRook, 0, 0, ANY);
5461             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5462         }
5463
5464         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5465             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5466             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5467                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5468                 while(piecesLeft[p] >= 2) {
5469                     AddOnePiece(board, p, 0, LITE);
5470                     AddOnePiece(board, p, 0, DARK);
5471                 }
5472                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5473             }
5474
5475         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5476             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5477             // but we leave King and Rooks for last, to possibly obey FRC restriction
5478             if(p == (int)WhiteRook) continue;
5479             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5480             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5481         }
5482
5483         // now everything is placed, except perhaps King (Unicorn) and Rooks
5484
5485         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5486             // Last King gets castling rights
5487             while(piecesLeft[(int)WhiteUnicorn]) {
5488                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5489                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5490             }
5491
5492             while(piecesLeft[(int)WhiteKing]) {
5493                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5494                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5495             }
5496
5497
5498         } else {
5499             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5500             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5501         }
5502
5503         // Only Rooks can be left; simply place them all
5504         while(piecesLeft[(int)WhiteRook]) {
5505                 i = put(board, WhiteRook, 0, 0, ANY);
5506                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5507                         if(first) {
5508                                 first=0;
5509                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5510                         }
5511                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5512                 }
5513         }
5514         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5515             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5516         }
5517
5518         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5519 }
5520
5521 int SetCharTable( char *table, const char * map )
5522 /* [HGM] moved here from winboard.c because of its general usefulness */
5523 /*       Basically a safe strcpy that uses the last character as King */
5524 {
5525     int result = FALSE; int NrPieces;
5526
5527     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5528                     && NrPieces >= 12 && !(NrPieces&1)) {
5529         int i; /* [HGM] Accept even length from 12 to 34 */
5530
5531         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5532         for( i=0; i<NrPieces/2-1; i++ ) {
5533             table[i] = map[i];
5534             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5535         }
5536         table[(int) WhiteKing]  = map[NrPieces/2-1];
5537         table[(int) BlackKing]  = map[NrPieces-1];
5538
5539         result = TRUE;
5540     }
5541
5542     return result;
5543 }
5544
5545 void Prelude(Board board)
5546 {       // [HGM] superchess: random selection of exo-pieces
5547         int i, j, k; ChessSquare p;
5548         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5549
5550         GetPositionNumber(); // use FRC position number
5551
5552         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5553             SetCharTable(pieceToChar, appData.pieceToCharTable);
5554             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5555                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5556         }
5557
5558         j = seed%4;                 seed /= 4;
5559         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5562         j = seed%3 + (seed%3 >= j); seed /= 3;
5563         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5566         j = seed%3;                 seed /= 3;
5567         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5568         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5569         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5570         j = seed%2 + (seed%2 >= j); seed /= 2;
5571         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5572         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5573         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5574         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5575         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5576         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5577         put(board, exoPieces[0],    0, 0, ANY);
5578         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5579 }
5580
5581 void
5582 InitPosition(redraw)
5583      int redraw;
5584 {
5585     ChessSquare (* pieces)[BOARD_FILES];
5586     int i, j, pawnRow, overrule,
5587     oldx = gameInfo.boardWidth,
5588     oldy = gameInfo.boardHeight,
5589     oldh = gameInfo.holdingsWidth;
5590     static int oldv;
5591
5592     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5593
5594     /* [AS] Initialize pv info list [HGM] and game status */
5595     {
5596         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5597             pvInfoList[i].depth = 0;
5598             boards[i][EP_STATUS] = EP_NONE;
5599             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5600         }
5601
5602         initialRulePlies = 0; /* 50-move counter start */
5603
5604         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5605         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5606     }
5607
5608
5609     /* [HGM] logic here is completely changed. In stead of full positions */
5610     /* the initialized data only consist of the two backranks. The switch */
5611     /* selects which one we will use, which is than copied to the Board   */
5612     /* initialPosition, which for the rest is initialized by Pawns and    */
5613     /* empty squares. This initial position is then copied to boards[0],  */
5614     /* possibly after shuffling, so that it remains available.            */
5615
5616     gameInfo.holdingsWidth = 0; /* default board sizes */
5617     gameInfo.boardWidth    = 8;
5618     gameInfo.boardHeight   = 8;
5619     gameInfo.holdingsSize  = 0;
5620     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5621     for(i=0; i<BOARD_FILES-2; i++)
5622       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5623     initialPosition[EP_STATUS] = EP_NONE;
5624     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5625     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5626          SetCharTable(pieceNickName, appData.pieceNickNames);
5627     else SetCharTable(pieceNickName, "............");
5628     pieces = FIDEArray;
5629
5630     switch (gameInfo.variant) {
5631     case VariantFischeRandom:
5632       shuffleOpenings = TRUE;
5633     default:
5634       break;
5635     case VariantShatranj:
5636       pieces = ShatranjArray;
5637       nrCastlingRights = 0;
5638       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5639       break;
5640     case VariantMakruk:
5641       pieces = makrukArray;
5642       nrCastlingRights = 0;
5643       startedFromSetupPosition = TRUE;
5644       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5645       break;
5646     case VariantTwoKings:
5647       pieces = twoKingsArray;
5648       break;
5649     case VariantCapaRandom:
5650       shuffleOpenings = TRUE;
5651     case VariantCapablanca:
5652       pieces = CapablancaArray;
5653       gameInfo.boardWidth = 10;
5654       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5655       break;
5656     case VariantGothic:
5657       pieces = GothicArray;
5658       gameInfo.boardWidth = 10;
5659       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5660       break;
5661     case VariantSChess:
5662       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5663       gameInfo.holdingsSize = 7;
5664       break;
5665     case VariantJanus:
5666       pieces = JanusArray;
5667       gameInfo.boardWidth = 10;
5668       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5669       nrCastlingRights = 6;
5670         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5671         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5672         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5673         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5674         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5675         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5676       break;
5677     case VariantFalcon:
5678       pieces = FalconArray;
5679       gameInfo.boardWidth = 10;
5680       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5681       break;
5682     case VariantXiangqi:
5683       pieces = XiangqiArray;
5684       gameInfo.boardWidth  = 9;
5685       gameInfo.boardHeight = 10;
5686       nrCastlingRights = 0;
5687       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5688       break;
5689     case VariantShogi:
5690       pieces = ShogiArray;
5691       gameInfo.boardWidth  = 9;
5692       gameInfo.boardHeight = 9;
5693       gameInfo.holdingsSize = 7;
5694       nrCastlingRights = 0;
5695       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5696       break;
5697     case VariantCourier:
5698       pieces = CourierArray;
5699       gameInfo.boardWidth  = 12;
5700       nrCastlingRights = 0;
5701       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5702       break;
5703     case VariantKnightmate:
5704       pieces = KnightmateArray;
5705       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5706       break;
5707     case VariantSpartan:
5708       pieces = SpartanArray;
5709       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5710       break;
5711     case VariantFairy:
5712       pieces = fairyArray;
5713       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5714       break;
5715     case VariantGreat:
5716       pieces = GreatArray;
5717       gameInfo.boardWidth = 10;
5718       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5719       gameInfo.holdingsSize = 8;
5720       break;
5721     case VariantSuper:
5722       pieces = FIDEArray;
5723       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5724       gameInfo.holdingsSize = 8;
5725       startedFromSetupPosition = TRUE;
5726       break;
5727     case VariantCrazyhouse:
5728     case VariantBughouse:
5729       pieces = FIDEArray;
5730       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5731       gameInfo.holdingsSize = 5;
5732       break;
5733     case VariantWildCastle:
5734       pieces = FIDEArray;
5735       /* !!?shuffle with kings guaranteed to be on d or e file */
5736       shuffleOpenings = 1;
5737       break;
5738     case VariantNoCastle:
5739       pieces = FIDEArray;
5740       nrCastlingRights = 0;
5741       /* !!?unconstrained back-rank shuffle */
5742       shuffleOpenings = 1;
5743       break;
5744     }
5745
5746     overrule = 0;
5747     if(appData.NrFiles >= 0) {
5748         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5749         gameInfo.boardWidth = appData.NrFiles;
5750     }
5751     if(appData.NrRanks >= 0) {
5752         gameInfo.boardHeight = appData.NrRanks;
5753     }
5754     if(appData.holdingsSize >= 0) {
5755         i = appData.holdingsSize;
5756         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5757         gameInfo.holdingsSize = i;
5758     }
5759     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5760     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5761         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5762
5763     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5764     if(pawnRow < 1) pawnRow = 1;
5765     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5766
5767     /* User pieceToChar list overrules defaults */
5768     if(appData.pieceToCharTable != NULL)
5769         SetCharTable(pieceToChar, appData.pieceToCharTable);
5770
5771     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5772
5773         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5774             s = (ChessSquare) 0; /* account holding counts in guard band */
5775         for( i=0; i<BOARD_HEIGHT; i++ )
5776             initialPosition[i][j] = s;
5777
5778         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5779         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5780         initialPosition[pawnRow][j] = WhitePawn;
5781         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5782         if(gameInfo.variant == VariantXiangqi) {
5783             if(j&1) {
5784                 initialPosition[pawnRow][j] =
5785                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5786                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5787                    initialPosition[2][j] = WhiteCannon;
5788                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5789                 }
5790             }
5791         }
5792         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5793     }
5794     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5795
5796             j=BOARD_LEFT+1;
5797             initialPosition[1][j] = WhiteBishop;
5798             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5799             j=BOARD_RGHT-2;
5800             initialPosition[1][j] = WhiteRook;
5801             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5802     }
5803
5804     if( nrCastlingRights == -1) {
5805         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5806         /*       This sets default castling rights from none to normal corners   */
5807         /* Variants with other castling rights must set them themselves above    */
5808         nrCastlingRights = 6;
5809
5810         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5811         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5812         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5813         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5815         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5816      }
5817
5818      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5819      if(gameInfo.variant == VariantGreat) { // promotion commoners
5820         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5821         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5822         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5823         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5824      }
5825      if( gameInfo.variant == VariantSChess ) {
5826       initialPosition[1][0] = BlackMarshall;
5827       initialPosition[2][0] = BlackAngel;
5828       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5829       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5830       initialPosition[1][1] = initialPosition[2][1] = 
5831       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5832      }
5833   if (appData.debugMode) {
5834     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5835   }
5836     if(shuffleOpenings) {
5837         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5838         startedFromSetupPosition = TRUE;
5839     }
5840     if(startedFromPositionFile) {
5841       /* [HGM] loadPos: use PositionFile for every new game */
5842       CopyBoard(initialPosition, filePosition);
5843       for(i=0; i<nrCastlingRights; i++)
5844           initialRights[i] = filePosition[CASTLING][i];
5845       startedFromSetupPosition = TRUE;
5846     }
5847
5848     CopyBoard(boards[0], initialPosition);
5849
5850     if(oldx != gameInfo.boardWidth ||
5851        oldy != gameInfo.boardHeight ||
5852        oldv != gameInfo.variant ||
5853        oldh != gameInfo.holdingsWidth
5854                                          )
5855             InitDrawingSizes(-2 ,0);
5856
5857     oldv = gameInfo.variant;
5858     if (redraw)
5859       DrawPosition(TRUE, boards[currentMove]);
5860 }
5861
5862 void
5863 SendBoard(cps, moveNum)
5864      ChessProgramState *cps;
5865      int moveNum;
5866 {
5867     char message[MSG_SIZ];
5868
5869     if (cps->useSetboard) {
5870       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5871       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5872       SendToProgram(message, cps);
5873       free(fen);
5874
5875     } else {
5876       ChessSquare *bp;
5877       int i, j;
5878       /* Kludge to set black to move, avoiding the troublesome and now
5879        * deprecated "black" command.
5880        */
5881       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5882         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5883
5884       SendToProgram("edit\n", cps);
5885       SendToProgram("#\n", cps);
5886       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5887         bp = &boards[moveNum][i][BOARD_LEFT];
5888         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5889           if ((int) *bp < (int) BlackPawn) {
5890             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5891                     AAA + j, ONE + i);
5892             if(message[0] == '+' || message[0] == '~') {
5893               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5894                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5895                         AAA + j, ONE + i);
5896             }
5897             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5898                 message[1] = BOARD_RGHT   - 1 - j + '1';
5899                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5900             }
5901             SendToProgram(message, cps);
5902           }
5903         }
5904       }
5905
5906       SendToProgram("c\n", cps);
5907       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5908         bp = &boards[moveNum][i][BOARD_LEFT];
5909         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5910           if (((int) *bp != (int) EmptySquare)
5911               && ((int) *bp >= (int) BlackPawn)) {
5912             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5913                     AAA + j, ONE + i);
5914             if(message[0] == '+' || message[0] == '~') {
5915               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5916                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5917                         AAA + j, ONE + i);
5918             }
5919             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5920                 message[1] = BOARD_RGHT   - 1 - j + '1';
5921                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5922             }
5923             SendToProgram(message, cps);
5924           }
5925         }
5926       }
5927
5928       SendToProgram(".\n", cps);
5929     }
5930     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5931 }
5932
5933 ChessSquare
5934 DefaultPromoChoice(int white)
5935 {
5936     ChessSquare result;
5937     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5938         result = WhiteFerz; // no choice
5939     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5940         result= WhiteKing; // in Suicide Q is the last thing we want
5941     else if(gameInfo.variant == VariantSpartan)
5942         result = white ? WhiteQueen : WhiteAngel;
5943     else result = WhiteQueen;
5944     if(!white) result = WHITE_TO_BLACK result;
5945     return result;
5946 }
5947
5948 static int autoQueen; // [HGM] oneclick
5949
5950 int
5951 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5952 {
5953     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5954     /* [HGM] add Shogi promotions */
5955     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5956     ChessSquare piece;
5957     ChessMove moveType;
5958     Boolean premove;
5959
5960     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5961     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5962
5963     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5964       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5965         return FALSE;
5966
5967     piece = boards[currentMove][fromY][fromX];
5968     if(gameInfo.variant == VariantShogi) {
5969         promotionZoneSize = BOARD_HEIGHT/3;
5970         highestPromotingPiece = (int)WhiteFerz;
5971     } else if(gameInfo.variant == VariantMakruk) {
5972         promotionZoneSize = 3;
5973     }
5974
5975     // Treat Lance as Pawn when it is not representing Amazon
5976     if(gameInfo.variant != VariantSuper) {
5977         if(piece == WhiteLance) piece = WhitePawn; else
5978         if(piece == BlackLance) piece = BlackPawn;
5979     }
5980
5981     // next weed out all moves that do not touch the promotion zone at all
5982     if((int)piece >= BlackPawn) {
5983         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5984              return FALSE;
5985         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5986     } else {
5987         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5988            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5989     }
5990
5991     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5992
5993     // weed out mandatory Shogi promotions
5994     if(gameInfo.variant == VariantShogi) {
5995         if(piece >= BlackPawn) {
5996             if(toY == 0 && piece == BlackPawn ||
5997                toY == 0 && piece == BlackQueen ||
5998                toY <= 1 && piece == BlackKnight) {
5999                 *promoChoice = '+';
6000                 return FALSE;
6001             }
6002         } else {
6003             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6004                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6005                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6006                 *promoChoice = '+';
6007                 return FALSE;
6008             }
6009         }
6010     }
6011
6012     // weed out obviously illegal Pawn moves
6013     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6014         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6015         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6016         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6017         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6018         // note we are not allowed to test for valid (non-)capture, due to premove
6019     }
6020
6021     // we either have a choice what to promote to, or (in Shogi) whether to promote
6022     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6023         *promoChoice = PieceToChar(BlackFerz);  // no choice
6024         return FALSE;
6025     }
6026     // no sense asking what we must promote to if it is going to explode...
6027     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6028         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6029         return FALSE;
6030     }
6031     // give caller the default choice even if we will not make it
6032     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6033     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6034     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6035                            && gameInfo.variant != VariantShogi
6036                            && gameInfo.variant != VariantSuper) return FALSE;
6037     if(autoQueen) return FALSE; // predetermined
6038
6039     // suppress promotion popup on illegal moves that are not premoves
6040     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6041               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6042     if(appData.testLegality && !premove) {
6043         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6044                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6045         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6046             return FALSE;
6047     }
6048
6049     return TRUE;
6050 }
6051
6052 int
6053 InPalace(row, column)
6054      int row, column;
6055 {   /* [HGM] for Xiangqi */
6056     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6057          column < (BOARD_WIDTH + 4)/2 &&
6058          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6059     return FALSE;
6060 }
6061
6062 int
6063 PieceForSquare (x, y)
6064      int x;
6065      int y;
6066 {
6067   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6068      return -1;
6069   else
6070      return boards[currentMove][y][x];
6071 }
6072
6073 int
6074 OKToStartUserMove(x, y)
6075      int x, y;
6076 {
6077     ChessSquare from_piece;
6078     int white_piece;
6079
6080     if (matchMode) return FALSE;
6081     if (gameMode == EditPosition) return TRUE;
6082
6083     if (x >= 0 && y >= 0)
6084       from_piece = boards[currentMove][y][x];
6085     else
6086       from_piece = EmptySquare;
6087
6088     if (from_piece == EmptySquare) return FALSE;
6089
6090     white_piece = (int)from_piece >= (int)WhitePawn &&
6091       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6092
6093     switch (gameMode) {
6094       case PlayFromGameFile:
6095       case AnalyzeFile:
6096       case TwoMachinesPlay:
6097       case EndOfGame:
6098         return FALSE;
6099
6100       case IcsObserving:
6101       case IcsIdle:
6102         return FALSE;
6103
6104       case MachinePlaysWhite:
6105       case IcsPlayingBlack:
6106         if (appData.zippyPlay) return FALSE;
6107         if (white_piece) {
6108             DisplayMoveError(_("You are playing Black"));
6109             return FALSE;
6110         }
6111         break;
6112
6113       case MachinePlaysBlack:
6114       case IcsPlayingWhite:
6115         if (appData.zippyPlay) return FALSE;
6116         if (!white_piece) {
6117             DisplayMoveError(_("You are playing White"));
6118             return FALSE;
6119         }
6120         break;
6121
6122       case EditGame:
6123         if (!white_piece && WhiteOnMove(currentMove)) {
6124             DisplayMoveError(_("It is White's turn"));
6125             return FALSE;
6126         }
6127         if (white_piece && !WhiteOnMove(currentMove)) {
6128             DisplayMoveError(_("It is Black's turn"));
6129             return FALSE;
6130         }
6131         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6132             /* Editing correspondence game history */
6133             /* Could disallow this or prompt for confirmation */
6134             cmailOldMove = -1;
6135         }
6136         break;
6137
6138       case BeginningOfGame:
6139         if (appData.icsActive) return FALSE;
6140         if (!appData.noChessProgram) {
6141             if (!white_piece) {
6142                 DisplayMoveError(_("You are playing White"));
6143                 return FALSE;
6144             }
6145         }
6146         break;
6147
6148       case Training:
6149         if (!white_piece && WhiteOnMove(currentMove)) {
6150             DisplayMoveError(_("It is White's turn"));
6151             return FALSE;
6152         }
6153         if (white_piece && !WhiteOnMove(currentMove)) {
6154             DisplayMoveError(_("It is Black's turn"));
6155             return FALSE;
6156         }
6157         break;
6158
6159       default:
6160       case IcsExamining:
6161         break;
6162     }
6163     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6164         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6165         && gameMode != AnalyzeFile && gameMode != Training) {
6166         DisplayMoveError(_("Displayed position is not current"));
6167         return FALSE;
6168     }
6169     return TRUE;
6170 }
6171
6172 Boolean
6173 OnlyMove(int *x, int *y, Boolean captures) {
6174     DisambiguateClosure cl;
6175     if (appData.zippyPlay) return FALSE;
6176     switch(gameMode) {
6177       case MachinePlaysBlack:
6178       case IcsPlayingWhite:
6179       case BeginningOfGame:
6180         if(!WhiteOnMove(currentMove)) return FALSE;
6181         break;
6182       case MachinePlaysWhite:
6183       case IcsPlayingBlack:
6184         if(WhiteOnMove(currentMove)) return FALSE;
6185         break;
6186       case EditGame:
6187         break;
6188       default:
6189         return FALSE;
6190     }
6191     cl.pieceIn = EmptySquare;
6192     cl.rfIn = *y;
6193     cl.ffIn = *x;
6194     cl.rtIn = -1;
6195     cl.ftIn = -1;
6196     cl.promoCharIn = NULLCHAR;
6197     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6198     if( cl.kind == NormalMove ||
6199         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6200         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6201         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6202       fromX = cl.ff;
6203       fromY = cl.rf;
6204       *x = cl.ft;
6205       *y = cl.rt;
6206       return TRUE;
6207     }
6208     if(cl.kind != ImpossibleMove) return FALSE;
6209     cl.pieceIn = EmptySquare;
6210     cl.rfIn = -1;
6211     cl.ffIn = -1;
6212     cl.rtIn = *y;
6213     cl.ftIn = *x;
6214     cl.promoCharIn = NULLCHAR;
6215     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6216     if( cl.kind == NormalMove ||
6217         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6218         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6219         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6220       fromX = cl.ff;
6221       fromY = cl.rf;
6222       *x = cl.ft;
6223       *y = cl.rt;
6224       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6225       return TRUE;
6226     }
6227     return FALSE;
6228 }
6229
6230 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6231 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6232 int lastLoadGameUseList = FALSE;
6233 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6234 ChessMove lastLoadGameStart = EndOfFile;
6235
6236 void
6237 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6238      int fromX, fromY, toX, toY;
6239      int promoChar;
6240 {
6241     ChessMove moveType;
6242     ChessSquare pdown, pup;
6243
6244     /* Check if the user is playing in turn.  This is complicated because we
6245        let the user "pick up" a piece before it is his turn.  So the piece he
6246        tried to pick up may have been captured by the time he puts it down!
6247        Therefore we use the color the user is supposed to be playing in this
6248        test, not the color of the piece that is currently on the starting
6249        square---except in EditGame mode, where the user is playing both
6250        sides; fortunately there the capture race can't happen.  (It can
6251        now happen in IcsExamining mode, but that's just too bad.  The user
6252        will get a somewhat confusing message in that case.)
6253        */
6254
6255     switch (gameMode) {
6256       case PlayFromGameFile:
6257       case AnalyzeFile:
6258       case TwoMachinesPlay:
6259       case EndOfGame:
6260       case IcsObserving:
6261       case IcsIdle:
6262         /* We switched into a game mode where moves are not accepted,
6263            perhaps while the mouse button was down. */
6264         return;
6265
6266       case MachinePlaysWhite:
6267         /* User is moving for Black */
6268         if (WhiteOnMove(currentMove)) {
6269             DisplayMoveError(_("It is White's turn"));
6270             return;
6271         }
6272         break;
6273
6274       case MachinePlaysBlack:
6275         /* User is moving for White */
6276         if (!WhiteOnMove(currentMove)) {
6277             DisplayMoveError(_("It is Black's turn"));
6278             return;
6279         }
6280         break;
6281
6282       case EditGame:
6283       case IcsExamining:
6284       case BeginningOfGame:
6285       case AnalyzeMode:
6286       case Training:
6287         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6288         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6289             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6290             /* User is moving for Black */
6291             if (WhiteOnMove(currentMove)) {
6292                 DisplayMoveError(_("It is White's turn"));
6293                 return;
6294             }
6295         } else {
6296             /* User is moving for White */
6297             if (!WhiteOnMove(currentMove)) {
6298                 DisplayMoveError(_("It is Black's turn"));
6299                 return;
6300             }
6301         }
6302         break;
6303
6304       case IcsPlayingBlack:
6305         /* User is moving for Black */
6306         if (WhiteOnMove(currentMove)) {
6307             if (!appData.premove) {
6308                 DisplayMoveError(_("It is White's turn"));
6309             } else if (toX >= 0 && toY >= 0) {
6310                 premoveToX = toX;
6311                 premoveToY = toY;
6312                 premoveFromX = fromX;
6313                 premoveFromY = fromY;
6314                 premovePromoChar = promoChar;
6315                 gotPremove = 1;
6316                 if (appData.debugMode)
6317                     fprintf(debugFP, "Got premove: fromX %d,"
6318                             "fromY %d, toX %d, toY %d\n",
6319                             fromX, fromY, toX, toY);
6320             }
6321             return;
6322         }
6323         break;
6324
6325       case IcsPlayingWhite:
6326         /* User is moving for White */
6327         if (!WhiteOnMove(currentMove)) {
6328             if (!appData.premove) {
6329                 DisplayMoveError(_("It is Black's turn"));
6330             } else if (toX >= 0 && toY >= 0) {
6331                 premoveToX = toX;
6332                 premoveToY = toY;
6333                 premoveFromX = fromX;
6334                 premoveFromY = fromY;
6335                 premovePromoChar = promoChar;
6336                 gotPremove = 1;
6337                 if (appData.debugMode)
6338                     fprintf(debugFP, "Got premove: fromX %d,"
6339                             "fromY %d, toX %d, toY %d\n",
6340                             fromX, fromY, toX, toY);
6341             }
6342             return;
6343         }
6344         break;
6345
6346       default:
6347         break;
6348
6349       case EditPosition:
6350         /* EditPosition, empty square, or different color piece;
6351            click-click move is possible */
6352         if (toX == -2 || toY == -2) {
6353             boards[0][fromY][fromX] = EmptySquare;
6354             DrawPosition(FALSE, boards[currentMove]);
6355             return;
6356         } else if (toX >= 0 && toY >= 0) {
6357             boards[0][toY][toX] = boards[0][fromY][fromX];
6358             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6359                 if(boards[0][fromY][0] != EmptySquare) {
6360                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6361                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6362                 }
6363             } else
6364             if(fromX == BOARD_RGHT+1) {
6365                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6366                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6367                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6368                 }
6369             } else
6370             boards[0][fromY][fromX] = EmptySquare;
6371             DrawPosition(FALSE, boards[currentMove]);
6372             return;
6373         }
6374         return;
6375     }
6376
6377     if(toX < 0 || toY < 0) return;
6378     pdown = boards[currentMove][fromY][fromX];
6379     pup = boards[currentMove][toY][toX];
6380
6381     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6382     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6383          if( pup != EmptySquare ) return;
6384          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6385            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6386                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6387            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6388            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6389            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6390            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6391          fromY = DROP_RANK;
6392     }
6393
6394     /* [HGM] always test for legality, to get promotion info */
6395     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6396                                          fromY, fromX, toY, toX, promoChar);
6397     /* [HGM] but possibly ignore an IllegalMove result */
6398     if (appData.testLegality) {
6399         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6400             DisplayMoveError(_("Illegal move"));
6401             return;
6402         }
6403     }
6404
6405     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6406 }
6407
6408 /* Common tail of UserMoveEvent and DropMenuEvent */
6409 int
6410 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6411      ChessMove moveType;
6412      int fromX, fromY, toX, toY;
6413      /*char*/int promoChar;
6414 {
6415     char *bookHit = 0;
6416
6417     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6418         // [HGM] superchess: suppress promotions to non-available piece
6419         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6420         if(WhiteOnMove(currentMove)) {
6421             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6422         } else {
6423             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6424         }
6425     }
6426
6427     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6428        move type in caller when we know the move is a legal promotion */
6429     if(moveType == NormalMove && promoChar)
6430         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6431
6432     /* [HGM] <popupFix> The following if has been moved here from
6433        UserMoveEvent(). Because it seemed to belong here (why not allow
6434        piece drops in training games?), and because it can only be
6435        performed after it is known to what we promote. */
6436     if (gameMode == Training) {
6437       /* compare the move played on the board to the next move in the
6438        * game. If they match, display the move and the opponent's response.
6439        * If they don't match, display an error message.
6440        */
6441       int saveAnimate;
6442       Board testBoard;
6443       CopyBoard(testBoard, boards[currentMove]);
6444       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6445
6446       if (CompareBoards(testBoard, boards[currentMove+1])) {
6447         ForwardInner(currentMove+1);
6448
6449         /* Autoplay the opponent's response.
6450          * if appData.animate was TRUE when Training mode was entered,
6451          * the response will be animated.
6452          */
6453         saveAnimate = appData.animate;
6454         appData.animate = animateTraining;
6455         ForwardInner(currentMove+1);
6456         appData.animate = saveAnimate;
6457
6458         /* check for the end of the game */
6459         if (currentMove >= forwardMostMove) {
6460           gameMode = PlayFromGameFile;
6461           ModeHighlight();
6462           SetTrainingModeOff();
6463           DisplayInformation(_("End of game"));
6464         }
6465       } else {
6466         DisplayError(_("Incorrect move"), 0);
6467       }
6468       return 1;
6469     }
6470
6471   /* Ok, now we know that the move is good, so we can kill
6472      the previous line in Analysis Mode */
6473   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6474                                 && currentMove < forwardMostMove) {
6475     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6476     else forwardMostMove = currentMove;
6477   }
6478
6479   /* If we need the chess program but it's dead, restart it */
6480   ResurrectChessProgram();
6481
6482   /* A user move restarts a paused game*/
6483   if (pausing)
6484     PauseEvent();
6485
6486   thinkOutput[0] = NULLCHAR;
6487
6488   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6489
6490   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6491     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6492     return 1;
6493   }
6494
6495   if (gameMode == BeginningOfGame) {
6496     if (appData.noChessProgram) {
6497       gameMode = EditGame;
6498       SetGameInfo();
6499     } else {
6500       char buf[MSG_SIZ];
6501       gameMode = MachinePlaysBlack;
6502       StartClocks();
6503       SetGameInfo();
6504       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6505       DisplayTitle(buf);
6506       if (first.sendName) {
6507         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6508         SendToProgram(buf, &first);
6509       }
6510       StartClocks();
6511     }
6512     ModeHighlight();
6513   }
6514
6515   /* Relay move to ICS or chess engine */
6516   if (appData.icsActive) {
6517     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6518         gameMode == IcsExamining) {
6519       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6520         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6521         SendToICS("draw ");
6522         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6523       }
6524       // also send plain move, in case ICS does not understand atomic claims
6525       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6526       ics_user_moved = 1;
6527     }
6528   } else {
6529     if (first.sendTime && (gameMode == BeginningOfGame ||
6530                            gameMode == MachinePlaysWhite ||
6531                            gameMode == MachinePlaysBlack)) {
6532       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6533     }
6534     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6535          // [HGM] book: if program might be playing, let it use book
6536         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6537         first.maybeThinking = TRUE;
6538     } else SendMoveToProgram(forwardMostMove-1, &first);
6539     if (currentMove == cmailOldMove + 1) {
6540       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6541     }
6542   }
6543
6544   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6545
6546   switch (gameMode) {
6547   case EditGame:
6548     if(appData.testLegality)
6549     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6550     case MT_NONE:
6551     case MT_CHECK:
6552       break;
6553     case MT_CHECKMATE:
6554     case MT_STAINMATE:
6555       if (WhiteOnMove(currentMove)) {
6556         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6557       } else {
6558         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6559       }
6560       break;
6561     case MT_STALEMATE:
6562       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6563       break;
6564     }
6565     break;
6566
6567   case MachinePlaysBlack:
6568   case MachinePlaysWhite:
6569     /* disable certain menu options while machine is thinking */
6570     SetMachineThinkingEnables();
6571     break;
6572
6573   default:
6574     break;
6575   }
6576
6577   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6578   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6579
6580   if(bookHit) { // [HGM] book: simulate book reply
6581         static char bookMove[MSG_SIZ]; // a bit generous?
6582
6583         programStats.nodes = programStats.depth = programStats.time =
6584         programStats.score = programStats.got_only_move = 0;
6585         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6586
6587         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6588         strcat(bookMove, bookHit);
6589         HandleMachineMove(bookMove, &first);
6590   }
6591   return 1;
6592 }
6593
6594 void
6595 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6596      Board board;
6597      int flags;
6598      ChessMove kind;
6599      int rf, ff, rt, ft;
6600      VOIDSTAR closure;
6601 {
6602     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6603     Markers *m = (Markers *) closure;
6604     if(rf == fromY && ff == fromX)
6605         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6606                          || kind == WhiteCapturesEnPassant
6607                          || kind == BlackCapturesEnPassant);
6608     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6609 }
6610
6611 void
6612 MarkTargetSquares(int clear)
6613 {
6614   int x, y;
6615   if(!appData.markers || !appData.highlightDragging ||
6616      !appData.testLegality || gameMode == EditPosition) return;
6617   if(clear) {
6618     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6619   } else {
6620     int capt = 0;
6621     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6622     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6623       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6624       if(capt)
6625       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6626     }
6627   }
6628   DrawPosition(TRUE, NULL);
6629 }
6630
6631 int
6632 Explode(Board board, int fromX, int fromY, int toX, int toY)
6633 {
6634     if(gameInfo.variant == VariantAtomic &&
6635        (board[toY][toX] != EmptySquare ||                     // capture?
6636         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6637                          board[fromY][fromX] == BlackPawn   )
6638       )) {
6639         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6640         return TRUE;
6641     }
6642     return FALSE;
6643 }
6644
6645 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6646
6647 int CanPromote(ChessSquare piece, int y)
6648 {
6649         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6650         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6651         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6652            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6653            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6654                                                   gameInfo.variant == VariantMakruk) return FALSE;
6655         return (piece == BlackPawn && y == 1 ||
6656                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6657                 piece == BlackLance && y == 1 ||
6658                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6659 }
6660
6661 void LeftClick(ClickType clickType, int xPix, int yPix)
6662 {
6663     int x, y;
6664     Boolean saveAnimate;
6665     static int second = 0, promotionChoice = 0, clearFlag = 0;
6666     char promoChoice = NULLCHAR;
6667     ChessSquare piece;
6668
6669     if(appData.seekGraph && appData.icsActive && loggedOn &&
6670         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6671         SeekGraphClick(clickType, xPix, yPix, 0);
6672         return;
6673     }
6674
6675     if (clickType == Press) ErrorPopDown();
6676     MarkTargetSquares(1);
6677
6678     x = EventToSquare(xPix, BOARD_WIDTH);
6679     y = EventToSquare(yPix, BOARD_HEIGHT);
6680     if (!flipView && y >= 0) {
6681         y = BOARD_HEIGHT - 1 - y;
6682     }
6683     if (flipView && x >= 0) {
6684         x = BOARD_WIDTH - 1 - x;
6685     }
6686
6687     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6688         defaultPromoChoice = promoSweep;
6689         promoSweep = EmptySquare;   // terminate sweep
6690         promoDefaultAltered = TRUE;
6691         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6692     }
6693
6694     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6695         if(clickType == Release) return; // ignore upclick of click-click destination
6696         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6697         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6698         if(gameInfo.holdingsWidth &&
6699                 (WhiteOnMove(currentMove)
6700                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6701                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6702             // click in right holdings, for determining promotion piece
6703             ChessSquare p = boards[currentMove][y][x];
6704             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6705             if(p != EmptySquare) {
6706                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6707                 fromX = fromY = -1;
6708                 return;
6709             }
6710         }
6711         DrawPosition(FALSE, boards[currentMove]);
6712         return;
6713     }
6714
6715     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6716     if(clickType == Press
6717             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6718               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6719               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6720         return;
6721
6722     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6723         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6724
6725     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6726         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6727                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6728         defaultPromoChoice = DefaultPromoChoice(side);
6729     }
6730
6731     autoQueen = appData.alwaysPromoteToQueen;
6732
6733     if (fromX == -1) {
6734       int originalY = y;
6735       gatingPiece = EmptySquare;
6736       if (clickType != Press) {
6737         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6738             DragPieceEnd(xPix, yPix); dragging = 0;
6739             DrawPosition(FALSE, NULL);
6740         }
6741         return;
6742       }
6743       fromX = x; fromY = y;
6744       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6745          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6746          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6747             /* First square */
6748             if (OKToStartUserMove(fromX, fromY)) {
6749                 second = 0;
6750                 MarkTargetSquares(0);
6751                 DragPieceBegin(xPix, yPix); dragging = 1;
6752                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6753                     promoSweep = defaultPromoChoice;
6754                     selectFlag = 0; lastX = xPix; lastY = yPix;
6755                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6756                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6757                 }
6758                 if (appData.highlightDragging) {
6759                     SetHighlights(fromX, fromY, -1, -1);
6760                 }
6761             } else fromX = fromY = -1;
6762             return;
6763         }
6764     }
6765
6766     /* fromX != -1 */
6767     if (clickType == Press && gameMode != EditPosition) {
6768         ChessSquare fromP;
6769         ChessSquare toP;
6770         int frc;
6771
6772         // ignore off-board to clicks
6773         if(y < 0 || x < 0) return;
6774
6775         /* Check if clicking again on the same color piece */
6776         fromP = boards[currentMove][fromY][fromX];
6777         toP = boards[currentMove][y][x];
6778         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6779         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6780              WhitePawn <= toP && toP <= WhiteKing &&
6781              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6782              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6783             (BlackPawn <= fromP && fromP <= BlackKing &&
6784              BlackPawn <= toP && toP <= BlackKing &&
6785              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6786              !(fromP == BlackKing && toP == BlackRook && frc))) {
6787             /* Clicked again on same color piece -- changed his mind */
6788             second = (x == fromX && y == fromY);
6789             promoDefaultAltered = FALSE;
6790            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6791             if (appData.highlightDragging) {
6792                 SetHighlights(x, y, -1, -1);
6793             } else {
6794                 ClearHighlights();
6795             }
6796             if (OKToStartUserMove(x, y)) {
6797                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6798                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6799                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6800                  gatingPiece = boards[currentMove][fromY][fromX];
6801                 else gatingPiece = EmptySquare;
6802                 fromX = x;
6803                 fromY = y; dragging = 1;
6804                 MarkTargetSquares(0);
6805                 DragPieceBegin(xPix, yPix);
6806                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6807                     promoSweep = defaultPromoChoice;
6808                     selectFlag = 0; lastX = xPix; lastY = yPix;
6809                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6810                 }
6811             }
6812            }
6813            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6814            second = FALSE; 
6815         }
6816         // ignore clicks on holdings
6817         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6818     }
6819
6820     if (clickType == Release && x == fromX && y == fromY) {
6821         DragPieceEnd(xPix, yPix); dragging = 0;
6822         if(clearFlag) {
6823             // a deferred attempt to click-click move an empty square on top of a piece
6824             boards[currentMove][y][x] = EmptySquare;
6825             ClearHighlights();
6826             DrawPosition(FALSE, boards[currentMove]);
6827             fromX = fromY = -1; clearFlag = 0;
6828             return;
6829         }
6830         if (appData.animateDragging) {
6831             /* Undo animation damage if any */
6832             DrawPosition(FALSE, NULL);
6833         }
6834         if (second) {
6835             /* Second up/down in same square; just abort move */
6836             second = 0;
6837             fromX = fromY = -1;
6838             gatingPiece = EmptySquare;
6839             ClearHighlights();
6840             gotPremove = 0;
6841             ClearPremoveHighlights();
6842         } else {
6843             /* First upclick in same square; start click-click mode */
6844             SetHighlights(x, y, -1, -1);
6845         }
6846         return;
6847     }
6848
6849     clearFlag = 0;
6850
6851     /* we now have a different from- and (possibly off-board) to-square */
6852     /* Completed move */
6853     toX = x;
6854     toY = y;
6855     saveAnimate = appData.animate;
6856     if (clickType == Press) {
6857         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6858             // must be Edit Position mode with empty-square selected
6859             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6860             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6861             return;
6862         }
6863         /* Finish clickclick move */
6864         if (appData.animate || appData.highlightLastMove) {
6865             SetHighlights(fromX, fromY, toX, toY);
6866         } else {
6867             ClearHighlights();
6868         }
6869     } else {
6870         /* Finish drag move */
6871         if (appData.highlightLastMove) {
6872             SetHighlights(fromX, fromY, toX, toY);
6873         } else {
6874             ClearHighlights();
6875         }
6876         DragPieceEnd(xPix, yPix); dragging = 0;
6877         /* Don't animate move and drag both */
6878         appData.animate = FALSE;
6879     }
6880
6881     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6882     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6883         ChessSquare piece = boards[currentMove][fromY][fromX];
6884         if(gameMode == EditPosition && piece != EmptySquare &&
6885            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6886             int n;
6887
6888             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6889                 n = PieceToNumber(piece - (int)BlackPawn);
6890                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6891                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6892                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6893             } else
6894             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6895                 n = PieceToNumber(piece);
6896                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6897                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6898                 boards[currentMove][n][BOARD_WIDTH-2]++;
6899             }
6900             boards[currentMove][fromY][fromX] = EmptySquare;
6901         }
6902         ClearHighlights();
6903         fromX = fromY = -1;
6904         DrawPosition(TRUE, boards[currentMove]);
6905         return;
6906     }
6907
6908     // off-board moves should not be highlighted
6909     if(x < 0 || y < 0) ClearHighlights();
6910
6911     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6912
6913     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6914         SetHighlights(fromX, fromY, toX, toY);
6915         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6916             // [HGM] super: promotion to captured piece selected from holdings
6917             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6918             promotionChoice = TRUE;
6919             // kludge follows to temporarily execute move on display, without promoting yet
6920             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6921             boards[currentMove][toY][toX] = p;
6922             DrawPosition(FALSE, boards[currentMove]);
6923             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6924             boards[currentMove][toY][toX] = q;
6925             DisplayMessage("Click in holdings to choose piece", "");
6926             return;
6927         }
6928         PromotionPopUp();
6929     } else {
6930         int oldMove = currentMove;
6931         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6932         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6933         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6934         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6935            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6936             DrawPosition(TRUE, boards[currentMove]);
6937         fromX = fromY = -1;
6938     }
6939     appData.animate = saveAnimate;
6940     if (appData.animate || appData.animateDragging) {
6941         /* Undo animation damage if needed */
6942         DrawPosition(FALSE, NULL);
6943     }
6944 }
6945
6946 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6947 {   // front-end-free part taken out of PieceMenuPopup
6948     int whichMenu; int xSqr, ySqr;
6949
6950     if(seekGraphUp) { // [HGM] seekgraph
6951         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6952         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6953         return -2;
6954     }
6955
6956     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6957          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6958         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6959         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6960         if(action == Press)   {
6961             originalFlip = flipView;
6962             flipView = !flipView; // temporarily flip board to see game from partners perspective
6963             DrawPosition(TRUE, partnerBoard);
6964             DisplayMessage(partnerStatus, "");
6965             partnerUp = TRUE;
6966         } else if(action == Release) {
6967             flipView = originalFlip;
6968             DrawPosition(TRUE, boards[currentMove]);
6969             partnerUp = FALSE;
6970         }
6971         return -2;
6972     }
6973
6974     xSqr = EventToSquare(x, BOARD_WIDTH);
6975     ySqr = EventToSquare(y, BOARD_HEIGHT);
6976     if (action == Release) {
6977         if(pieceSweep != EmptySquare) {
6978             EditPositionMenuEvent(pieceSweep, toX, toY);
6979             pieceSweep = EmptySquare;
6980         } else UnLoadPV(); // [HGM] pv
6981     }
6982     if (action != Press) return -2; // return code to be ignored
6983     switch (gameMode) {
6984       case IcsExamining:
6985         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6986       case EditPosition:
6987         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6988         if (xSqr < 0 || ySqr < 0) return -1;
6989         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6990         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6991         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6992         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6993         NextPiece(0);
6994         return -2;\r
6995       case IcsObserving:
6996         if(!appData.icsEngineAnalyze) return -1;
6997       case IcsPlayingWhite:
6998       case IcsPlayingBlack:
6999         if(!appData.zippyPlay) goto noZip;
7000       case AnalyzeMode:
7001       case AnalyzeFile:
7002       case MachinePlaysWhite:
7003       case MachinePlaysBlack:
7004       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7005         if (!appData.dropMenu) {
7006           LoadPV(x, y);
7007           return 2; // flag front-end to grab mouse events
7008         }
7009         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7010            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7011       case EditGame:
7012       noZip:
7013         if (xSqr < 0 || ySqr < 0) return -1;
7014         if (!appData.dropMenu || appData.testLegality &&
7015             gameInfo.variant != VariantBughouse &&
7016             gameInfo.variant != VariantCrazyhouse) return -1;
7017         whichMenu = 1; // drop menu
7018         break;
7019       default:
7020         return -1;
7021     }
7022
7023     if (((*fromX = xSqr) < 0) ||
7024         ((*fromY = ySqr) < 0)) {
7025         *fromX = *fromY = -1;
7026         return -1;
7027     }
7028     if (flipView)
7029       *fromX = BOARD_WIDTH - 1 - *fromX;
7030     else
7031       *fromY = BOARD_HEIGHT - 1 - *fromY;
7032
7033     return whichMenu;
7034 }
7035
7036 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7037 {
7038 //    char * hint = lastHint;
7039     FrontEndProgramStats stats;
7040
7041     stats.which = cps == &first ? 0 : 1;
7042     stats.depth = cpstats->depth;
7043     stats.nodes = cpstats->nodes;
7044     stats.score = cpstats->score;
7045     stats.time = cpstats->time;
7046     stats.pv = cpstats->movelist;
7047     stats.hint = lastHint;
7048     stats.an_move_index = 0;
7049     stats.an_move_count = 0;
7050
7051     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7052         stats.hint = cpstats->move_name;
7053         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7054         stats.an_move_count = cpstats->nr_moves;
7055     }
7056
7057     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
7058
7059     SetProgramStats( &stats );
7060 }
7061
7062 #define MAXPLAYERS 500
7063
7064 char *
7065 TourneyStandings(int display)
7066 {
7067     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7068     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7069     char result, *p, *names[MAXPLAYERS];
7070
7071     names[0] = p = strdup(appData.participants);
7072     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7073
7074     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7075
7076     while(result = appData.results[nr]) {
7077         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7078         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7079         wScore = bScore = 0;
7080         switch(result) {
7081           case '+': wScore = 2; break;
7082           case '-': bScore = 2; break;
7083           case '=': wScore = bScore = 1; break;
7084           case ' ':
7085           case '*': return NULL; // tourney not finished
7086         }
7087         score[w] += wScore;
7088         score[b] += bScore;
7089         games[w]++;
7090         games[b]++;
7091         nr++;
7092     }
7093     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7094     for(w=0; w<nPlayers; w++) {
7095         bScore = -1;
7096         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7097         ranking[w] = b; points[w] = bScore; score[b] = -2;
7098     }
7099     p = malloc(nPlayers*34+1);
7100     for(w=0; w<nPlayers && w<display; w++)
7101         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7102     free(names[0]);
7103     return p;
7104 }
7105
7106 void
7107 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7108 {       // count all piece types
7109         int p, f, r;
7110         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7111         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7112         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7113                 p = board[r][f];
7114                 pCnt[p]++;
7115                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7116                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7117                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7118                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7119                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7120                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7121         }
7122 }
7123
7124 int
7125 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7126 {
7127         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7128         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7129
7130         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7131         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7132         if(myPawns == 2 && nMine == 3) // KPP
7133             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7134         if(myPawns == 1 && nMine == 2) // KP
7135             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7136         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7137             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7138         if(myPawns) return FALSE;
7139         if(pCnt[WhiteRook+side])
7140             return pCnt[BlackRook-side] ||
7141                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7142                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7143                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7144         if(pCnt[WhiteCannon+side]) {
7145             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7146             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7147         }
7148         if(pCnt[WhiteKnight+side])
7149             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7150         return FALSE;
7151 }
7152
7153 int
7154 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7155 {
7156         VariantClass v = gameInfo.variant;
7157
7158         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7159         if(v == VariantShatranj) return TRUE; // always winnable through baring
7160         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7161         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7162
7163         if(v == VariantXiangqi) {
7164                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7165
7166                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7167                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7168                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7169                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7170                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7171                 if(stale) // we have at least one last-rank P plus perhaps C
7172                     return majors // KPKX
7173                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7174                 else // KCA*E*
7175                     return pCnt[WhiteFerz+side] // KCAK
7176                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7177                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7178                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7179
7180         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7181                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7182
7183                 if(nMine == 1) return FALSE; // bare King
7184                 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
7185                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7186                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7187                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7188                 if(pCnt[WhiteKnight+side])
7189                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7190                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7191                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7192                 if(nBishops)
7193                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7194                 if(pCnt[WhiteAlfil+side])
7195                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7196                 if(pCnt[WhiteWazir+side])
7197                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7198         }
7199
7200         return TRUE;
7201 }
7202
7203 int
7204 Adjudicate(ChessProgramState *cps)
7205 {       // [HGM] some adjudications useful with buggy engines
7206         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7207         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7208         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7209         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7210         int k, count = 0; static int bare = 1;
7211         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7212         Boolean canAdjudicate = !appData.icsActive;
7213
7214         // most tests only when we understand the game, i.e. legality-checking on
7215             if( appData.testLegality )
7216             {   /* [HGM] Some more adjudications for obstinate engines */
7217                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7218                 static int moveCount = 6;
7219                 ChessMove result;
7220                 char *reason = NULL;
7221
7222                 /* Count what is on board. */
7223                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7224
7225                 /* Some material-based adjudications that have to be made before stalemate test */
7226                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7227                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7228                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7229                      if(canAdjudicate && appData.checkMates) {
7230                          if(engineOpponent)
7231                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7232                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7233                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7234                          return 1;
7235                      }
7236                 }
7237
7238                 /* Bare King in Shatranj (loses) or Losers (wins) */
7239                 if( nrW == 1 || nrB == 1) {
7240                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7241                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7242                      if(canAdjudicate && appData.checkMates) {
7243                          if(engineOpponent)
7244                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7245                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7246                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7247                          return 1;
7248                      }
7249                   } else
7250                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7251                   {    /* bare King */
7252                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7253                         if(canAdjudicate && appData.checkMates) {
7254                             /* but only adjudicate if adjudication enabled */
7255                             if(engineOpponent)
7256                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7257                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7258                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7259                             return 1;
7260                         }
7261                   }
7262                 } else bare = 1;
7263
7264
7265             // don't wait for engine to announce game end if we can judge ourselves
7266             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7267               case MT_CHECK:
7268                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7269                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7270                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7271                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7272                             checkCnt++;
7273                         if(checkCnt >= 2) {
7274                             reason = "Xboard adjudication: 3rd check";
7275                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7276                             break;
7277                         }
7278                     }
7279                 }
7280               case MT_NONE:
7281               default:
7282                 break;
7283               case MT_STALEMATE:
7284               case MT_STAINMATE:
7285                 reason = "Xboard adjudication: Stalemate";
7286                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7287                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7288                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7289                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7290                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7291                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7292                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7293                                                                         EP_CHECKMATE : EP_WINS);
7294                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7295                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7296                 }
7297                 break;
7298               case MT_CHECKMATE:
7299                 reason = "Xboard adjudication: Checkmate";
7300                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7301                 break;
7302             }
7303
7304                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7305                     case EP_STALEMATE:
7306                         result = GameIsDrawn; break;
7307                     case EP_CHECKMATE:
7308                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7309                     case EP_WINS:
7310                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7311                     default:
7312                         result = EndOfFile;
7313                 }
7314                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7315                     if(engineOpponent)
7316                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7317                     GameEnds( result, reason, GE_XBOARD );
7318                     return 1;
7319                 }
7320
7321                 /* Next absolutely insufficient mating material. */
7322                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7323                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7324                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7325
7326                      /* always flag draws, for judging claims */
7327                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7328
7329                      if(canAdjudicate && appData.materialDraws) {
7330                          /* but only adjudicate them if adjudication enabled */
7331                          if(engineOpponent) {
7332                            SendToProgram("force\n", engineOpponent); // suppress reply
7333                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7334                          }
7335                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7336                          return 1;
7337                      }
7338                 }
7339
7340                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7341                 if(gameInfo.variant == VariantXiangqi ?
7342                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7343                  : nrW + nrB == 4 &&
7344                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7345                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7346                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7347                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7348                    ) ) {
7349                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7350                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7351                           if(engineOpponent) {
7352                             SendToProgram("force\n", engineOpponent); // suppress reply
7353                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7354                           }
7355                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7356                           return 1;
7357                      }
7358                 } else moveCount = 6;
7359             }
7360         if (appData.debugMode) { int i;
7361             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7362                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7363                     appData.drawRepeats);
7364             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7365               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7366
7367         }
7368
7369         // Repetition draws and 50-move rule can be applied independently of legality testing
7370
7371                 /* Check for rep-draws */
7372                 count = 0;
7373                 for(k = forwardMostMove-2;
7374                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7375                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7376                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7377                     k-=2)
7378                 {   int rights=0;
7379                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7380                         /* compare castling rights */
7381                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7382                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7383                                 rights++; /* King lost rights, while rook still had them */
7384                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7385                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7386                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7387                                    rights++; /* but at least one rook lost them */
7388                         }
7389                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7390                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7391                                 rights++;
7392                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7393                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7394                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7395                                    rights++;
7396                         }
7397                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7398                             && appData.drawRepeats > 1) {
7399                              /* adjudicate after user-specified nr of repeats */
7400                              int result = GameIsDrawn;
7401                              char *details = "XBoard adjudication: repetition draw";
7402                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7403                                 // [HGM] xiangqi: check for forbidden perpetuals
7404                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7405                                 for(m=forwardMostMove; m>k; m-=2) {
7406                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7407                                         ourPerpetual = 0; // the current mover did not always check
7408                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7409                                         hisPerpetual = 0; // the opponent did not always check
7410                                 }
7411                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7412                                                                         ourPerpetual, hisPerpetual);
7413                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7414                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7415                                     details = "Xboard adjudication: perpetual checking";
7416                                 } else
7417                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7418                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7419                                 } else
7420                                 // Now check for perpetual chases
7421                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7422                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7423                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7424                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7425                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7426                                         details = "Xboard adjudication: perpetual chasing";
7427                                     } else
7428                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7429                                         break; // Abort repetition-checking loop.
7430                                 }
7431                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7432                              }
7433                              if(engineOpponent) {
7434                                SendToProgram("force\n", engineOpponent); // suppress reply
7435                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7436                              }
7437                              GameEnds( result, details, GE_XBOARD );
7438                              return 1;
7439                         }
7440                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7441                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7442                     }
7443                 }
7444
7445                 /* Now we test for 50-move draws. Determine ply count */
7446                 count = forwardMostMove;
7447                 /* look for last irreversble move */
7448                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7449                     count--;
7450                 /* if we hit starting position, add initial plies */
7451                 if( count == backwardMostMove )
7452                     count -= initialRulePlies;
7453                 count = forwardMostMove - count;
7454                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7455                         // adjust reversible move counter for checks in Xiangqi
7456                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7457                         if(i < backwardMostMove) i = backwardMostMove;
7458                         while(i <= forwardMostMove) {
7459                                 lastCheck = inCheck; // check evasion does not count
7460                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7461                                 if(inCheck || lastCheck) count--; // check does not count
7462                                 i++;
7463                         }
7464                 }
7465                 if( count >= 100)
7466                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7467                          /* this is used to judge if draw claims are legal */
7468                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7469                          if(engineOpponent) {
7470                            SendToProgram("force\n", engineOpponent); // suppress reply
7471                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7472                          }
7473                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7474                          return 1;
7475                 }
7476
7477                 /* if draw offer is pending, treat it as a draw claim
7478                  * when draw condition present, to allow engines a way to
7479                  * claim draws before making their move to avoid a race
7480                  * condition occurring after their move
7481                  */
7482                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7483                          char *p = NULL;
7484                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7485                              p = "Draw claim: 50-move rule";
7486                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7487                              p = "Draw claim: 3-fold repetition";
7488                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7489                              p = "Draw claim: insufficient mating material";
7490                          if( p != NULL && canAdjudicate) {
7491                              if(engineOpponent) {
7492                                SendToProgram("force\n", engineOpponent); // suppress reply
7493                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7494                              }
7495                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7496                              return 1;
7497                          }
7498                 }
7499
7500                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7501                     if(engineOpponent) {
7502                       SendToProgram("force\n", engineOpponent); // suppress reply
7503                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7504                     }
7505                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7506                     return 1;
7507                 }
7508         return 0;
7509 }
7510
7511 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7512 {   // [HGM] book: this routine intercepts moves to simulate book replies
7513     char *bookHit = NULL;
7514
7515     //first determine if the incoming move brings opponent into his book
7516     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7517         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7518     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7519     if(bookHit != NULL && !cps->bookSuspend) {
7520         // make sure opponent is not going to reply after receiving move to book position
7521         SendToProgram("force\n", cps);
7522         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7523     }
7524     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7525     // now arrange restart after book miss
7526     if(bookHit) {
7527         // after a book hit we never send 'go', and the code after the call to this routine
7528         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7529         char buf[MSG_SIZ];
7530         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7531         SendToProgram(buf, cps);
7532         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7533     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7534         SendToProgram("go\n", cps);
7535         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7536     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7537         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7538             SendToProgram("go\n", cps);
7539         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7540     }
7541     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7542 }
7543
7544 char *savedMessage;
7545 ChessProgramState *savedState;
7546 void DeferredBookMove(void)
7547 {
7548         if(savedState->lastPing != savedState->lastPong)
7549                     ScheduleDelayedEvent(DeferredBookMove, 10);
7550         else
7551         HandleMachineMove(savedMessage, savedState);
7552 }
7553
7554 void
7555 HandleMachineMove(message, cps)
7556      char *message;
7557      ChessProgramState *cps;
7558 {
7559     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7560     char realname[MSG_SIZ];
7561     int fromX, fromY, toX, toY;
7562     ChessMove moveType;
7563     char promoChar;
7564     char *p;
7565     int machineWhite;
7566     char *bookHit;
7567
7568     cps->userError = 0;
7569
7570 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7571     /*
7572      * Kludge to ignore BEL characters
7573      */
7574     while (*message == '\007') message++;
7575
7576     /*
7577      * [HGM] engine debug message: ignore lines starting with '#' character
7578      */
7579     if(cps->debug && *message == '#') return;
7580
7581     /*
7582      * Look for book output
7583      */
7584     if (cps == &first && bookRequested) {
7585         if (message[0] == '\t' || message[0] == ' ') {
7586             /* Part of the book output is here; append it */
7587             strcat(bookOutput, message);
7588             strcat(bookOutput, "  \n");
7589             return;
7590         } else if (bookOutput[0] != NULLCHAR) {
7591             /* All of book output has arrived; display it */
7592             char *p = bookOutput;
7593             while (*p != NULLCHAR) {
7594                 if (*p == '\t') *p = ' ';
7595                 p++;
7596             }
7597             DisplayInformation(bookOutput);
7598             bookRequested = FALSE;
7599             /* Fall through to parse the current output */
7600         }
7601     }
7602
7603     /*
7604      * Look for machine move.
7605      */
7606     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7607         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7608     {
7609         /* This method is only useful on engines that support ping */
7610         if (cps->lastPing != cps->lastPong) {
7611           if (gameMode == BeginningOfGame) {
7612             /* Extra move from before last new; ignore */
7613             if (appData.debugMode) {
7614                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7615             }
7616           } else {
7617             if (appData.debugMode) {
7618                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7619                         cps->which, gameMode);
7620             }
7621
7622             SendToProgram("undo\n", cps);
7623           }
7624           return;
7625         }
7626
7627         switch (gameMode) {
7628           case BeginningOfGame:
7629             /* Extra move from before last reset; ignore */
7630             if (appData.debugMode) {
7631                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7632             }
7633             return;
7634
7635           case EndOfGame:
7636           case IcsIdle:
7637           default:
7638             /* Extra move after we tried to stop.  The mode test is
7639                not a reliable way of detecting this problem, but it's
7640                the best we can do on engines that don't support ping.
7641             */
7642             if (appData.debugMode) {
7643                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7644                         cps->which, gameMode);
7645             }
7646             SendToProgram("undo\n", cps);
7647             return;
7648
7649           case MachinePlaysWhite:
7650           case IcsPlayingWhite:
7651             machineWhite = TRUE;
7652             break;
7653
7654           case MachinePlaysBlack:
7655           case IcsPlayingBlack:
7656             machineWhite = FALSE;
7657             break;
7658
7659           case TwoMachinesPlay:
7660             machineWhite = (cps->twoMachinesColor[0] == 'w');
7661             break;
7662         }
7663         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7664             if (appData.debugMode) {
7665                 fprintf(debugFP,
7666                         "Ignoring move out of turn by %s, gameMode %d"
7667                         ", forwardMost %d\n",
7668                         cps->which, gameMode, forwardMostMove);
7669             }
7670             return;
7671         }
7672
7673     if (appData.debugMode) { int f = forwardMostMove;
7674         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7675                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7676                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7677     }
7678         if(cps->alphaRank) AlphaRank(machineMove, 4);
7679         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7680                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7681             /* Machine move could not be parsed; ignore it. */
7682           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7683                     machineMove, _(cps->which));
7684             DisplayError(buf1, 0);
7685             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7686                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7687             if (gameMode == TwoMachinesPlay) {
7688               GameEnds(machineWhite ? BlackWins : WhiteWins,
7689                        buf1, GE_XBOARD);
7690             }
7691             return;
7692         }
7693
7694         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7695         /* So we have to redo legality test with true e.p. status here,  */
7696         /* to make sure an illegal e.p. capture does not slip through,   */
7697         /* to cause a forfeit on a justified illegal-move complaint      */
7698         /* of the opponent.                                              */
7699         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7700            ChessMove moveType;
7701            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7702                              fromY, fromX, toY, toX, promoChar);
7703             if (appData.debugMode) {
7704                 int i;
7705                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7706                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7707                 fprintf(debugFP, "castling rights\n");
7708             }
7709             if(moveType == IllegalMove) {
7710               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7711                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7712                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7713                            buf1, GE_XBOARD);
7714                 return;
7715            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7716            /* [HGM] Kludge to handle engines that send FRC-style castling
7717               when they shouldn't (like TSCP-Gothic) */
7718            switch(moveType) {
7719              case WhiteASideCastleFR:
7720              case BlackASideCastleFR:
7721                toX+=2;
7722                currentMoveString[2]++;
7723                break;
7724              case WhiteHSideCastleFR:
7725              case BlackHSideCastleFR:
7726                toX--;
7727                currentMoveString[2]--;
7728                break;
7729              default: ; // nothing to do, but suppresses warning of pedantic compilers
7730            }
7731         }
7732         hintRequested = FALSE;
7733         lastHint[0] = NULLCHAR;
7734         bookRequested = FALSE;
7735         /* Program may be pondering now */
7736         cps->maybeThinking = TRUE;
7737         if (cps->sendTime == 2) cps->sendTime = 1;
7738         if (cps->offeredDraw) cps->offeredDraw--;
7739
7740         /* [AS] Save move info*/
7741         pvInfoList[ forwardMostMove ].score = programStats.score;
7742         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7743         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7744
7745         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7746
7747         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7748         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7749             int count = 0;
7750
7751             while( count < adjudicateLossPlies ) {
7752                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7753
7754                 if( count & 1 ) {
7755                     score = -score; /* Flip score for winning side */
7756                 }
7757
7758                 if( score > adjudicateLossThreshold ) {
7759                     break;
7760                 }
7761
7762                 count++;
7763             }
7764
7765             if( count >= adjudicateLossPlies ) {
7766                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7767
7768                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7769                     "Xboard adjudication",
7770                     GE_XBOARD );
7771
7772                 return;
7773             }
7774         }
7775
7776         if(Adjudicate(cps)) {
7777             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7778             return; // [HGM] adjudicate: for all automatic game ends
7779         }
7780
7781 #if ZIPPY
7782         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7783             first.initDone) {
7784           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7785                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7786                 SendToICS("draw ");
7787                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7788           }
7789           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7790           ics_user_moved = 1;
7791           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7792                 char buf[3*MSG_SIZ];
7793
7794                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7795                         programStats.score / 100.,
7796                         programStats.depth,
7797                         programStats.time / 100.,
7798                         (unsigned int)programStats.nodes,
7799                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7800                         programStats.movelist);
7801                 SendToICS(buf);
7802 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7803           }
7804         }
7805 #endif
7806
7807         /* [AS] Clear stats for next move */
7808         ClearProgramStats();
7809         thinkOutput[0] = NULLCHAR;
7810         hiddenThinkOutputState = 0;
7811
7812         bookHit = NULL;
7813         if (gameMode == TwoMachinesPlay) {
7814             /* [HGM] relaying draw offers moved to after reception of move */
7815             /* and interpreting offer as claim if it brings draw condition */
7816             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7817                 SendToProgram("draw\n", cps->other);
7818             }
7819             if (cps->other->sendTime) {
7820                 SendTimeRemaining(cps->other,
7821                                   cps->other->twoMachinesColor[0] == 'w');
7822             }
7823             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7824             if (firstMove && !bookHit) {
7825                 firstMove = FALSE;
7826                 if (cps->other->useColors) {
7827                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7828                 }
7829                 SendToProgram("go\n", cps->other);
7830             }
7831             cps->other->maybeThinking = TRUE;
7832         }
7833
7834         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7835
7836         if (!pausing && appData.ringBellAfterMoves) {
7837             RingBell();
7838         }
7839
7840         /*
7841          * Reenable menu items that were disabled while
7842          * machine was thinking
7843          */
7844         if (gameMode != TwoMachinesPlay)
7845             SetUserThinkingEnables();
7846
7847         // [HGM] book: after book hit opponent has received move and is now in force mode
7848         // force the book reply into it, and then fake that it outputted this move by jumping
7849         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7850         if(bookHit) {
7851                 static char bookMove[MSG_SIZ]; // a bit generous?
7852
7853                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7854                 strcat(bookMove, bookHit);
7855                 message = bookMove;
7856                 cps = cps->other;
7857                 programStats.nodes = programStats.depth = programStats.time =
7858                 programStats.score = programStats.got_only_move = 0;
7859                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7860
7861                 if(cps->lastPing != cps->lastPong) {
7862                     savedMessage = message; // args for deferred call
7863                     savedState = cps;
7864                     ScheduleDelayedEvent(DeferredBookMove, 10);
7865                     return;
7866                 }
7867                 goto FakeBookMove;
7868         }
7869
7870         return;
7871     }
7872
7873     /* Set special modes for chess engines.  Later something general
7874      *  could be added here; for now there is just one kludge feature,
7875      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7876      *  when "xboard" is given as an interactive command.
7877      */
7878     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7879         cps->useSigint = FALSE;
7880         cps->useSigterm = FALSE;
7881     }
7882     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7883       ParseFeatures(message+8, cps);
7884       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7885     }
7886
7887     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7888       int dummy, s=6; char buf[MSG_SIZ];
7889       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7890       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7891       ParseFEN(boards[0], &dummy, message+s);
7892       DrawPosition(TRUE, boards[0]);
7893       startedFromSetupPosition = TRUE;
7894       return;
7895     }
7896     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7897      * want this, I was asked to put it in, and obliged.
7898      */
7899     if (!strncmp(message, "setboard ", 9)) {
7900         Board initial_position;
7901
7902         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7903
7904         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7905             DisplayError(_("Bad FEN received from engine"), 0);
7906             return ;
7907         } else {
7908            Reset(TRUE, FALSE);
7909            CopyBoard(boards[0], initial_position);
7910            initialRulePlies = FENrulePlies;
7911            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7912            else gameMode = MachinePlaysBlack;
7913            DrawPosition(FALSE, boards[currentMove]);
7914         }
7915         return;
7916     }
7917
7918     /*
7919      * Look for communication commands
7920      */
7921     if (!strncmp(message, "telluser ", 9)) {
7922         if(message[9] == '\\' && message[10] == '\\')
7923             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7924         DisplayNote(message + 9);
7925         return;
7926     }
7927     if (!strncmp(message, "tellusererror ", 14)) {
7928         cps->userError = 1;
7929         if(message[14] == '\\' && message[15] == '\\')
7930             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7931         DisplayError(message + 14, 0);
7932         return;
7933     }
7934     if (!strncmp(message, "tellopponent ", 13)) {
7935       if (appData.icsActive) {
7936         if (loggedOn) {
7937           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7938           SendToICS(buf1);
7939         }
7940       } else {
7941         DisplayNote(message + 13);
7942       }
7943       return;
7944     }
7945     if (!strncmp(message, "tellothers ", 11)) {
7946       if (appData.icsActive) {
7947         if (loggedOn) {
7948           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7949           SendToICS(buf1);
7950         }
7951       }
7952       return;
7953     }
7954     if (!strncmp(message, "tellall ", 8)) {
7955       if (appData.icsActive) {
7956         if (loggedOn) {
7957           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7958           SendToICS(buf1);
7959         }
7960       } else {
7961         DisplayNote(message + 8);
7962       }
7963       return;
7964     }
7965     if (strncmp(message, "warning", 7) == 0) {
7966         /* Undocumented feature, use tellusererror in new code */
7967         DisplayError(message, 0);
7968         return;
7969     }
7970     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7971         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7972         strcat(realname, " query");
7973         AskQuestion(realname, buf2, buf1, cps->pr);
7974         return;
7975     }
7976     /* Commands from the engine directly to ICS.  We don't allow these to be
7977      *  sent until we are logged on. Crafty kibitzes have been known to
7978      *  interfere with the login process.
7979      */
7980     if (loggedOn) {
7981         if (!strncmp(message, "tellics ", 8)) {
7982             SendToICS(message + 8);
7983             SendToICS("\n");
7984             return;
7985         }
7986         if (!strncmp(message, "tellicsnoalias ", 15)) {
7987             SendToICS(ics_prefix);
7988             SendToICS(message + 15);
7989             SendToICS("\n");
7990             return;
7991         }
7992         /* The following are for backward compatibility only */
7993         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7994             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7995             SendToICS(ics_prefix);
7996             SendToICS(message);
7997             SendToICS("\n");
7998             return;
7999         }
8000     }
8001     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8002         return;
8003     }
8004     /*
8005      * If the move is illegal, cancel it and redraw the board.
8006      * Also deal with other error cases.  Matching is rather loose
8007      * here to accommodate engines written before the spec.
8008      */
8009     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8010         strncmp(message, "Error", 5) == 0) {
8011         if (StrStr(message, "name") ||
8012             StrStr(message, "rating") || StrStr(message, "?") ||
8013             StrStr(message, "result") || StrStr(message, "board") ||
8014             StrStr(message, "bk") || StrStr(message, "computer") ||
8015             StrStr(message, "variant") || StrStr(message, "hint") ||
8016             StrStr(message, "random") || StrStr(message, "depth") ||
8017             StrStr(message, "accepted")) {
8018             return;
8019         }
8020         if (StrStr(message, "protover")) {
8021           /* Program is responding to input, so it's apparently done
8022              initializing, and this error message indicates it is
8023              protocol version 1.  So we don't need to wait any longer
8024              for it to initialize and send feature commands. */
8025           FeatureDone(cps, 1);
8026           cps->protocolVersion = 1;
8027           return;
8028         }
8029         cps->maybeThinking = FALSE;
8030
8031         if (StrStr(message, "draw")) {
8032             /* Program doesn't have "draw" command */
8033             cps->sendDrawOffers = 0;
8034             return;
8035         }
8036         if (cps->sendTime != 1 &&
8037             (StrStr(message, "time") || StrStr(message, "otim"))) {
8038           /* Program apparently doesn't have "time" or "otim" command */
8039           cps->sendTime = 0;
8040           return;
8041         }
8042         if (StrStr(message, "analyze")) {
8043             cps->analysisSupport = FALSE;
8044             cps->analyzing = FALSE;
8045             Reset(FALSE, TRUE);
8046             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8047             DisplayError(buf2, 0);
8048             return;
8049         }
8050         if (StrStr(message, "(no matching move)st")) {
8051           /* Special kludge for GNU Chess 4 only */
8052           cps->stKludge = TRUE;
8053           SendTimeControl(cps, movesPerSession, timeControl,
8054                           timeIncrement, appData.searchDepth,
8055                           searchTime);
8056           return;
8057         }
8058         if (StrStr(message, "(no matching move)sd")) {
8059           /* Special kludge for GNU Chess 4 only */
8060           cps->sdKludge = TRUE;
8061           SendTimeControl(cps, movesPerSession, timeControl,
8062                           timeIncrement, appData.searchDepth,
8063                           searchTime);
8064           return;
8065         }
8066         if (!StrStr(message, "llegal")) {
8067             return;
8068         }
8069         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8070             gameMode == IcsIdle) return;
8071         if (forwardMostMove <= backwardMostMove) return;
8072         if (pausing) PauseEvent();
8073       if(appData.forceIllegal) {
8074             // [HGM] illegal: machine refused move; force position after move into it
8075           SendToProgram("force\n", cps);
8076           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8077                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8078                 // when black is to move, while there might be nothing on a2 or black
8079                 // might already have the move. So send the board as if white has the move.
8080                 // But first we must change the stm of the engine, as it refused the last move
8081                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8082                 if(WhiteOnMove(forwardMostMove)) {
8083                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8084                     SendBoard(cps, forwardMostMove); // kludgeless board
8085                 } else {
8086                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8087                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8088                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8089                 }
8090           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8091             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8092                  gameMode == TwoMachinesPlay)
8093               SendToProgram("go\n", cps);
8094             return;
8095       } else
8096         if (gameMode == PlayFromGameFile) {
8097             /* Stop reading this game file */
8098             gameMode = EditGame;
8099             ModeHighlight();
8100         }
8101         /* [HGM] illegal-move claim should forfeit game when Xboard */
8102         /* only passes fully legal moves                            */
8103         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8104             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8105                                 "False illegal-move claim", GE_XBOARD );
8106             return; // do not take back move we tested as valid
8107         }
8108         currentMove = forwardMostMove-1;
8109         DisplayMove(currentMove-1); /* before DisplayMoveError */
8110         SwitchClocks(forwardMostMove-1); // [HGM] race
8111         DisplayBothClocks();
8112         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8113                 parseList[currentMove], _(cps->which));
8114         DisplayMoveError(buf1);
8115         DrawPosition(FALSE, boards[currentMove]);
8116         return;
8117     }
8118     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8119         /* Program has a broken "time" command that
8120            outputs a string not ending in newline.
8121            Don't use it. */
8122         cps->sendTime = 0;
8123     }
8124
8125     /*
8126      * If chess program startup fails, exit with an error message.
8127      * Attempts to recover here are futile.
8128      */
8129     if ((StrStr(message, "unknown host") != NULL)
8130         || (StrStr(message, "No remote directory") != NULL)
8131         || (StrStr(message, "not found") != NULL)
8132         || (StrStr(message, "No such file") != NULL)
8133         || (StrStr(message, "can't alloc") != NULL)
8134         || (StrStr(message, "Permission denied") != NULL)) {
8135
8136         cps->maybeThinking = FALSE;
8137         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8138                 _(cps->which), cps->program, cps->host, message);
8139         RemoveInputSource(cps->isr);
8140         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8141             if(cps == &first) appData.noChessProgram = TRUE;
8142             DisplayError(buf1, 0);
8143         }
8144         return;
8145     }
8146
8147     /*
8148      * Look for hint output
8149      */
8150     if (sscanf(message, "Hint: %s", buf1) == 1) {
8151         if (cps == &first && hintRequested) {
8152             hintRequested = FALSE;
8153             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8154                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8155                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8156                                     PosFlags(forwardMostMove),
8157                                     fromY, fromX, toY, toX, promoChar, buf1);
8158                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8159                 DisplayInformation(buf2);
8160             } else {
8161                 /* Hint move could not be parsed!? */
8162               snprintf(buf2, sizeof(buf2),
8163                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8164                         buf1, _(cps->which));
8165                 DisplayError(buf2, 0);
8166             }
8167         } else {
8168           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8169         }
8170         return;
8171     }
8172
8173     /*
8174      * Ignore other messages if game is not in progress
8175      */
8176     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8177         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8178
8179     /*
8180      * look for win, lose, draw, or draw offer
8181      */
8182     if (strncmp(message, "1-0", 3) == 0) {
8183         char *p, *q, *r = "";
8184         p = strchr(message, '{');
8185         if (p) {
8186             q = strchr(p, '}');
8187             if (q) {
8188                 *q = NULLCHAR;
8189                 r = p + 1;
8190             }
8191         }
8192         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8193         return;
8194     } else if (strncmp(message, "0-1", 3) == 0) {
8195         char *p, *q, *r = "";
8196         p = strchr(message, '{');
8197         if (p) {
8198             q = strchr(p, '}');
8199             if (q) {
8200                 *q = NULLCHAR;
8201                 r = p + 1;
8202             }
8203         }
8204         /* Kludge for Arasan 4.1 bug */
8205         if (strcmp(r, "Black resigns") == 0) {
8206             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8207             return;
8208         }
8209         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8210         return;
8211     } else if (strncmp(message, "1/2", 3) == 0) {
8212         char *p, *q, *r = "";
8213         p = strchr(message, '{');
8214         if (p) {
8215             q = strchr(p, '}');
8216             if (q) {
8217                 *q = NULLCHAR;
8218                 r = p + 1;
8219             }
8220         }
8221
8222         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8223         return;
8224
8225     } else if (strncmp(message, "White resign", 12) == 0) {
8226         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8227         return;
8228     } else if (strncmp(message, "Black resign", 12) == 0) {
8229         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8230         return;
8231     } else if (strncmp(message, "White matches", 13) == 0 ||
8232                strncmp(message, "Black matches", 13) == 0   ) {
8233         /* [HGM] ignore GNUShogi noises */
8234         return;
8235     } else if (strncmp(message, "White", 5) == 0 &&
8236                message[5] != '(' &&
8237                StrStr(message, "Black") == NULL) {
8238         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8239         return;
8240     } else if (strncmp(message, "Black", 5) == 0 &&
8241                message[5] != '(') {
8242         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8243         return;
8244     } else if (strcmp(message, "resign") == 0 ||
8245                strcmp(message, "computer resigns") == 0) {
8246         switch (gameMode) {
8247           case MachinePlaysBlack:
8248           case IcsPlayingBlack:
8249             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8250             break;
8251           case MachinePlaysWhite:
8252           case IcsPlayingWhite:
8253             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8254             break;
8255           case TwoMachinesPlay:
8256             if (cps->twoMachinesColor[0] == 'w')
8257               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8258             else
8259               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8260             break;
8261           default:
8262             /* can't happen */
8263             break;
8264         }
8265         return;
8266     } else if (strncmp(message, "opponent mates", 14) == 0) {
8267         switch (gameMode) {
8268           case MachinePlaysBlack:
8269           case IcsPlayingBlack:
8270             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8271             break;
8272           case MachinePlaysWhite:
8273           case IcsPlayingWhite:
8274             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8275             break;
8276           case TwoMachinesPlay:
8277             if (cps->twoMachinesColor[0] == 'w')
8278               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8279             else
8280               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8281             break;
8282           default:
8283             /* can't happen */
8284             break;
8285         }
8286         return;
8287     } else if (strncmp(message, "computer mates", 14) == 0) {
8288         switch (gameMode) {
8289           case MachinePlaysBlack:
8290           case IcsPlayingBlack:
8291             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8292             break;
8293           case MachinePlaysWhite:
8294           case IcsPlayingWhite:
8295             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8296             break;
8297           case TwoMachinesPlay:
8298             if (cps->twoMachinesColor[0] == 'w')
8299               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8300             else
8301               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8302             break;
8303           default:
8304             /* can't happen */
8305             break;
8306         }
8307         return;
8308     } else if (strncmp(message, "checkmate", 9) == 0) {
8309         if (WhiteOnMove(forwardMostMove)) {
8310             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8311         } else {
8312             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8313         }
8314         return;
8315     } else if (strstr(message, "Draw") != NULL ||
8316                strstr(message, "game is a draw") != NULL) {
8317         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8318         return;
8319     } else if (strstr(message, "offer") != NULL &&
8320                strstr(message, "draw") != NULL) {
8321 #if ZIPPY
8322         if (appData.zippyPlay && first.initDone) {
8323             /* Relay offer to ICS */
8324             SendToICS(ics_prefix);
8325             SendToICS("draw\n");
8326         }
8327 #endif
8328         cps->offeredDraw = 2; /* valid until this engine moves twice */
8329         if (gameMode == TwoMachinesPlay) {
8330             if (cps->other->offeredDraw) {
8331                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8332             /* [HGM] in two-machine mode we delay relaying draw offer      */
8333             /* until after we also have move, to see if it is really claim */
8334             }
8335         } else if (gameMode == MachinePlaysWhite ||
8336                    gameMode == MachinePlaysBlack) {
8337           if (userOfferedDraw) {
8338             DisplayInformation(_("Machine accepts your draw offer"));
8339             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8340           } else {
8341             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8342           }
8343         }
8344     }
8345
8346
8347     /*
8348      * Look for thinking output
8349      */
8350     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8351           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8352                                 ) {
8353         int plylev, mvleft, mvtot, curscore, time;
8354         char mvname[MOVE_LEN];
8355         u64 nodes; // [DM]
8356         char plyext;
8357         int ignore = FALSE;
8358         int prefixHint = FALSE;
8359         mvname[0] = NULLCHAR;
8360
8361         switch (gameMode) {
8362           case MachinePlaysBlack:
8363           case IcsPlayingBlack:
8364             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8365             break;
8366           case MachinePlaysWhite:
8367           case IcsPlayingWhite:
8368             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8369             break;
8370           case AnalyzeMode:
8371           case AnalyzeFile:
8372             break;
8373           case IcsObserving: /* [DM] icsEngineAnalyze */
8374             if (!appData.icsEngineAnalyze) ignore = TRUE;
8375             break;
8376           case TwoMachinesPlay:
8377             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8378                 ignore = TRUE;
8379             }
8380             break;
8381           default:
8382             ignore = TRUE;
8383             break;
8384         }
8385
8386         if (!ignore) {
8387             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8388             buf1[0] = NULLCHAR;
8389             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8390                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8391
8392                 if (plyext != ' ' && plyext != '\t') {
8393                     time *= 100;
8394                 }
8395
8396                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8397                 if( cps->scoreIsAbsolute &&
8398                     ( gameMode == MachinePlaysBlack ||
8399                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8400                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8401                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8402                      !WhiteOnMove(currentMove)
8403                     ) )
8404                 {
8405                     curscore = -curscore;
8406                 }
8407
8408
8409                 tempStats.depth = plylev;
8410                 tempStats.nodes = nodes;
8411                 tempStats.time = time;
8412                 tempStats.score = curscore;
8413                 tempStats.got_only_move = 0;
8414
8415                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8416                         int ticklen;
8417
8418                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8419                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8420                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8421                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8422                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8423                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8424                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8425                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8426                 }
8427
8428                 /* Buffer overflow protection */
8429                 if (buf1[0] != NULLCHAR) {
8430                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8431                         && appData.debugMode) {
8432                         fprintf(debugFP,
8433                                 "PV is too long; using the first %u bytes.\n",
8434                                 (unsigned) sizeof(tempStats.movelist) - 1);
8435                     }
8436
8437                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8438                 } else {
8439                     sprintf(tempStats.movelist, " no PV\n");
8440                 }
8441
8442                 if (tempStats.seen_stat) {
8443                     tempStats.ok_to_send = 1;
8444                 }
8445
8446                 if (strchr(tempStats.movelist, '(') != NULL) {
8447                     tempStats.line_is_book = 1;
8448                     tempStats.nr_moves = 0;
8449                     tempStats.moves_left = 0;
8450                 } else {
8451                     tempStats.line_is_book = 0;
8452                 }
8453
8454                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8455                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8456
8457                 SendProgramStatsToFrontend( cps, &tempStats );
8458
8459                 /*
8460                     [AS] Protect the thinkOutput buffer from overflow... this
8461                     is only useful if buf1 hasn't overflowed first!
8462                 */
8463                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8464                          plylev,
8465                          (gameMode == TwoMachinesPlay ?
8466                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8467                          ((double) curscore) / 100.0,
8468                          prefixHint ? lastHint : "",
8469                          prefixHint ? " " : "" );
8470
8471                 if( buf1[0] != NULLCHAR ) {
8472                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8473
8474                     if( strlen(buf1) > max_len ) {
8475                         if( appData.debugMode) {
8476                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8477                         }
8478                         buf1[max_len+1] = '\0';
8479                     }
8480
8481                     strcat( thinkOutput, buf1 );
8482                 }
8483
8484                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8485                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8486                     DisplayMove(currentMove - 1);
8487                 }
8488                 return;
8489
8490             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8491                 /* crafty (9.25+) says "(only move) <move>"
8492                  * if there is only 1 legal move
8493                  */
8494                 sscanf(p, "(only move) %s", buf1);
8495                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8496                 sprintf(programStats.movelist, "%s (only move)", buf1);
8497                 programStats.depth = 1;
8498                 programStats.nr_moves = 1;
8499                 programStats.moves_left = 1;
8500                 programStats.nodes = 1;
8501                 programStats.time = 1;
8502                 programStats.got_only_move = 1;
8503
8504                 /* Not really, but we also use this member to
8505                    mean "line isn't going to change" (Crafty
8506                    isn't searching, so stats won't change) */
8507                 programStats.line_is_book = 1;
8508
8509                 SendProgramStatsToFrontend( cps, &programStats );
8510
8511                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8512                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8513                     DisplayMove(currentMove - 1);
8514                 }
8515                 return;
8516             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8517                               &time, &nodes, &plylev, &mvleft,
8518                               &mvtot, mvname) >= 5) {
8519                 /* The stat01: line is from Crafty (9.29+) in response
8520                    to the "." command */
8521                 programStats.seen_stat = 1;
8522                 cps->maybeThinking = TRUE;
8523
8524                 if (programStats.got_only_move || !appData.periodicUpdates)
8525                   return;
8526
8527                 programStats.depth = plylev;
8528                 programStats.time = time;
8529                 programStats.nodes = nodes;
8530                 programStats.moves_left = mvleft;
8531                 programStats.nr_moves = mvtot;
8532                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8533                 programStats.ok_to_send = 1;
8534                 programStats.movelist[0] = '\0';
8535
8536                 SendProgramStatsToFrontend( cps, &programStats );
8537
8538                 return;
8539
8540             } else if (strncmp(message,"++",2) == 0) {
8541                 /* Crafty 9.29+ outputs this */
8542                 programStats.got_fail = 2;
8543                 return;
8544
8545             } else if (strncmp(message,"--",2) == 0) {
8546                 /* Crafty 9.29+ outputs this */
8547                 programStats.got_fail = 1;
8548                 return;
8549
8550             } else if (thinkOutput[0] != NULLCHAR &&
8551                        strncmp(message, "    ", 4) == 0) {
8552                 unsigned message_len;
8553
8554                 p = message;
8555                 while (*p && *p == ' ') p++;
8556
8557                 message_len = strlen( p );
8558
8559                 /* [AS] Avoid buffer overflow */
8560                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8561                     strcat(thinkOutput, " ");
8562                     strcat(thinkOutput, p);
8563                 }
8564
8565                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8566                     strcat(programStats.movelist, " ");
8567                     strcat(programStats.movelist, p);
8568                 }
8569
8570                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8571                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8572                     DisplayMove(currentMove - 1);
8573                 }
8574                 return;
8575             }
8576         }
8577         else {
8578             buf1[0] = NULLCHAR;
8579
8580             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8581                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8582             {
8583                 ChessProgramStats cpstats;
8584
8585                 if (plyext != ' ' && plyext != '\t') {
8586                     time *= 100;
8587                 }
8588
8589                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8590                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8591                     curscore = -curscore;
8592                 }
8593
8594                 cpstats.depth = plylev;
8595                 cpstats.nodes = nodes;
8596                 cpstats.time = time;
8597                 cpstats.score = curscore;
8598                 cpstats.got_only_move = 0;
8599                 cpstats.movelist[0] = '\0';
8600
8601                 if (buf1[0] != NULLCHAR) {
8602                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8603                 }
8604
8605                 cpstats.ok_to_send = 0;
8606                 cpstats.line_is_book = 0;
8607                 cpstats.nr_moves = 0;
8608                 cpstats.moves_left = 0;
8609
8610                 SendProgramStatsToFrontend( cps, &cpstats );
8611             }
8612         }
8613     }
8614 }
8615
8616
8617 /* Parse a game score from the character string "game", and
8618    record it as the history of the current game.  The game
8619    score is NOT assumed to start from the standard position.
8620    The display is not updated in any way.
8621    */
8622 void
8623 ParseGameHistory(game)
8624      char *game;
8625 {
8626     ChessMove moveType;
8627     int fromX, fromY, toX, toY, boardIndex;
8628     char promoChar;
8629     char *p, *q;
8630     char buf[MSG_SIZ];
8631
8632     if (appData.debugMode)
8633       fprintf(debugFP, "Parsing game history: %s\n", game);
8634
8635     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8636     gameInfo.site = StrSave(appData.icsHost);
8637     gameInfo.date = PGNDate();
8638     gameInfo.round = StrSave("-");
8639
8640     /* Parse out names of players */
8641     while (*game == ' ') game++;
8642     p = buf;
8643     while (*game != ' ') *p++ = *game++;
8644     *p = NULLCHAR;
8645     gameInfo.white = StrSave(buf);
8646     while (*game == ' ') game++;
8647     p = buf;
8648     while (*game != ' ' && *game != '\n') *p++ = *game++;
8649     *p = NULLCHAR;
8650     gameInfo.black = StrSave(buf);
8651
8652     /* Parse moves */
8653     boardIndex = blackPlaysFirst ? 1 : 0;
8654     yynewstr(game);
8655     for (;;) {
8656         yyboardindex = boardIndex;
8657         moveType = (ChessMove) Myylex();
8658         switch (moveType) {
8659           case IllegalMove:             /* maybe suicide chess, etc. */
8660   if (appData.debugMode) {
8661     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8662     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8663     setbuf(debugFP, NULL);
8664   }
8665           case WhitePromotion:
8666           case BlackPromotion:
8667           case WhiteNonPromotion:
8668           case BlackNonPromotion:
8669           case NormalMove:
8670           case WhiteCapturesEnPassant:
8671           case BlackCapturesEnPassant:
8672           case WhiteKingSideCastle:
8673           case WhiteQueenSideCastle:
8674           case BlackKingSideCastle:
8675           case BlackQueenSideCastle:
8676           case WhiteKingSideCastleWild:
8677           case WhiteQueenSideCastleWild:
8678           case BlackKingSideCastleWild:
8679           case BlackQueenSideCastleWild:
8680           /* PUSH Fabien */
8681           case WhiteHSideCastleFR:
8682           case WhiteASideCastleFR:
8683           case BlackHSideCastleFR:
8684           case BlackASideCastleFR:
8685           /* POP Fabien */
8686             fromX = currentMoveString[0] - AAA;
8687             fromY = currentMoveString[1] - ONE;
8688             toX = currentMoveString[2] - AAA;
8689             toY = currentMoveString[3] - ONE;
8690             promoChar = currentMoveString[4];
8691             break;
8692           case WhiteDrop:
8693           case BlackDrop:
8694             fromX = moveType == WhiteDrop ?
8695               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8696             (int) CharToPiece(ToLower(currentMoveString[0]));
8697             fromY = DROP_RANK;
8698             toX = currentMoveString[2] - AAA;
8699             toY = currentMoveString[3] - ONE;
8700             promoChar = NULLCHAR;
8701             break;
8702           case AmbiguousMove:
8703             /* bug? */
8704             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8705   if (appData.debugMode) {
8706     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8707     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8708     setbuf(debugFP, NULL);
8709   }
8710             DisplayError(buf, 0);
8711             return;
8712           case ImpossibleMove:
8713             /* bug? */
8714             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8715   if (appData.debugMode) {
8716     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8717     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8718     setbuf(debugFP, NULL);
8719   }
8720             DisplayError(buf, 0);
8721             return;
8722           case EndOfFile:
8723             if (boardIndex < backwardMostMove) {
8724                 /* Oops, gap.  How did that happen? */
8725                 DisplayError(_("Gap in move list"), 0);
8726                 return;
8727             }
8728             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8729             if (boardIndex > forwardMostMove) {
8730                 forwardMostMove = boardIndex;
8731             }
8732             return;
8733           case ElapsedTime:
8734             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8735                 strcat(parseList[boardIndex-1], " ");
8736                 strcat(parseList[boardIndex-1], yy_text);
8737             }
8738             continue;
8739           case Comment:
8740           case PGNTag:
8741           case NAG:
8742           default:
8743             /* ignore */
8744             continue;
8745           case WhiteWins:
8746           case BlackWins:
8747           case GameIsDrawn:
8748           case GameUnfinished:
8749             if (gameMode == IcsExamining) {
8750                 if (boardIndex < backwardMostMove) {
8751                     /* Oops, gap.  How did that happen? */
8752                     return;
8753                 }
8754                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8755                 return;
8756             }
8757             gameInfo.result = moveType;
8758             p = strchr(yy_text, '{');
8759             if (p == NULL) p = strchr(yy_text, '(');
8760             if (p == NULL) {
8761                 p = yy_text;
8762                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8763             } else {
8764                 q = strchr(p, *p == '{' ? '}' : ')');
8765                 if (q != NULL) *q = NULLCHAR;
8766                 p++;
8767             }
8768             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8769             gameInfo.resultDetails = StrSave(p);
8770             continue;
8771         }
8772         if (boardIndex >= forwardMostMove &&
8773             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8774             backwardMostMove = blackPlaysFirst ? 1 : 0;
8775             return;
8776         }
8777         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8778                                  fromY, fromX, toY, toX, promoChar,
8779                                  parseList[boardIndex]);
8780         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8781         /* currentMoveString is set as a side-effect of yylex */
8782         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8783         strcat(moveList[boardIndex], "\n");
8784         boardIndex++;
8785         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8786         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8787           case MT_NONE:
8788           case MT_STALEMATE:
8789           default:
8790             break;
8791           case MT_CHECK:
8792             if(gameInfo.variant != VariantShogi)
8793                 strcat(parseList[boardIndex - 1], "+");
8794             break;
8795           case MT_CHECKMATE:
8796           case MT_STAINMATE:
8797             strcat(parseList[boardIndex - 1], "#");
8798             break;
8799         }
8800     }
8801 }
8802
8803
8804 /* Apply a move to the given board  */
8805 void
8806 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8807      int fromX, fromY, toX, toY;
8808      int promoChar;
8809      Board board;
8810 {
8811   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8812   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8813
8814     /* [HGM] compute & store e.p. status and castling rights for new position */
8815     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8816
8817       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8818       oldEP = (signed char)board[EP_STATUS];
8819       board[EP_STATUS] = EP_NONE;
8820
8821       if( board[toY][toX] != EmptySquare )
8822            board[EP_STATUS] = EP_CAPTURE;
8823
8824   if (fromY == DROP_RANK) {
8825         /* must be first */
8826         piece = board[toY][toX] = (ChessSquare) fromX;
8827   } else {
8828       int i;
8829
8830       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8831            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8832                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8833       } else
8834       if( board[fromY][fromX] == WhitePawn ) {
8835            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8836                board[EP_STATUS] = EP_PAWN_MOVE;
8837            if( toY-fromY==2) {
8838                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8839                         gameInfo.variant != VariantBerolina || toX < fromX)
8840                       board[EP_STATUS] = toX | berolina;
8841                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8842                         gameInfo.variant != VariantBerolina || toX > fromX)
8843                       board[EP_STATUS] = toX;
8844            }
8845       } else
8846       if( board[fromY][fromX] == BlackPawn ) {
8847            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8848                board[EP_STATUS] = EP_PAWN_MOVE;
8849            if( toY-fromY== -2) {
8850                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8851                         gameInfo.variant != VariantBerolina || toX < fromX)
8852                       board[EP_STATUS] = toX | berolina;
8853                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8854                         gameInfo.variant != VariantBerolina || toX > fromX)
8855                       board[EP_STATUS] = toX;
8856            }
8857        }
8858
8859        for(i=0; i<nrCastlingRights; i++) {
8860            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8861               board[CASTLING][i] == toX   && castlingRank[i] == toY
8862              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8863        }
8864
8865      if (fromX == toX && fromY == toY) return;
8866
8867      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8868      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8869      if(gameInfo.variant == VariantKnightmate)
8870          king += (int) WhiteUnicorn - (int) WhiteKing;
8871
8872     /* Code added by Tord: */
8873     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8874     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8875         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8876       board[fromY][fromX] = EmptySquare;
8877       board[toY][toX] = EmptySquare;
8878       if((toX > fromX) != (piece == WhiteRook)) {
8879         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8880       } else {
8881         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8882       }
8883     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8884                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8885       board[fromY][fromX] = EmptySquare;
8886       board[toY][toX] = EmptySquare;
8887       if((toX > fromX) != (piece == BlackRook)) {
8888         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8889       } else {
8890         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8891       }
8892     /* End of code added by Tord */
8893
8894     } else if (board[fromY][fromX] == king
8895         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8896         && toY == fromY && toX > fromX+1) {
8897         board[fromY][fromX] = EmptySquare;
8898         board[toY][toX] = king;
8899         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8900         board[fromY][BOARD_RGHT-1] = EmptySquare;
8901     } else if (board[fromY][fromX] == king
8902         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8903                && toY == fromY && toX < fromX-1) {
8904         board[fromY][fromX] = EmptySquare;
8905         board[toY][toX] = king;
8906         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8907         board[fromY][BOARD_LEFT] = EmptySquare;
8908     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8909                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8910                && toY >= BOARD_HEIGHT-promoRank
8911                ) {
8912         /* white pawn promotion */
8913         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8914         if (board[toY][toX] == EmptySquare) {
8915             board[toY][toX] = WhiteQueen;
8916         }
8917         if(gameInfo.variant==VariantBughouse ||
8918            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8919             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8920         board[fromY][fromX] = EmptySquare;
8921     } else if ((fromY == BOARD_HEIGHT-4)
8922                && (toX != fromX)
8923                && gameInfo.variant != VariantXiangqi
8924                && gameInfo.variant != VariantBerolina
8925                && (board[fromY][fromX] == WhitePawn)
8926                && (board[toY][toX] == EmptySquare)) {
8927         board[fromY][fromX] = EmptySquare;
8928         board[toY][toX] = WhitePawn;
8929         captured = board[toY - 1][toX];
8930         board[toY - 1][toX] = EmptySquare;
8931     } else if ((fromY == BOARD_HEIGHT-4)
8932                && (toX == fromX)
8933                && gameInfo.variant == VariantBerolina
8934                && (board[fromY][fromX] == WhitePawn)
8935                && (board[toY][toX] == EmptySquare)) {
8936         board[fromY][fromX] = EmptySquare;
8937         board[toY][toX] = WhitePawn;
8938         if(oldEP & EP_BEROLIN_A) {
8939                 captured = board[fromY][fromX-1];
8940                 board[fromY][fromX-1] = EmptySquare;
8941         }else{  captured = board[fromY][fromX+1];
8942                 board[fromY][fromX+1] = EmptySquare;
8943         }
8944     } else if (board[fromY][fromX] == king
8945         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8946                && toY == fromY && toX > fromX+1) {
8947         board[fromY][fromX] = EmptySquare;
8948         board[toY][toX] = king;
8949         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8950         board[fromY][BOARD_RGHT-1] = EmptySquare;
8951     } else if (board[fromY][fromX] == king
8952         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8953                && toY == fromY && toX < fromX-1) {
8954         board[fromY][fromX] = EmptySquare;
8955         board[toY][toX] = king;
8956         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8957         board[fromY][BOARD_LEFT] = EmptySquare;
8958     } else if (fromY == 7 && fromX == 3
8959                && board[fromY][fromX] == BlackKing
8960                && toY == 7 && toX == 5) {
8961         board[fromY][fromX] = EmptySquare;
8962         board[toY][toX] = BlackKing;
8963         board[fromY][7] = EmptySquare;
8964         board[toY][4] = BlackRook;
8965     } else if (fromY == 7 && fromX == 3
8966                && board[fromY][fromX] == BlackKing
8967                && toY == 7 && toX == 1) {
8968         board[fromY][fromX] = EmptySquare;
8969         board[toY][toX] = BlackKing;
8970         board[fromY][0] = EmptySquare;
8971         board[toY][2] = BlackRook;
8972     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8973                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8974                && toY < promoRank
8975                ) {
8976         /* black pawn promotion */
8977         board[toY][toX] = CharToPiece(ToLower(promoChar));
8978         if (board[toY][toX] == EmptySquare) {
8979             board[toY][toX] = BlackQueen;
8980         }
8981         if(gameInfo.variant==VariantBughouse ||
8982            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8983             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8984         board[fromY][fromX] = EmptySquare;
8985     } else if ((fromY == 3)
8986                && (toX != fromX)
8987                && gameInfo.variant != VariantXiangqi
8988                && gameInfo.variant != VariantBerolina
8989                && (board[fromY][fromX] == BlackPawn)
8990                && (board[toY][toX] == EmptySquare)) {
8991         board[fromY][fromX] = EmptySquare;
8992         board[toY][toX] = BlackPawn;
8993         captured = board[toY + 1][toX];
8994         board[toY + 1][toX] = EmptySquare;
8995     } else if ((fromY == 3)
8996                && (toX == fromX)
8997                && gameInfo.variant == VariantBerolina
8998                && (board[fromY][fromX] == BlackPawn)
8999                && (board[toY][toX] == EmptySquare)) {
9000         board[fromY][fromX] = EmptySquare;
9001         board[toY][toX] = BlackPawn;
9002         if(oldEP & EP_BEROLIN_A) {
9003                 captured = board[fromY][fromX-1];
9004                 board[fromY][fromX-1] = EmptySquare;
9005         }else{  captured = board[fromY][fromX+1];
9006                 board[fromY][fromX+1] = EmptySquare;
9007         }
9008     } else {
9009         board[toY][toX] = board[fromY][fromX];
9010         board[fromY][fromX] = EmptySquare;
9011     }
9012   }
9013
9014     if (gameInfo.holdingsWidth != 0) {
9015
9016       /* !!A lot more code needs to be written to support holdings  */
9017       /* [HGM] OK, so I have written it. Holdings are stored in the */
9018       /* penultimate board files, so they are automaticlly stored   */
9019       /* in the game history.                                       */
9020       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9021                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9022         /* Delete from holdings, by decreasing count */
9023         /* and erasing image if necessary            */
9024         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9025         if(p < (int) BlackPawn) { /* white drop */
9026              p -= (int)WhitePawn;
9027                  p = PieceToNumber((ChessSquare)p);
9028              if(p >= gameInfo.holdingsSize) p = 0;
9029              if(--board[p][BOARD_WIDTH-2] <= 0)
9030                   board[p][BOARD_WIDTH-1] = EmptySquare;
9031              if((int)board[p][BOARD_WIDTH-2] < 0)
9032                         board[p][BOARD_WIDTH-2] = 0;
9033         } else {                  /* black drop */
9034              p -= (int)BlackPawn;
9035                  p = PieceToNumber((ChessSquare)p);
9036              if(p >= gameInfo.holdingsSize) p = 0;
9037              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9038                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9039              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9040                         board[BOARD_HEIGHT-1-p][1] = 0;
9041         }
9042       }
9043       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9044           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9045         /* [HGM] holdings: Add to holdings, if holdings exist */
9046         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9047                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9048                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9049         }
9050         p = (int) captured;
9051         if (p >= (int) BlackPawn) {
9052           p -= (int)BlackPawn;
9053           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9054                   /* in Shogi restore piece to its original  first */
9055                   captured = (ChessSquare) (DEMOTED captured);
9056                   p = DEMOTED p;
9057           }
9058           p = PieceToNumber((ChessSquare)p);
9059           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9060           board[p][BOARD_WIDTH-2]++;
9061           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9062         } else {
9063           p -= (int)WhitePawn;
9064           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9065                   captured = (ChessSquare) (DEMOTED captured);
9066                   p = DEMOTED p;
9067           }
9068           p = PieceToNumber((ChessSquare)p);
9069           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9070           board[BOARD_HEIGHT-1-p][1]++;
9071           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9072         }
9073       }
9074     } else if (gameInfo.variant == VariantAtomic) {
9075       if (captured != EmptySquare) {
9076         int y, x;
9077         for (y = toY-1; y <= toY+1; y++) {
9078           for (x = toX-1; x <= toX+1; x++) {
9079             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9080                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9081               board[y][x] = EmptySquare;
9082             }
9083           }
9084         }
9085         board[toY][toX] = EmptySquare;
9086       }
9087     }
9088     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9089         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9090     } else
9091     if(promoChar == '+') {
9092         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9093         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9094     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9095         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9096     }
9097     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9098                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9099         // [HGM] superchess: take promotion piece out of holdings
9100         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9101         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9102             if(!--board[k][BOARD_WIDTH-2])
9103                 board[k][BOARD_WIDTH-1] = EmptySquare;
9104         } else {
9105             if(!--board[BOARD_HEIGHT-1-k][1])
9106                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9107         }
9108     }
9109
9110 }
9111
9112 /* Updates forwardMostMove */
9113 void
9114 MakeMove(fromX, fromY, toX, toY, promoChar)
9115      int fromX, fromY, toX, toY;
9116      int promoChar;
9117 {
9118 //    forwardMostMove++; // [HGM] bare: moved downstream
9119
9120     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9121         int timeLeft; static int lastLoadFlag=0; int king, piece;
9122         piece = boards[forwardMostMove][fromY][fromX];
9123         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9124         if(gameInfo.variant == VariantKnightmate)
9125             king += (int) WhiteUnicorn - (int) WhiteKing;
9126         if(forwardMostMove == 0) {
9127             if(blackPlaysFirst)
9128                 fprintf(serverMoves, "%s;", second.tidy);
9129             fprintf(serverMoves, "%s;", first.tidy);
9130             if(!blackPlaysFirst)
9131                 fprintf(serverMoves, "%s;", second.tidy);
9132         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9133         lastLoadFlag = loadFlag;
9134         // print base move
9135         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9136         // print castling suffix
9137         if( toY == fromY && piece == king ) {
9138             if(toX-fromX > 1)
9139                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9140             if(fromX-toX >1)
9141                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9142         }
9143         // e.p. suffix
9144         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9145              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9146              boards[forwardMostMove][toY][toX] == EmptySquare
9147              && fromX != toX && fromY != toY)
9148                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9149         // promotion suffix
9150         if(promoChar != NULLCHAR)
9151                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9152         if(!loadFlag) {
9153             fprintf(serverMoves, "/%d/%d",
9154                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9155             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9156             else                      timeLeft = blackTimeRemaining/1000;
9157             fprintf(serverMoves, "/%d", timeLeft);
9158         }
9159         fflush(serverMoves);
9160     }
9161
9162     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9163       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9164                         0, 1);
9165       return;
9166     }
9167     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9168     if (commentList[forwardMostMove+1] != NULL) {
9169         free(commentList[forwardMostMove+1]);
9170         commentList[forwardMostMove+1] = NULL;
9171     }
9172     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9173     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9174     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9175     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9176     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9177     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9178     gameInfo.result = GameUnfinished;
9179     if (gameInfo.resultDetails != NULL) {
9180         free(gameInfo.resultDetails);
9181         gameInfo.resultDetails = NULL;
9182     }
9183     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9184                               moveList[forwardMostMove - 1]);
9185     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9186                              PosFlags(forwardMostMove - 1),
9187                              fromY, fromX, toY, toX, promoChar,
9188                              parseList[forwardMostMove - 1]);
9189     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9190       case MT_NONE:
9191       case MT_STALEMATE:
9192       default:
9193         break;
9194       case MT_CHECK:
9195         if(gameInfo.variant != VariantShogi)
9196             strcat(parseList[forwardMostMove - 1], "+");
9197         break;
9198       case MT_CHECKMATE:
9199       case MT_STAINMATE:
9200         strcat(parseList[forwardMostMove - 1], "#");
9201         break;
9202     }
9203     if (appData.debugMode) {
9204         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9205     }
9206
9207 }
9208
9209 /* Updates currentMove if not pausing */
9210 void
9211 ShowMove(fromX, fromY, toX, toY)
9212 {
9213     int instant = (gameMode == PlayFromGameFile) ?
9214         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9215     if(appData.noGUI) return;
9216     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9217         if (!instant) {
9218             if (forwardMostMove == currentMove + 1) {
9219                 AnimateMove(boards[forwardMostMove - 1],
9220                             fromX, fromY, toX, toY);
9221             }
9222             if (appData.highlightLastMove) {
9223                 SetHighlights(fromX, fromY, toX, toY);
9224             }
9225         }
9226         currentMove = forwardMostMove;
9227     }
9228
9229     if (instant) return;
9230
9231     DisplayMove(currentMove - 1);
9232     DrawPosition(FALSE, boards[currentMove]);
9233     DisplayBothClocks();
9234     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9235 }
9236
9237 void SendEgtPath(ChessProgramState *cps)
9238 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9239         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9240
9241         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9242
9243         while(*p) {
9244             char c, *q = name+1, *r, *s;
9245
9246             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9247             while(*p && *p != ',') *q++ = *p++;
9248             *q++ = ':'; *q = 0;
9249             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9250                 strcmp(name, ",nalimov:") == 0 ) {
9251                 // take nalimov path from the menu-changeable option first, if it is defined
9252               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9253                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9254             } else
9255             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9256                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9257                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9258                 s = r = StrStr(s, ":") + 1; // beginning of path info
9259                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9260                 c = *r; *r = 0;             // temporarily null-terminate path info
9261                     *--q = 0;               // strip of trailig ':' from name
9262                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9263                 *r = c;
9264                 SendToProgram(buf,cps);     // send egtbpath command for this format
9265             }
9266             if(*p == ',') p++; // read away comma to position for next format name
9267         }
9268 }
9269
9270 void
9271 InitChessProgram(cps, setup)
9272      ChessProgramState *cps;
9273      int setup; /* [HGM] needed to setup FRC opening position */
9274 {
9275     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9276     if (appData.noChessProgram) return;
9277     hintRequested = FALSE;
9278     bookRequested = FALSE;
9279
9280     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9281     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9282     if(cps->memSize) { /* [HGM] memory */
9283       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9284         SendToProgram(buf, cps);
9285     }
9286     SendEgtPath(cps); /* [HGM] EGT */
9287     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9288       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9289         SendToProgram(buf, cps);
9290     }
9291
9292     SendToProgram(cps->initString, cps);
9293     if (gameInfo.variant != VariantNormal &&
9294         gameInfo.variant != VariantLoadable
9295         /* [HGM] also send variant if board size non-standard */
9296         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9297                                             ) {
9298       char *v = VariantName(gameInfo.variant);
9299       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9300         /* [HGM] in protocol 1 we have to assume all variants valid */
9301         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9302         DisplayFatalError(buf, 0, 1);
9303         return;
9304       }
9305
9306       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9307       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9308       if( gameInfo.variant == VariantXiangqi )
9309            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9310       if( gameInfo.variant == VariantShogi )
9311            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9312       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9313            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9314       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9315           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9316            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9317       if( gameInfo.variant == VariantCourier )
9318            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9319       if( gameInfo.variant == VariantSuper )
9320            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9321       if( gameInfo.variant == VariantGreat )
9322            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9323       if( gameInfo.variant == VariantSChess )
9324            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9325
9326       if(overruled) {
9327         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9328                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9329            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9330            if(StrStr(cps->variants, b) == NULL) {
9331                // specific sized variant not known, check if general sizing allowed
9332                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9333                    if(StrStr(cps->variants, "boardsize") == NULL) {
9334                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9335                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9336                        DisplayFatalError(buf, 0, 1);
9337                        return;
9338                    }
9339                    /* [HGM] here we really should compare with the maximum supported board size */
9340                }
9341            }
9342       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9343       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9344       SendToProgram(buf, cps);
9345     }
9346     currentlyInitializedVariant = gameInfo.variant;
9347
9348     /* [HGM] send opening position in FRC to first engine */
9349     if(setup) {
9350           SendToProgram("force\n", cps);
9351           SendBoard(cps, 0);
9352           /* engine is now in force mode! Set flag to wake it up after first move. */
9353           setboardSpoiledMachineBlack = 1;
9354     }
9355
9356     if (cps->sendICS) {
9357       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9358       SendToProgram(buf, cps);
9359     }
9360     cps->maybeThinking = FALSE;
9361     cps->offeredDraw = 0;
9362     if (!appData.icsActive) {
9363         SendTimeControl(cps, movesPerSession, timeControl,
9364                         timeIncrement, appData.searchDepth,
9365                         searchTime);
9366     }
9367     if (appData.showThinking
9368         // [HGM] thinking: four options require thinking output to be sent
9369         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9370                                 ) {
9371         SendToProgram("post\n", cps);
9372     }
9373     SendToProgram("hard\n", cps);
9374     if (!appData.ponderNextMove) {
9375         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9376            it without being sure what state we are in first.  "hard"
9377            is not a toggle, so that one is OK.
9378          */
9379         SendToProgram("easy\n", cps);
9380     }
9381     if (cps->usePing) {
9382       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9383       SendToProgram(buf, cps);
9384     }
9385     cps->initDone = TRUE;
9386 }
9387
9388
9389 void
9390 StartChessProgram(cps)
9391      ChessProgramState *cps;
9392 {
9393     char buf[MSG_SIZ];
9394     int err;
9395
9396     if (appData.noChessProgram) return;
9397     cps->initDone = FALSE;
9398
9399     if (strcmp(cps->host, "localhost") == 0) {
9400         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9401     } else if (*appData.remoteShell == NULLCHAR) {
9402         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9403     } else {
9404         if (*appData.remoteUser == NULLCHAR) {
9405           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9406                     cps->program);
9407         } else {
9408           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9409                     cps->host, appData.remoteUser, cps->program);
9410         }
9411         err = StartChildProcess(buf, "", &cps->pr);
9412     }
9413
9414     if (err != 0) {
9415       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9416         DisplayFatalError(buf, err, 1);
9417         cps->pr = NoProc;
9418         cps->isr = NULL;
9419         return;
9420     }
9421
9422     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9423     if (cps->protocolVersion > 1) {
9424       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9425       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9426       cps->comboCnt = 0;  //                and values of combo boxes
9427       SendToProgram(buf, cps);
9428     } else {
9429       SendToProgram("xboard\n", cps);
9430     }
9431 }
9432
9433 void
9434 TwoMachinesEventIfReady P((void))
9435 {
9436   static int curMess = 0;
9437   if (first.lastPing != first.lastPong) {
9438     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9439     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9440     return;
9441   }
9442   if (second.lastPing != second.lastPong) {
9443     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9444     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9445     return;
9446   }
9447   DisplayMessage("", ""); curMess = 0;
9448   ThawUI();
9449   TwoMachinesEvent();
9450 }
9451
9452 int
9453 CreateTourney(char *name)
9454 {
9455         FILE *f;
9456         if(name[0] == NULLCHAR) return 0;
9457         f = fopen(appData.tourneyFile, "r");
9458         if(f) { // file exists
9459             ParseArgsFromFile(f); // parse it
9460         } else {
9461             f = fopen(appData.tourneyFile, "w");
9462             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9463                 // create a file with tournament description
9464                 fprintf(f, "-participants {%s}\n", appData.participants);
9465                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9466                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9467                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9468                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9469                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9470                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9471                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9472                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9473                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9474                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9475                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9476                 fprintf(f, "-results \"\"\n");
9477             }
9478         }
9479         fclose(f);
9480         appData.noChessProgram = FALSE;
9481         appData.clockMode = TRUE;
9482         SetGNUMode();
9483         return 1;
9484 }
9485
9486 #define MAXENGINES 1000
9487 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9488
9489 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9490 {
9491     char buf[MSG_SIZ], *p, *q;
9492     int i=1;
9493     while(*names) {
9494         p = names; q = buf;
9495         while(*p && *p != '\n') *q++ = *p++;
9496         *q = 0;
9497         if(engineList[i]) free(engineList[i]);
9498         engineList[i] = strdup(buf);
9499         if(*p == '\n') p++;
9500         TidyProgramName(engineList[i], "localhost", buf);
9501         if(engineMnemonic[i]) free(engineMnemonic[i]);
9502         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9503             strcat(buf, " (");
9504             sscanf(q + 8, "%s", buf + strlen(buf));
9505             strcat(buf, ")");
9506         }
9507         engineMnemonic[i] = strdup(buf);
9508         names = p; i++;
9509       if(i > MAXENGINES - 2) break;
9510     }
9511     engineList[i] = NULL;
9512 }
9513
9514 // following implemented as macro to avoid type limitations
9515 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9516
9517 void SwapEngines(int n)
9518 {   // swap settings for first engine and other engine (so far only some selected options)
9519     int h;
9520     char *p;
9521     if(n == 0) return;
9522     SWAP(directory, p)
9523     SWAP(chessProgram, p)
9524     SWAP(isUCI, h)
9525     SWAP(hasOwnBookUCI, h)
9526     SWAP(protocolVersion, h)
9527     SWAP(reuse, h)
9528     SWAP(scoreIsAbsolute, h)
9529     SWAP(timeOdds, h)
9530     SWAP(logo, p)
9531 }
9532
9533 void
9534 SetPlayer(int player)
9535 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9536     int i;
9537     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9538     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9539                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9540     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9541     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9542     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9543     if(mnemonic[i]) {
9544         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9545         ParseArgsFromString(resetOptions);
9546         ParseArgsFromString(buf);
9547     }
9548     free(engineName);
9549 }
9550
9551 int
9552 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9553 {   // determine players from game number
9554     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9555
9556     if(appData.tourneyType == 0) {
9557         roundsPerCycle = (nPlayers - 1) | 1;
9558         pairingsPerRound = nPlayers / 2;
9559     } else if(appData.tourneyType > 0) {
9560         roundsPerCycle = nPlayers - appData.tourneyType;
9561         pairingsPerRound = appData.tourneyType;
9562     }
9563     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9564     gamesPerCycle = gamesPerRound * roundsPerCycle;
9565     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9566     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9567     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9568     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9569     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9570     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9571
9572     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9573     if(appData.roundSync) *syncInterval = gamesPerRound;
9574
9575     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9576
9577     if(appData.tourneyType == 0) {
9578         if(curPairing == (nPlayers-1)/2 ) {
9579             *whitePlayer = curRound;
9580             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9581         } else {
9582             *whitePlayer = curRound - pairingsPerRound + curPairing;
9583             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9584             *blackPlayer = curRound + pairingsPerRound - curPairing;
9585             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9586         }
9587     } else if(appData.tourneyType > 0) {
9588         *whitePlayer = curPairing;
9589         *blackPlayer = curRound + appData.tourneyType;
9590     }
9591
9592     // take care of white/black alternation per round. 
9593     // For cycles and games this is already taken care of by default, derived from matchGame!
9594     return curRound & 1;
9595 }
9596
9597 int
9598 NextTourneyGame(int nr, int *swapColors)
9599 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9600     char *p, *q;
9601     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9602     FILE *tf;
9603     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9604     tf = fopen(appData.tourneyFile, "r");
9605     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9606     ParseArgsFromFile(tf); fclose(tf);
9607
9608     p = appData.participants;
9609     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9610     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9611
9612     if(syncInterval) {
9613         p = q = appData.results;
9614         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9615         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9616             DisplayMessage(_("Waiting for other game(s)"),"");
9617             waitingForGame = TRUE;
9618             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9619             return 0;
9620         }
9621         waitingForGame = FALSE;
9622     }
9623
9624     if(first.pr != NoProc) return 1; // engines already loaded
9625
9626     // redefine engines, engine dir, etc.
9627     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9628     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9629     SwapEngines(1);
9630     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9631     SwapEngines(1);         // and make that valid for second engine by swapping
9632     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9633     InitEngine(&second, 1);
9634     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9635     return 1;
9636 }
9637
9638 void
9639 NextMatchGame()
9640 {   // performs game initialization that does not invoke engines, and then tries to start the game
9641     int firstWhite, swapColors = 0;
9642     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9643     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9644     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9645     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9646     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9647     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9648     Reset(FALSE, first.pr != NoProc);
9649     appData.noChessProgram = FALSE;
9650     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9651     TwoMachinesEvent();
9652 }
9653
9654 void UserAdjudicationEvent( int result )
9655 {
9656     ChessMove gameResult = GameIsDrawn;
9657
9658     if( result > 0 ) {
9659         gameResult = WhiteWins;
9660     }
9661     else if( result < 0 ) {
9662         gameResult = BlackWins;
9663     }
9664
9665     if( gameMode == TwoMachinesPlay ) {
9666         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9667     }
9668 }
9669
9670
9671 // [HGM] save: calculate checksum of game to make games easily identifiable
9672 int StringCheckSum(char *s)
9673 {
9674         int i = 0;
9675         if(s==NULL) return 0;
9676         while(*s) i = i*259 + *s++;
9677         return i;
9678 }
9679
9680 int GameCheckSum()
9681 {
9682         int i, sum=0;
9683         for(i=backwardMostMove; i<forwardMostMove; i++) {
9684                 sum += pvInfoList[i].depth;
9685                 sum += StringCheckSum(parseList[i]);
9686                 sum += StringCheckSum(commentList[i]);
9687                 sum *= 261;
9688         }
9689         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9690         return sum + StringCheckSum(commentList[i]);
9691 } // end of save patch
9692
9693 void
9694 GameEnds(result, resultDetails, whosays)
9695      ChessMove result;
9696      char *resultDetails;
9697      int whosays;
9698 {
9699     GameMode nextGameMode;
9700     int isIcsGame;
9701     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9702
9703     if(endingGame) return; /* [HGM] crash: forbid recursion */
9704     endingGame = 1;
9705     if(twoBoards) { // [HGM] dual: switch back to one board
9706         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9707         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9708     }
9709     if (appData.debugMode) {
9710       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9711               result, resultDetails ? resultDetails : "(null)", whosays);
9712     }
9713
9714     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9715
9716     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9717         /* If we are playing on ICS, the server decides when the
9718            game is over, but the engine can offer to draw, claim
9719            a draw, or resign.
9720          */
9721 #if ZIPPY
9722         if (appData.zippyPlay && first.initDone) {
9723             if (result == GameIsDrawn) {
9724                 /* In case draw still needs to be claimed */
9725                 SendToICS(ics_prefix);
9726                 SendToICS("draw\n");
9727             } else if (StrCaseStr(resultDetails, "resign")) {
9728                 SendToICS(ics_prefix);
9729                 SendToICS("resign\n");
9730             }
9731         }
9732 #endif
9733         endingGame = 0; /* [HGM] crash */
9734         return;
9735     }
9736
9737     /* If we're loading the game from a file, stop */
9738     if (whosays == GE_FILE) {
9739       (void) StopLoadGameTimer();
9740       gameFileFP = NULL;
9741     }
9742
9743     /* Cancel draw offers */
9744     first.offeredDraw = second.offeredDraw = 0;
9745
9746     /* If this is an ICS game, only ICS can really say it's done;
9747        if not, anyone can. */
9748     isIcsGame = (gameMode == IcsPlayingWhite ||
9749                  gameMode == IcsPlayingBlack ||
9750                  gameMode == IcsObserving    ||
9751                  gameMode == IcsExamining);
9752
9753     if (!isIcsGame || whosays == GE_ICS) {
9754         /* OK -- not an ICS game, or ICS said it was done */
9755         StopClocks();
9756         if (!isIcsGame && !appData.noChessProgram)
9757           SetUserThinkingEnables();
9758
9759         /* [HGM] if a machine claims the game end we verify this claim */
9760         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9761             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9762                 char claimer;
9763                 ChessMove trueResult = (ChessMove) -1;
9764
9765                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9766                                             first.twoMachinesColor[0] :
9767                                             second.twoMachinesColor[0] ;
9768
9769                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9770                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9771                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9772                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9773                 } else
9774                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9775                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9776                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9777                 } else
9778                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9779                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9780                 }
9781
9782                 // now verify win claims, but not in drop games, as we don't understand those yet
9783                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9784                                                  || gameInfo.variant == VariantGreat) &&
9785                     (result == WhiteWins && claimer == 'w' ||
9786                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9787                       if (appData.debugMode) {
9788                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9789                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9790                       }
9791                       if(result != trueResult) {
9792                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9793                               result = claimer == 'w' ? BlackWins : WhiteWins;
9794                               resultDetails = buf;
9795                       }
9796                 } else
9797                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9798                     && (forwardMostMove <= backwardMostMove ||
9799                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9800                         (claimer=='b')==(forwardMostMove&1))
9801                                                                                   ) {
9802                       /* [HGM] verify: draws that were not flagged are false claims */
9803                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9804                       result = claimer == 'w' ? BlackWins : WhiteWins;
9805                       resultDetails = buf;
9806                 }
9807                 /* (Claiming a loss is accepted no questions asked!) */
9808             }
9809             /* [HGM] bare: don't allow bare King to win */
9810             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9811                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9812                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9813                && result != GameIsDrawn)
9814             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9815                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9816                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9817                         if(p >= 0 && p <= (int)WhiteKing) k++;
9818                 }
9819                 if (appData.debugMode) {
9820                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9821                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9822                 }
9823                 if(k <= 1) {
9824                         result = GameIsDrawn;
9825                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9826                         resultDetails = buf;
9827                 }
9828             }
9829         }
9830
9831
9832         if(serverMoves != NULL && !loadFlag) { char c = '=';
9833             if(result==WhiteWins) c = '+';
9834             if(result==BlackWins) c = '-';
9835             if(resultDetails != NULL)
9836                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9837         }
9838         if (resultDetails != NULL) {
9839             gameInfo.result = result;
9840             gameInfo.resultDetails = StrSave(resultDetails);
9841
9842             /* display last move only if game was not loaded from file */
9843             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9844                 DisplayMove(currentMove - 1);
9845
9846             if (forwardMostMove != 0) {
9847                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9848                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9849                                                                 ) {
9850                     if (*appData.saveGameFile != NULLCHAR) {
9851                         SaveGameToFile(appData.saveGameFile, TRUE);
9852                     } else if (appData.autoSaveGames) {
9853                         AutoSaveGame();
9854                     }
9855                     if (*appData.savePositionFile != NULLCHAR) {
9856                         SavePositionToFile(appData.savePositionFile);
9857                     }
9858                 }
9859             }
9860
9861             /* Tell program how game ended in case it is learning */
9862             /* [HGM] Moved this to after saving the PGN, just in case */
9863             /* engine died and we got here through time loss. In that */
9864             /* case we will get a fatal error writing the pipe, which */
9865             /* would otherwise lose us the PGN.                       */
9866             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9867             /* output during GameEnds should never be fatal anymore   */
9868             if (gameMode == MachinePlaysWhite ||
9869                 gameMode == MachinePlaysBlack ||
9870                 gameMode == TwoMachinesPlay ||
9871                 gameMode == IcsPlayingWhite ||
9872                 gameMode == IcsPlayingBlack ||
9873                 gameMode == BeginningOfGame) {
9874                 char buf[MSG_SIZ];
9875                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9876                         resultDetails);
9877                 if (first.pr != NoProc) {
9878                     SendToProgram(buf, &first);
9879                 }
9880                 if (second.pr != NoProc &&
9881                     gameMode == TwoMachinesPlay) {
9882                     SendToProgram(buf, &second);
9883                 }
9884             }
9885         }
9886
9887         if (appData.icsActive) {
9888             if (appData.quietPlay &&
9889                 (gameMode == IcsPlayingWhite ||
9890                  gameMode == IcsPlayingBlack)) {
9891                 SendToICS(ics_prefix);
9892                 SendToICS("set shout 1\n");
9893             }
9894             nextGameMode = IcsIdle;
9895             ics_user_moved = FALSE;
9896             /* clean up premove.  It's ugly when the game has ended and the
9897              * premove highlights are still on the board.
9898              */
9899             if (gotPremove) {
9900               gotPremove = FALSE;
9901               ClearPremoveHighlights();
9902               DrawPosition(FALSE, boards[currentMove]);
9903             }
9904             if (whosays == GE_ICS) {
9905                 switch (result) {
9906                 case WhiteWins:
9907                     if (gameMode == IcsPlayingWhite)
9908                         PlayIcsWinSound();
9909                     else if(gameMode == IcsPlayingBlack)
9910                         PlayIcsLossSound();
9911                     break;
9912                 case BlackWins:
9913                     if (gameMode == IcsPlayingBlack)
9914                         PlayIcsWinSound();
9915                     else if(gameMode == IcsPlayingWhite)
9916                         PlayIcsLossSound();
9917                     break;
9918                 case GameIsDrawn:
9919                     PlayIcsDrawSound();
9920                     break;
9921                 default:
9922                     PlayIcsUnfinishedSound();
9923                 }
9924             }
9925         } else if (gameMode == EditGame ||
9926                    gameMode == PlayFromGameFile ||
9927                    gameMode == AnalyzeMode ||
9928                    gameMode == AnalyzeFile) {
9929             nextGameMode = gameMode;
9930         } else {
9931             nextGameMode = EndOfGame;
9932         }
9933         pausing = FALSE;
9934         ModeHighlight();
9935     } else {
9936         nextGameMode = gameMode;
9937     }
9938
9939     if (appData.noChessProgram) {
9940         gameMode = nextGameMode;
9941         ModeHighlight();
9942         endingGame = 0; /* [HGM] crash */
9943         return;
9944     }
9945
9946     if (first.reuse) {
9947         /* Put first chess program into idle state */
9948         if (first.pr != NoProc &&
9949             (gameMode == MachinePlaysWhite ||
9950              gameMode == MachinePlaysBlack ||
9951              gameMode == TwoMachinesPlay ||
9952              gameMode == IcsPlayingWhite ||
9953              gameMode == IcsPlayingBlack ||
9954              gameMode == BeginningOfGame)) {
9955             SendToProgram("force\n", &first);
9956             if (first.usePing) {
9957               char buf[MSG_SIZ];
9958               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9959               SendToProgram(buf, &first);
9960             }
9961         }
9962     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9963         /* Kill off first chess program */
9964         if (first.isr != NULL)
9965           RemoveInputSource(first.isr);
9966         first.isr = NULL;
9967
9968         if (first.pr != NoProc) {
9969             ExitAnalyzeMode();
9970             DoSleep( appData.delayBeforeQuit );
9971             SendToProgram("quit\n", &first);
9972             DoSleep( appData.delayAfterQuit );
9973             DestroyChildProcess(first.pr, first.useSigterm);
9974         }
9975         first.pr = NoProc;
9976     }
9977     if (second.reuse) {
9978         /* Put second chess program into idle state */
9979         if (second.pr != NoProc &&
9980             gameMode == TwoMachinesPlay) {
9981             SendToProgram("force\n", &second);
9982             if (second.usePing) {
9983               char buf[MSG_SIZ];
9984               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9985               SendToProgram(buf, &second);
9986             }
9987         }
9988     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9989         /* Kill off second chess program */
9990         if (second.isr != NULL)
9991           RemoveInputSource(second.isr);
9992         second.isr = NULL;
9993
9994         if (second.pr != NoProc) {
9995             DoSleep( appData.delayBeforeQuit );
9996             SendToProgram("quit\n", &second);
9997             DoSleep( appData.delayAfterQuit );
9998             DestroyChildProcess(second.pr, second.useSigterm);
9999         }
10000         second.pr = NoProc;
10001     }
10002
10003     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10004         char resChar = '=';
10005         switch (result) {
10006         case WhiteWins:
10007           resChar = '+';
10008           if (first.twoMachinesColor[0] == 'w') {
10009             first.matchWins++;
10010           } else {
10011             second.matchWins++;
10012           }
10013           break;
10014         case BlackWins:
10015           resChar = '-';
10016           if (first.twoMachinesColor[0] == 'b') {
10017             first.matchWins++;
10018           } else {
10019             second.matchWins++;
10020           }
10021           break;
10022         case GameUnfinished:
10023           resChar = ' ';
10024         default:
10025           break;
10026         }
10027
10028         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10029         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10030             ReserveGame(nextGame, resChar); // sets nextGame
10031             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10032         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10033
10034         if (nextGame <= appData.matchGames) {
10035             gameMode = nextGameMode;
10036             matchGame = nextGame; // this will be overruled in tourney mode!
10037             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10038             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10039             endingGame = 0; /* [HGM] crash */
10040             return;
10041         } else {
10042             gameMode = nextGameMode;
10043             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10044                      first.tidy, second.tidy,
10045                      first.matchWins, second.matchWins,
10046                      appData.matchGames - (first.matchWins + second.matchWins));
10047             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10048             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10049                 first.twoMachinesColor = "black\n";
10050                 second.twoMachinesColor = "white\n";
10051             } else {
10052                 first.twoMachinesColor = "white\n";
10053                 second.twoMachinesColor = "black\n";
10054             }
10055         }
10056     }
10057     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10058         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10059       ExitAnalyzeMode();
10060     gameMode = nextGameMode;
10061     ModeHighlight();
10062     endingGame = 0;  /* [HGM] crash */
10063     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10064       if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10065         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10066         DisplayNote(ranking ? ranking : buf);
10067       }
10068       if(ranking) free(ranking);
10069     }
10070 }
10071
10072 /* Assumes program was just initialized (initString sent).
10073    Leaves program in force mode. */
10074 void
10075 FeedMovesToProgram(cps, upto)
10076      ChessProgramState *cps;
10077      int upto;
10078 {
10079     int i;
10080
10081     if (appData.debugMode)
10082       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10083               startedFromSetupPosition ? "position and " : "",
10084               backwardMostMove, upto, cps->which);
10085     if(currentlyInitializedVariant != gameInfo.variant) {
10086       char buf[MSG_SIZ];
10087         // [HGM] variantswitch: make engine aware of new variant
10088         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10089                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10090         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10091         SendToProgram(buf, cps);
10092         currentlyInitializedVariant = gameInfo.variant;
10093     }
10094     SendToProgram("force\n", cps);
10095     if (startedFromSetupPosition) {
10096         SendBoard(cps, backwardMostMove);
10097     if (appData.debugMode) {
10098         fprintf(debugFP, "feedMoves\n");
10099     }
10100     }
10101     for (i = backwardMostMove; i < upto; i++) {
10102         SendMoveToProgram(i, cps);
10103     }
10104 }
10105
10106
10107 int
10108 ResurrectChessProgram()
10109 {
10110      /* The chess program may have exited.
10111         If so, restart it and feed it all the moves made so far. */
10112     static int doInit = 0;
10113
10114     if (appData.noChessProgram) return 1;
10115
10116     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10117         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10118         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10119         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10120     } else {
10121         if (first.pr != NoProc) return 1;
10122         StartChessProgram(&first);
10123     }
10124     InitChessProgram(&first, FALSE);
10125     FeedMovesToProgram(&first, currentMove);
10126
10127     if (!first.sendTime) {
10128         /* can't tell gnuchess what its clock should read,
10129            so we bow to its notion. */
10130         ResetClocks();
10131         timeRemaining[0][currentMove] = whiteTimeRemaining;
10132         timeRemaining[1][currentMove] = blackTimeRemaining;
10133     }
10134
10135     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10136                 appData.icsEngineAnalyze) && first.analysisSupport) {
10137       SendToProgram("analyze\n", &first);
10138       first.analyzing = TRUE;
10139     }
10140     return 1;
10141 }
10142
10143 /*
10144  * Button procedures
10145  */
10146 void
10147 Reset(redraw, init)
10148      int redraw, init;
10149 {
10150     int i;
10151
10152     if (appData.debugMode) {
10153         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10154                 redraw, init, gameMode);
10155     }
10156     CleanupTail(); // [HGM] vari: delete any stored variations
10157     pausing = pauseExamInvalid = FALSE;
10158     startedFromSetupPosition = blackPlaysFirst = FALSE;
10159     firstMove = TRUE;
10160     whiteFlag = blackFlag = FALSE;
10161     userOfferedDraw = FALSE;
10162     hintRequested = bookRequested = FALSE;
10163     first.maybeThinking = FALSE;
10164     second.maybeThinking = FALSE;
10165     first.bookSuspend = FALSE; // [HGM] book
10166     second.bookSuspend = FALSE;
10167     thinkOutput[0] = NULLCHAR;
10168     lastHint[0] = NULLCHAR;
10169     ClearGameInfo(&gameInfo);
10170     gameInfo.variant = StringToVariant(appData.variant);
10171     ics_user_moved = ics_clock_paused = FALSE;
10172     ics_getting_history = H_FALSE;
10173     ics_gamenum = -1;
10174     white_holding[0] = black_holding[0] = NULLCHAR;
10175     ClearProgramStats();
10176     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10177
10178     ResetFrontEnd();
10179     ClearHighlights();
10180     flipView = appData.flipView;
10181     ClearPremoveHighlights();
10182     gotPremove = FALSE;
10183     alarmSounded = FALSE;
10184
10185     GameEnds(EndOfFile, NULL, GE_PLAYER);
10186     if(appData.serverMovesName != NULL) {
10187         /* [HGM] prepare to make moves file for broadcasting */
10188         clock_t t = clock();
10189         if(serverMoves != NULL) fclose(serverMoves);
10190         serverMoves = fopen(appData.serverMovesName, "r");
10191         if(serverMoves != NULL) {
10192             fclose(serverMoves);
10193             /* delay 15 sec before overwriting, so all clients can see end */
10194             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10195         }
10196         serverMoves = fopen(appData.serverMovesName, "w");
10197     }
10198
10199     ExitAnalyzeMode();
10200     gameMode = BeginningOfGame;
10201     ModeHighlight();
10202     if(appData.icsActive) gameInfo.variant = VariantNormal;
10203     currentMove = forwardMostMove = backwardMostMove = 0;
10204     InitPosition(redraw);
10205     for (i = 0; i < MAX_MOVES; i++) {
10206         if (commentList[i] != NULL) {
10207             free(commentList[i]);
10208             commentList[i] = NULL;
10209         }
10210     }
10211     ResetClocks();
10212     timeRemaining[0][0] = whiteTimeRemaining;
10213     timeRemaining[1][0] = blackTimeRemaining;
10214
10215     if (first.pr == NULL) {
10216         StartChessProgram(&first);
10217     }
10218     if (init) {
10219             InitChessProgram(&first, startedFromSetupPosition);
10220     }
10221     DisplayTitle("");
10222     DisplayMessage("", "");
10223     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10224     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10225 }
10226
10227 void
10228 AutoPlayGameLoop()
10229 {
10230     for (;;) {
10231         if (!AutoPlayOneMove())
10232           return;
10233         if (matchMode || appData.timeDelay == 0)
10234           continue;
10235         if (appData.timeDelay < 0)
10236           return;
10237         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10238         break;
10239     }
10240 }
10241
10242
10243 int
10244 AutoPlayOneMove()
10245 {
10246     int fromX, fromY, toX, toY;
10247
10248     if (appData.debugMode) {
10249       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10250     }
10251
10252     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10253       return FALSE;
10254
10255     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10256       pvInfoList[currentMove].depth = programStats.depth;
10257       pvInfoList[currentMove].score = programStats.score;
10258       pvInfoList[currentMove].time  = 0;
10259       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10260     }
10261
10262     if (currentMove >= forwardMostMove) {
10263       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10264       gameMode = EditGame;
10265       ModeHighlight();
10266
10267       /* [AS] Clear current move marker at the end of a game */
10268       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10269
10270       return FALSE;
10271     }
10272
10273     toX = moveList[currentMove][2] - AAA;
10274     toY = moveList[currentMove][3] - ONE;
10275
10276     if (moveList[currentMove][1] == '@') {
10277         if (appData.highlightLastMove) {
10278             SetHighlights(-1, -1, toX, toY);
10279         }
10280     } else {
10281         fromX = moveList[currentMove][0] - AAA;
10282         fromY = moveList[currentMove][1] - ONE;
10283
10284         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10285
10286         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10287
10288         if (appData.highlightLastMove) {
10289             SetHighlights(fromX, fromY, toX, toY);
10290         }
10291     }
10292     DisplayMove(currentMove);
10293     SendMoveToProgram(currentMove++, &first);
10294     DisplayBothClocks();
10295     DrawPosition(FALSE, boards[currentMove]);
10296     // [HGM] PV info: always display, routine tests if empty
10297     DisplayComment(currentMove - 1, commentList[currentMove]);
10298     return TRUE;
10299 }
10300
10301
10302 int
10303 LoadGameOneMove(readAhead)
10304      ChessMove readAhead;
10305 {
10306     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10307     char promoChar = NULLCHAR;
10308     ChessMove moveType;
10309     char move[MSG_SIZ];
10310     char *p, *q;
10311
10312     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10313         gameMode != AnalyzeMode && gameMode != Training) {
10314         gameFileFP = NULL;
10315         return FALSE;
10316     }
10317
10318     yyboardindex = forwardMostMove;
10319     if (readAhead != EndOfFile) {
10320       moveType = readAhead;
10321     } else {
10322       if (gameFileFP == NULL)
10323           return FALSE;
10324       moveType = (ChessMove) Myylex();
10325     }
10326
10327     done = FALSE;
10328     switch (moveType) {
10329       case Comment:
10330         if (appData.debugMode)
10331           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10332         p = yy_text;
10333
10334         /* append the comment but don't display it */
10335         AppendComment(currentMove, p, FALSE);
10336         return TRUE;
10337
10338       case WhiteCapturesEnPassant:
10339       case BlackCapturesEnPassant:
10340       case WhitePromotion:
10341       case BlackPromotion:
10342       case WhiteNonPromotion:
10343       case BlackNonPromotion:
10344       case NormalMove:
10345       case WhiteKingSideCastle:
10346       case WhiteQueenSideCastle:
10347       case BlackKingSideCastle:
10348       case BlackQueenSideCastle:
10349       case WhiteKingSideCastleWild:
10350       case WhiteQueenSideCastleWild:
10351       case BlackKingSideCastleWild:
10352       case BlackQueenSideCastleWild:
10353       /* PUSH Fabien */
10354       case WhiteHSideCastleFR:
10355       case WhiteASideCastleFR:
10356       case BlackHSideCastleFR:
10357       case BlackASideCastleFR:
10358       /* POP Fabien */
10359         if (appData.debugMode)
10360           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10361         fromX = currentMoveString[0] - AAA;
10362         fromY = currentMoveString[1] - ONE;
10363         toX = currentMoveString[2] - AAA;
10364         toY = currentMoveString[3] - ONE;
10365         promoChar = currentMoveString[4];
10366         break;
10367
10368       case WhiteDrop:
10369       case BlackDrop:
10370         if (appData.debugMode)
10371           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10372         fromX = moveType == WhiteDrop ?
10373           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10374         (int) CharToPiece(ToLower(currentMoveString[0]));
10375         fromY = DROP_RANK;
10376         toX = currentMoveString[2] - AAA;
10377         toY = currentMoveString[3] - ONE;
10378         break;
10379
10380       case WhiteWins:
10381       case BlackWins:
10382       case GameIsDrawn:
10383       case GameUnfinished:
10384         if (appData.debugMode)
10385           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10386         p = strchr(yy_text, '{');
10387         if (p == NULL) p = strchr(yy_text, '(');
10388         if (p == NULL) {
10389             p = yy_text;
10390             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10391         } else {
10392             q = strchr(p, *p == '{' ? '}' : ')');
10393             if (q != NULL) *q = NULLCHAR;
10394             p++;
10395         }
10396         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10397         GameEnds(moveType, p, GE_FILE);
10398         done = TRUE;
10399         if (cmailMsgLoaded) {
10400             ClearHighlights();
10401             flipView = WhiteOnMove(currentMove);
10402             if (moveType == GameUnfinished) flipView = !flipView;
10403             if (appData.debugMode)
10404               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10405         }
10406         break;
10407
10408       case EndOfFile:
10409         if (appData.debugMode)
10410           fprintf(debugFP, "Parser hit end of file\n");
10411         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10412           case MT_NONE:
10413           case MT_CHECK:
10414             break;
10415           case MT_CHECKMATE:
10416           case MT_STAINMATE:
10417             if (WhiteOnMove(currentMove)) {
10418                 GameEnds(BlackWins, "Black mates", GE_FILE);
10419             } else {
10420                 GameEnds(WhiteWins, "White mates", GE_FILE);
10421             }
10422             break;
10423           case MT_STALEMATE:
10424             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10425             break;
10426         }
10427         done = TRUE;
10428         break;
10429
10430       case MoveNumberOne:
10431         if (lastLoadGameStart == GNUChessGame) {
10432             /* GNUChessGames have numbers, but they aren't move numbers */
10433             if (appData.debugMode)
10434               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10435                       yy_text, (int) moveType);
10436             return LoadGameOneMove(EndOfFile); /* tail recursion */
10437         }
10438         /* else fall thru */
10439
10440       case XBoardGame:
10441       case GNUChessGame:
10442       case PGNTag:
10443         /* Reached start of next game in file */
10444         if (appData.debugMode)
10445           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10446         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10447           case MT_NONE:
10448           case MT_CHECK:
10449             break;
10450           case MT_CHECKMATE:
10451           case MT_STAINMATE:
10452             if (WhiteOnMove(currentMove)) {
10453                 GameEnds(BlackWins, "Black mates", GE_FILE);
10454             } else {
10455                 GameEnds(WhiteWins, "White mates", GE_FILE);
10456             }
10457             break;
10458           case MT_STALEMATE:
10459             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10460             break;
10461         }
10462         done = TRUE;
10463         break;
10464
10465       case PositionDiagram:     /* should not happen; ignore */
10466       case ElapsedTime:         /* ignore */
10467       case NAG:                 /* ignore */
10468         if (appData.debugMode)
10469           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10470                   yy_text, (int) moveType);
10471         return LoadGameOneMove(EndOfFile); /* tail recursion */
10472
10473       case IllegalMove:
10474         if (appData.testLegality) {
10475             if (appData.debugMode)
10476               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10477             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10478                     (forwardMostMove / 2) + 1,
10479                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10480             DisplayError(move, 0);
10481             done = TRUE;
10482         } else {
10483             if (appData.debugMode)
10484               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10485                       yy_text, currentMoveString);
10486             fromX = currentMoveString[0] - AAA;
10487             fromY = currentMoveString[1] - ONE;
10488             toX = currentMoveString[2] - AAA;
10489             toY = currentMoveString[3] - ONE;
10490             promoChar = currentMoveString[4];
10491         }
10492         break;
10493
10494       case AmbiguousMove:
10495         if (appData.debugMode)
10496           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10497         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10498                 (forwardMostMove / 2) + 1,
10499                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10500         DisplayError(move, 0);
10501         done = TRUE;
10502         break;
10503
10504       default:
10505       case ImpossibleMove:
10506         if (appData.debugMode)
10507           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10508         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10509                 (forwardMostMove / 2) + 1,
10510                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10511         DisplayError(move, 0);
10512         done = TRUE;
10513         break;
10514     }
10515
10516     if (done) {
10517         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10518             DrawPosition(FALSE, boards[currentMove]);
10519             DisplayBothClocks();
10520             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10521               DisplayComment(currentMove - 1, commentList[currentMove]);
10522         }
10523         (void) StopLoadGameTimer();
10524         gameFileFP = NULL;
10525         cmailOldMove = forwardMostMove;
10526         return FALSE;
10527     } else {
10528         /* currentMoveString is set as a side-effect of yylex */
10529
10530         thinkOutput[0] = NULLCHAR;
10531         MakeMove(fromX, fromY, toX, toY, promoChar);
10532         currentMove = forwardMostMove;
10533         return TRUE;
10534     }
10535 }
10536
10537 /* Load the nth game from the given file */
10538 int
10539 LoadGameFromFile(filename, n, title, useList)
10540      char *filename;
10541      int n;
10542      char *title;
10543      /*Boolean*/ int useList;
10544 {
10545     FILE *f;
10546     char buf[MSG_SIZ];
10547
10548     if (strcmp(filename, "-") == 0) {
10549         f = stdin;
10550         title = "stdin";
10551     } else {
10552         f = fopen(filename, "rb");
10553         if (f == NULL) {
10554           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10555             DisplayError(buf, errno);
10556             return FALSE;
10557         }
10558     }
10559     if (fseek(f, 0, 0) == -1) {
10560         /* f is not seekable; probably a pipe */
10561         useList = FALSE;
10562     }
10563     if (useList && n == 0) {
10564         int error = GameListBuild(f);
10565         if (error) {
10566             DisplayError(_("Cannot build game list"), error);
10567         } else if (!ListEmpty(&gameList) &&
10568                    ((ListGame *) gameList.tailPred)->number > 1) {
10569             GameListPopUp(f, title);
10570             return TRUE;
10571         }
10572         GameListDestroy();
10573         n = 1;
10574     }
10575     if (n == 0) n = 1;
10576     return LoadGame(f, n, title, FALSE);
10577 }
10578
10579
10580 void
10581 MakeRegisteredMove()
10582 {
10583     int fromX, fromY, toX, toY;
10584     char promoChar;
10585     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10586         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10587           case CMAIL_MOVE:
10588           case CMAIL_DRAW:
10589             if (appData.debugMode)
10590               fprintf(debugFP, "Restoring %s for game %d\n",
10591                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10592
10593             thinkOutput[0] = NULLCHAR;
10594             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10595             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10596             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10597             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10598             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10599             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10600             MakeMove(fromX, fromY, toX, toY, promoChar);
10601             ShowMove(fromX, fromY, toX, toY);
10602
10603             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10604               case MT_NONE:
10605               case MT_CHECK:
10606                 break;
10607
10608               case MT_CHECKMATE:
10609               case MT_STAINMATE:
10610                 if (WhiteOnMove(currentMove)) {
10611                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10612                 } else {
10613                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10614                 }
10615                 break;
10616
10617               case MT_STALEMATE:
10618                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10619                 break;
10620             }
10621
10622             break;
10623
10624           case CMAIL_RESIGN:
10625             if (WhiteOnMove(currentMove)) {
10626                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10627             } else {
10628                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10629             }
10630             break;
10631
10632           case CMAIL_ACCEPT:
10633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10634             break;
10635
10636           default:
10637             break;
10638         }
10639     }
10640
10641     return;
10642 }
10643
10644 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10645 int
10646 CmailLoadGame(f, gameNumber, title, useList)
10647      FILE *f;
10648      int gameNumber;
10649      char *title;
10650      int useList;
10651 {
10652     int retVal;
10653
10654     if (gameNumber > nCmailGames) {
10655         DisplayError(_("No more games in this message"), 0);
10656         return FALSE;
10657     }
10658     if (f == lastLoadGameFP) {
10659         int offset = gameNumber - lastLoadGameNumber;
10660         if (offset == 0) {
10661             cmailMsg[0] = NULLCHAR;
10662             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10663                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10664                 nCmailMovesRegistered--;
10665             }
10666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10667             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10668                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10669             }
10670         } else {
10671             if (! RegisterMove()) return FALSE;
10672         }
10673     }
10674
10675     retVal = LoadGame(f, gameNumber, title, useList);
10676
10677     /* Make move registered during previous look at this game, if any */
10678     MakeRegisteredMove();
10679
10680     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10681         commentList[currentMove]
10682           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10683         DisplayComment(currentMove - 1, commentList[currentMove]);
10684     }
10685
10686     return retVal;
10687 }
10688
10689 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10690 int
10691 ReloadGame(offset)
10692      int offset;
10693 {
10694     int gameNumber = lastLoadGameNumber + offset;
10695     if (lastLoadGameFP == NULL) {
10696         DisplayError(_("No game has been loaded yet"), 0);
10697         return FALSE;
10698     }
10699     if (gameNumber <= 0) {
10700         DisplayError(_("Can't back up any further"), 0);
10701         return FALSE;
10702     }
10703     if (cmailMsgLoaded) {
10704         return CmailLoadGame(lastLoadGameFP, gameNumber,
10705                              lastLoadGameTitle, lastLoadGameUseList);
10706     } else {
10707         return LoadGame(lastLoadGameFP, gameNumber,
10708                         lastLoadGameTitle, lastLoadGameUseList);
10709     }
10710 }
10711
10712
10713
10714 /* Load the nth game from open file f */
10715 int
10716 LoadGame(f, gameNumber, title, useList)
10717      FILE *f;
10718      int gameNumber;
10719      char *title;
10720      int useList;
10721 {
10722     ChessMove cm;
10723     char buf[MSG_SIZ];
10724     int gn = gameNumber;
10725     ListGame *lg = NULL;
10726     int numPGNTags = 0;
10727     int err;
10728     GameMode oldGameMode;
10729     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10730
10731     if (appData.debugMode)
10732         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10733
10734     if (gameMode == Training )
10735         SetTrainingModeOff();
10736
10737     oldGameMode = gameMode;
10738     if (gameMode != BeginningOfGame) {
10739       Reset(FALSE, TRUE);
10740     }
10741
10742     gameFileFP = f;
10743     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10744         fclose(lastLoadGameFP);
10745     }
10746
10747     if (useList) {
10748         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10749
10750         if (lg) {
10751             fseek(f, lg->offset, 0);
10752             GameListHighlight(gameNumber);
10753             gn = 1;
10754         }
10755         else {
10756             DisplayError(_("Game number out of range"), 0);
10757             return FALSE;
10758         }
10759     } else {
10760         GameListDestroy();
10761         if (fseek(f, 0, 0) == -1) {
10762             if (f == lastLoadGameFP ?
10763                 gameNumber == lastLoadGameNumber + 1 :
10764                 gameNumber == 1) {
10765                 gn = 1;
10766             } else {
10767                 DisplayError(_("Can't seek on game file"), 0);
10768                 return FALSE;
10769             }
10770         }
10771     }
10772     lastLoadGameFP = f;
10773     lastLoadGameNumber = gameNumber;
10774     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10775     lastLoadGameUseList = useList;
10776
10777     yynewfile(f);
10778
10779     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10780       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10781                 lg->gameInfo.black);
10782             DisplayTitle(buf);
10783     } else if (*title != NULLCHAR) {
10784         if (gameNumber > 1) {
10785           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10786             DisplayTitle(buf);
10787         } else {
10788             DisplayTitle(title);
10789         }
10790     }
10791
10792     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10793         gameMode = PlayFromGameFile;
10794         ModeHighlight();
10795     }
10796
10797     currentMove = forwardMostMove = backwardMostMove = 0;
10798     CopyBoard(boards[0], initialPosition);
10799     StopClocks();
10800
10801     /*
10802      * Skip the first gn-1 games in the file.
10803      * Also skip over anything that precedes an identifiable
10804      * start of game marker, to avoid being confused by
10805      * garbage at the start of the file.  Currently
10806      * recognized start of game markers are the move number "1",
10807      * the pattern "gnuchess .* game", the pattern
10808      * "^[#;%] [^ ]* game file", and a PGN tag block.
10809      * A game that starts with one of the latter two patterns
10810      * will also have a move number 1, possibly
10811      * following a position diagram.
10812      * 5-4-02: Let's try being more lenient and allowing a game to
10813      * start with an unnumbered move.  Does that break anything?
10814      */
10815     cm = lastLoadGameStart = EndOfFile;
10816     while (gn > 0) {
10817         yyboardindex = forwardMostMove;
10818         cm = (ChessMove) Myylex();
10819         switch (cm) {
10820           case EndOfFile:
10821             if (cmailMsgLoaded) {
10822                 nCmailGames = CMAIL_MAX_GAMES - gn;
10823             } else {
10824                 Reset(TRUE, TRUE);
10825                 DisplayError(_("Game not found in file"), 0);
10826             }
10827             return FALSE;
10828
10829           case GNUChessGame:
10830           case XBoardGame:
10831             gn--;
10832             lastLoadGameStart = cm;
10833             break;
10834
10835           case MoveNumberOne:
10836             switch (lastLoadGameStart) {
10837               case GNUChessGame:
10838               case XBoardGame:
10839               case PGNTag:
10840                 break;
10841               case MoveNumberOne:
10842               case EndOfFile:
10843                 gn--;           /* count this game */
10844                 lastLoadGameStart = cm;
10845                 break;
10846               default:
10847                 /* impossible */
10848                 break;
10849             }
10850             break;
10851
10852           case PGNTag:
10853             switch (lastLoadGameStart) {
10854               case GNUChessGame:
10855               case PGNTag:
10856               case MoveNumberOne:
10857               case EndOfFile:
10858                 gn--;           /* count this game */
10859                 lastLoadGameStart = cm;
10860                 break;
10861               case XBoardGame:
10862                 lastLoadGameStart = cm; /* game counted already */
10863                 break;
10864               default:
10865                 /* impossible */
10866                 break;
10867             }
10868             if (gn > 0) {
10869                 do {
10870                     yyboardindex = forwardMostMove;
10871                     cm = (ChessMove) Myylex();
10872                 } while (cm == PGNTag || cm == Comment);
10873             }
10874             break;
10875
10876           case WhiteWins:
10877           case BlackWins:
10878           case GameIsDrawn:
10879             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10880                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10881                     != CMAIL_OLD_RESULT) {
10882                     nCmailResults ++ ;
10883                     cmailResult[  CMAIL_MAX_GAMES
10884                                 - gn - 1] = CMAIL_OLD_RESULT;
10885                 }
10886             }
10887             break;
10888
10889           case NormalMove:
10890             /* Only a NormalMove can be at the start of a game
10891              * without a position diagram. */
10892             if (lastLoadGameStart == EndOfFile ) {
10893               gn--;
10894               lastLoadGameStart = MoveNumberOne;
10895             }
10896             break;
10897
10898           default:
10899             break;
10900         }
10901     }
10902
10903     if (appData.debugMode)
10904       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10905
10906     if (cm == XBoardGame) {
10907         /* Skip any header junk before position diagram and/or move 1 */
10908         for (;;) {
10909             yyboardindex = forwardMostMove;
10910             cm = (ChessMove) Myylex();
10911
10912             if (cm == EndOfFile ||
10913                 cm == GNUChessGame || cm == XBoardGame) {
10914                 /* Empty game; pretend end-of-file and handle later */
10915                 cm = EndOfFile;
10916                 break;
10917             }
10918
10919             if (cm == MoveNumberOne || cm == PositionDiagram ||
10920                 cm == PGNTag || cm == Comment)
10921               break;
10922         }
10923     } else if (cm == GNUChessGame) {
10924         if (gameInfo.event != NULL) {
10925             free(gameInfo.event);
10926         }
10927         gameInfo.event = StrSave(yy_text);
10928     }
10929
10930     startedFromSetupPosition = FALSE;
10931     while (cm == PGNTag) {
10932         if (appData.debugMode)
10933           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10934         err = ParsePGNTag(yy_text, &gameInfo);
10935         if (!err) numPGNTags++;
10936
10937         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10938         if(gameInfo.variant != oldVariant) {
10939             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10940             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10941             InitPosition(TRUE);
10942             oldVariant = gameInfo.variant;
10943             if (appData.debugMode)
10944               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10945         }
10946
10947
10948         if (gameInfo.fen != NULL) {
10949           Board initial_position;
10950           startedFromSetupPosition = TRUE;
10951           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10952             Reset(TRUE, TRUE);
10953             DisplayError(_("Bad FEN position in file"), 0);
10954             return FALSE;
10955           }
10956           CopyBoard(boards[0], initial_position);
10957           if (blackPlaysFirst) {
10958             currentMove = forwardMostMove = backwardMostMove = 1;
10959             CopyBoard(boards[1], initial_position);
10960             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10961             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10962             timeRemaining[0][1] = whiteTimeRemaining;
10963             timeRemaining[1][1] = blackTimeRemaining;
10964             if (commentList[0] != NULL) {
10965               commentList[1] = commentList[0];
10966               commentList[0] = NULL;
10967             }
10968           } else {
10969             currentMove = forwardMostMove = backwardMostMove = 0;
10970           }
10971           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10972           {   int i;
10973               initialRulePlies = FENrulePlies;
10974               for( i=0; i< nrCastlingRights; i++ )
10975                   initialRights[i] = initial_position[CASTLING][i];
10976           }
10977           yyboardindex = forwardMostMove;
10978           free(gameInfo.fen);
10979           gameInfo.fen = NULL;
10980         }
10981
10982         yyboardindex = forwardMostMove;
10983         cm = (ChessMove) Myylex();
10984
10985         /* Handle comments interspersed among the tags */
10986         while (cm == Comment) {
10987             char *p;
10988             if (appData.debugMode)
10989               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10990             p = yy_text;
10991             AppendComment(currentMove, p, FALSE);
10992             yyboardindex = forwardMostMove;
10993             cm = (ChessMove) Myylex();
10994         }
10995     }
10996
10997     /* don't rely on existence of Event tag since if game was
10998      * pasted from clipboard the Event tag may not exist
10999      */
11000     if (numPGNTags > 0){
11001         char *tags;
11002         if (gameInfo.variant == VariantNormal) {
11003           VariantClass v = StringToVariant(gameInfo.event);
11004           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11005           if(v < VariantShogi) gameInfo.variant = v;
11006         }
11007         if (!matchMode) {
11008           if( appData.autoDisplayTags ) {
11009             tags = PGNTags(&gameInfo);
11010             TagsPopUp(tags, CmailMsg());
11011             free(tags);
11012           }
11013         }
11014     } else {
11015         /* Make something up, but don't display it now */
11016         SetGameInfo();
11017         TagsPopDown();
11018     }
11019
11020     if (cm == PositionDiagram) {
11021         int i, j;
11022         char *p;
11023         Board initial_position;
11024
11025         if (appData.debugMode)
11026           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11027
11028         if (!startedFromSetupPosition) {
11029             p = yy_text;
11030             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11031               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11032                 switch (*p) {
11033                   case '{':
11034                   case '[':
11035                   case '-':
11036                   case ' ':
11037                   case '\t':
11038                   case '\n':
11039                   case '\r':
11040                     break;
11041                   default:
11042                     initial_position[i][j++] = CharToPiece(*p);
11043                     break;
11044                 }
11045             while (*p == ' ' || *p == '\t' ||
11046                    *p == '\n' || *p == '\r') p++;
11047
11048             if (strncmp(p, "black", strlen("black"))==0)
11049               blackPlaysFirst = TRUE;
11050             else
11051               blackPlaysFirst = FALSE;
11052             startedFromSetupPosition = TRUE;
11053
11054             CopyBoard(boards[0], initial_position);
11055             if (blackPlaysFirst) {
11056                 currentMove = forwardMostMove = backwardMostMove = 1;
11057                 CopyBoard(boards[1], initial_position);
11058                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11059                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11060                 timeRemaining[0][1] = whiteTimeRemaining;
11061                 timeRemaining[1][1] = blackTimeRemaining;
11062                 if (commentList[0] != NULL) {
11063                     commentList[1] = commentList[0];
11064                     commentList[0] = NULL;
11065                 }
11066             } else {
11067                 currentMove = forwardMostMove = backwardMostMove = 0;
11068             }
11069         }
11070         yyboardindex = forwardMostMove;
11071         cm = (ChessMove) Myylex();
11072     }
11073
11074     if (first.pr == NoProc) {
11075         StartChessProgram(&first);
11076     }
11077     InitChessProgram(&first, FALSE);
11078     SendToProgram("force\n", &first);
11079     if (startedFromSetupPosition) {
11080         SendBoard(&first, forwardMostMove);
11081     if (appData.debugMode) {
11082         fprintf(debugFP, "Load Game\n");
11083     }
11084         DisplayBothClocks();
11085     }
11086
11087     /* [HGM] server: flag to write setup moves in broadcast file as one */
11088     loadFlag = appData.suppressLoadMoves;
11089
11090     while (cm == Comment) {
11091         char *p;
11092         if (appData.debugMode)
11093           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11094         p = yy_text;
11095         AppendComment(currentMove, p, FALSE);
11096         yyboardindex = forwardMostMove;
11097         cm = (ChessMove) Myylex();
11098     }
11099
11100     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11101         cm == WhiteWins || cm == BlackWins ||
11102         cm == GameIsDrawn || cm == GameUnfinished) {
11103         DisplayMessage("", _("No moves in game"));
11104         if (cmailMsgLoaded) {
11105             if (appData.debugMode)
11106               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11107             ClearHighlights();
11108             flipView = FALSE;
11109         }
11110         DrawPosition(FALSE, boards[currentMove]);
11111         DisplayBothClocks();
11112         gameMode = EditGame;
11113         ModeHighlight();
11114         gameFileFP = NULL;
11115         cmailOldMove = 0;
11116         return TRUE;
11117     }
11118
11119     // [HGM] PV info: routine tests if comment empty
11120     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11121         DisplayComment(currentMove - 1, commentList[currentMove]);
11122     }
11123     if (!matchMode && appData.timeDelay != 0)
11124       DrawPosition(FALSE, boards[currentMove]);
11125
11126     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11127       programStats.ok_to_send = 1;
11128     }
11129
11130     /* if the first token after the PGN tags is a move
11131      * and not move number 1, retrieve it from the parser
11132      */
11133     if (cm != MoveNumberOne)
11134         LoadGameOneMove(cm);
11135
11136     /* load the remaining moves from the file */
11137     while (LoadGameOneMove(EndOfFile)) {
11138       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11139       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11140     }
11141
11142     /* rewind to the start of the game */
11143     currentMove = backwardMostMove;
11144
11145     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11146
11147     if (oldGameMode == AnalyzeFile ||
11148         oldGameMode == AnalyzeMode) {
11149       AnalyzeFileEvent();
11150     }
11151
11152     if (matchMode || appData.timeDelay == 0) {
11153       ToEndEvent();
11154       gameMode = EditGame;
11155       ModeHighlight();
11156     } else if (appData.timeDelay > 0) {
11157       AutoPlayGameLoop();
11158     }
11159
11160     if (appData.debugMode)
11161         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11162
11163     loadFlag = 0; /* [HGM] true game starts */
11164     return TRUE;
11165 }
11166
11167 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11168 int
11169 ReloadPosition(offset)
11170      int offset;
11171 {
11172     int positionNumber = lastLoadPositionNumber + offset;
11173     if (lastLoadPositionFP == NULL) {
11174         DisplayError(_("No position has been loaded yet"), 0);
11175         return FALSE;
11176     }
11177     if (positionNumber <= 0) {
11178         DisplayError(_("Can't back up any further"), 0);
11179         return FALSE;
11180     }
11181     return LoadPosition(lastLoadPositionFP, positionNumber,
11182                         lastLoadPositionTitle);
11183 }
11184
11185 /* Load the nth position from the given file */
11186 int
11187 LoadPositionFromFile(filename, n, title)
11188      char *filename;
11189      int n;
11190      char *title;
11191 {
11192     FILE *f;
11193     char buf[MSG_SIZ];
11194
11195     if (strcmp(filename, "-") == 0) {
11196         return LoadPosition(stdin, n, "stdin");
11197     } else {
11198         f = fopen(filename, "rb");
11199         if (f == NULL) {
11200             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11201             DisplayError(buf, errno);
11202             return FALSE;
11203         } else {
11204             return LoadPosition(f, n, title);
11205         }
11206     }
11207 }
11208
11209 /* Load the nth position from the given open file, and close it */
11210 int
11211 LoadPosition(f, positionNumber, title)
11212      FILE *f;
11213      int positionNumber;
11214      char *title;
11215 {
11216     char *p, line[MSG_SIZ];
11217     Board initial_position;
11218     int i, j, fenMode, pn;
11219
11220     if (gameMode == Training )
11221         SetTrainingModeOff();
11222
11223     if (gameMode != BeginningOfGame) {
11224         Reset(FALSE, TRUE);
11225     }
11226     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11227         fclose(lastLoadPositionFP);
11228     }
11229     if (positionNumber == 0) positionNumber = 1;
11230     lastLoadPositionFP = f;
11231     lastLoadPositionNumber = positionNumber;
11232     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11233     if (first.pr == NoProc) {
11234       StartChessProgram(&first);
11235       InitChessProgram(&first, FALSE);
11236     }
11237     pn = positionNumber;
11238     if (positionNumber < 0) {
11239         /* Negative position number means to seek to that byte offset */
11240         if (fseek(f, -positionNumber, 0) == -1) {
11241             DisplayError(_("Can't seek on position file"), 0);
11242             return FALSE;
11243         };
11244         pn = 1;
11245     } else {
11246         if (fseek(f, 0, 0) == -1) {
11247             if (f == lastLoadPositionFP ?
11248                 positionNumber == lastLoadPositionNumber + 1 :
11249                 positionNumber == 1) {
11250                 pn = 1;
11251             } else {
11252                 DisplayError(_("Can't seek on position file"), 0);
11253                 return FALSE;
11254             }
11255         }
11256     }
11257     /* See if this file is FEN or old-style xboard */
11258     if (fgets(line, MSG_SIZ, f) == NULL) {
11259         DisplayError(_("Position not found in file"), 0);
11260         return FALSE;
11261     }
11262     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11263     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11264
11265     if (pn >= 2) {
11266         if (fenMode || line[0] == '#') pn--;
11267         while (pn > 0) {
11268             /* skip positions before number pn */
11269             if (fgets(line, MSG_SIZ, f) == NULL) {
11270                 Reset(TRUE, TRUE);
11271                 DisplayError(_("Position not found in file"), 0);
11272                 return FALSE;
11273             }
11274             if (fenMode || line[0] == '#') pn--;
11275         }
11276     }
11277
11278     if (fenMode) {
11279         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11280             DisplayError(_("Bad FEN position in file"), 0);
11281             return FALSE;
11282         }
11283     } else {
11284         (void) fgets(line, MSG_SIZ, f);
11285         (void) fgets(line, MSG_SIZ, f);
11286
11287         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11288             (void) fgets(line, MSG_SIZ, f);
11289             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11290                 if (*p == ' ')
11291                   continue;
11292                 initial_position[i][j++] = CharToPiece(*p);
11293             }
11294         }
11295
11296         blackPlaysFirst = FALSE;
11297         if (!feof(f)) {
11298             (void) fgets(line, MSG_SIZ, f);
11299             if (strncmp(line, "black", strlen("black"))==0)
11300               blackPlaysFirst = TRUE;
11301         }
11302     }
11303     startedFromSetupPosition = TRUE;
11304
11305     SendToProgram("force\n", &first);
11306     CopyBoard(boards[0], initial_position);
11307     if (blackPlaysFirst) {
11308         currentMove = forwardMostMove = backwardMostMove = 1;
11309         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11310         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11311         CopyBoard(boards[1], initial_position);
11312         DisplayMessage("", _("Black to play"));
11313     } else {
11314         currentMove = forwardMostMove = backwardMostMove = 0;
11315         DisplayMessage("", _("White to play"));
11316     }
11317     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11318     SendBoard(&first, forwardMostMove);
11319     if (appData.debugMode) {
11320 int i, j;
11321   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11322   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11323         fprintf(debugFP, "Load Position\n");
11324     }
11325
11326     if (positionNumber > 1) {
11327       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11328         DisplayTitle(line);
11329     } else {
11330         DisplayTitle(title);
11331     }
11332     gameMode = EditGame;
11333     ModeHighlight();
11334     ResetClocks();
11335     timeRemaining[0][1] = whiteTimeRemaining;
11336     timeRemaining[1][1] = blackTimeRemaining;
11337     DrawPosition(FALSE, boards[currentMove]);
11338
11339     return TRUE;
11340 }
11341
11342
11343 void
11344 CopyPlayerNameIntoFileName(dest, src)
11345      char **dest, *src;
11346 {
11347     while (*src != NULLCHAR && *src != ',') {
11348         if (*src == ' ') {
11349             *(*dest)++ = '_';
11350             src++;
11351         } else {
11352             *(*dest)++ = *src++;
11353         }
11354     }
11355 }
11356
11357 char *DefaultFileName(ext)
11358      char *ext;
11359 {
11360     static char def[MSG_SIZ];
11361     char *p;
11362
11363     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11364         p = def;
11365         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11366         *p++ = '-';
11367         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11368         *p++ = '.';
11369         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11370     } else {
11371         def[0] = NULLCHAR;
11372     }
11373     return def;
11374 }
11375
11376 /* Save the current game to the given file */
11377 int
11378 SaveGameToFile(filename, append)
11379      char *filename;
11380      int append;
11381 {
11382     FILE *f;
11383     char buf[MSG_SIZ];
11384     int result;
11385
11386     if (strcmp(filename, "-") == 0) {
11387         return SaveGame(stdout, 0, NULL);
11388     } else {
11389         f = fopen(filename, append ? "a" : "w");
11390         if (f == NULL) {
11391             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11392             DisplayError(buf, errno);
11393             return FALSE;
11394         } else {
11395             safeStrCpy(buf, lastMsg, MSG_SIZ);
11396             DisplayMessage(_("Waiting for access to save file"), "");
11397             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11398             DisplayMessage(_("Saving game"), "");
11399             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11400             result = SaveGame(f, 0, NULL);
11401             DisplayMessage(buf, "");
11402             return result;
11403         }
11404     }
11405 }
11406
11407 char *
11408 SavePart(str)
11409      char *str;
11410 {
11411     static char buf[MSG_SIZ];
11412     char *p;
11413
11414     p = strchr(str, ' ');
11415     if (p == NULL) return str;
11416     strncpy(buf, str, p - str);
11417     buf[p - str] = NULLCHAR;
11418     return buf;
11419 }
11420
11421 #define PGN_MAX_LINE 75
11422
11423 #define PGN_SIDE_WHITE  0
11424 #define PGN_SIDE_BLACK  1
11425
11426 /* [AS] */
11427 static int FindFirstMoveOutOfBook( int side )
11428 {
11429     int result = -1;
11430
11431     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11432         int index = backwardMostMove;
11433         int has_book_hit = 0;
11434
11435         if( (index % 2) != side ) {
11436             index++;
11437         }
11438
11439         while( index < forwardMostMove ) {
11440             /* Check to see if engine is in book */
11441             int depth = pvInfoList[index].depth;
11442             int score = pvInfoList[index].score;
11443             int in_book = 0;
11444
11445             if( depth <= 2 ) {
11446                 in_book = 1;
11447             }
11448             else if( score == 0 && depth == 63 ) {
11449                 in_book = 1; /* Zappa */
11450             }
11451             else if( score == 2 && depth == 99 ) {
11452                 in_book = 1; /* Abrok */
11453             }
11454
11455             has_book_hit += in_book;
11456
11457             if( ! in_book ) {
11458                 result = index;
11459
11460                 break;
11461             }
11462
11463             index += 2;
11464         }
11465     }
11466
11467     return result;
11468 }
11469
11470 /* [AS] */
11471 void GetOutOfBookInfo( char * buf )
11472 {
11473     int oob[2];
11474     int i;
11475     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11476
11477     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11478     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11479
11480     *buf = '\0';
11481
11482     if( oob[0] >= 0 || oob[1] >= 0 ) {
11483         for( i=0; i<2; i++ ) {
11484             int idx = oob[i];
11485
11486             if( idx >= 0 ) {
11487                 if( i > 0 && oob[0] >= 0 ) {
11488                     strcat( buf, "   " );
11489                 }
11490
11491                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11492                 sprintf( buf+strlen(buf), "%s%.2f",
11493                     pvInfoList[idx].score >= 0 ? "+" : "",
11494                     pvInfoList[idx].score / 100.0 );
11495             }
11496         }
11497     }
11498 }
11499
11500 /* Save game in PGN style and close the file */
11501 int
11502 SaveGamePGN(f)
11503      FILE *f;
11504 {
11505     int i, offset, linelen, newblock;
11506     time_t tm;
11507 //    char *movetext;
11508     char numtext[32];
11509     int movelen, numlen, blank;
11510     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11511
11512     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11513
11514     tm = time((time_t *) NULL);
11515
11516     PrintPGNTags(f, &gameInfo);
11517
11518     if (backwardMostMove > 0 || startedFromSetupPosition) {
11519         char *fen = PositionToFEN(backwardMostMove, NULL);
11520         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11521         fprintf(f, "\n{--------------\n");
11522         PrintPosition(f, backwardMostMove);
11523         fprintf(f, "--------------}\n");
11524         free(fen);
11525     }
11526     else {
11527         /* [AS] Out of book annotation */
11528         if( appData.saveOutOfBookInfo ) {
11529             char buf[64];
11530
11531             GetOutOfBookInfo( buf );
11532
11533             if( buf[0] != '\0' ) {
11534                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11535             }
11536         }
11537
11538         fprintf(f, "\n");
11539     }
11540
11541     i = backwardMostMove;
11542     linelen = 0;
11543     newblock = TRUE;
11544
11545     while (i < forwardMostMove) {
11546         /* Print comments preceding this move */
11547         if (commentList[i] != NULL) {
11548             if (linelen > 0) fprintf(f, "\n");
11549             fprintf(f, "%s", commentList[i]);
11550             linelen = 0;
11551             newblock = TRUE;
11552         }
11553
11554         /* Format move number */
11555         if ((i % 2) == 0)
11556           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11557         else
11558           if (newblock)
11559             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11560           else
11561             numtext[0] = NULLCHAR;
11562
11563         numlen = strlen(numtext);
11564         newblock = FALSE;
11565
11566         /* Print move number */
11567         blank = linelen > 0 && numlen > 0;
11568         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11569             fprintf(f, "\n");
11570             linelen = 0;
11571             blank = 0;
11572         }
11573         if (blank) {
11574             fprintf(f, " ");
11575             linelen++;
11576         }
11577         fprintf(f, "%s", numtext);
11578         linelen += numlen;
11579
11580         /* Get move */
11581         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11582         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11583
11584         /* Print move */
11585         blank = linelen > 0 && movelen > 0;
11586         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11587             fprintf(f, "\n");
11588             linelen = 0;
11589             blank = 0;
11590         }
11591         if (blank) {
11592             fprintf(f, " ");
11593             linelen++;
11594         }
11595         fprintf(f, "%s", move_buffer);
11596         linelen += movelen;
11597
11598         /* [AS] Add PV info if present */
11599         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11600             /* [HGM] add time */
11601             char buf[MSG_SIZ]; int seconds;
11602
11603             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11604
11605             if( seconds <= 0)
11606               buf[0] = 0;
11607             else
11608               if( seconds < 30 )
11609                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11610               else
11611                 {
11612                   seconds = (seconds + 4)/10; // round to full seconds
11613                   if( seconds < 60 )
11614                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11615                   else
11616                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11617                 }
11618
11619             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11620                       pvInfoList[i].score >= 0 ? "+" : "",
11621                       pvInfoList[i].score / 100.0,
11622                       pvInfoList[i].depth,
11623                       buf );
11624
11625             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11626
11627             /* Print score/depth */
11628             blank = linelen > 0 && movelen > 0;
11629             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11630                 fprintf(f, "\n");
11631                 linelen = 0;
11632                 blank = 0;
11633             }
11634             if (blank) {
11635                 fprintf(f, " ");
11636                 linelen++;
11637             }
11638             fprintf(f, "%s", move_buffer);
11639             linelen += movelen;
11640         }
11641
11642         i++;
11643     }
11644
11645     /* Start a new line */
11646     if (linelen > 0) fprintf(f, "\n");
11647
11648     /* Print comments after last move */
11649     if (commentList[i] != NULL) {
11650         fprintf(f, "%s\n", commentList[i]);
11651     }
11652
11653     /* Print result */
11654     if (gameInfo.resultDetails != NULL &&
11655         gameInfo.resultDetails[0] != NULLCHAR) {
11656         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11657                 PGNResult(gameInfo.result));
11658     } else {
11659         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11660     }
11661
11662     fclose(f);
11663     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11664     return TRUE;
11665 }
11666
11667 /* Save game in old style and close the file */
11668 int
11669 SaveGameOldStyle(f)
11670      FILE *f;
11671 {
11672     int i, offset;
11673     time_t tm;
11674
11675     tm = time((time_t *) NULL);
11676
11677     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11678     PrintOpponents(f);
11679
11680     if (backwardMostMove > 0 || startedFromSetupPosition) {
11681         fprintf(f, "\n[--------------\n");
11682         PrintPosition(f, backwardMostMove);
11683         fprintf(f, "--------------]\n");
11684     } else {
11685         fprintf(f, "\n");
11686     }
11687
11688     i = backwardMostMove;
11689     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11690
11691     while (i < forwardMostMove) {
11692         if (commentList[i] != NULL) {
11693             fprintf(f, "[%s]\n", commentList[i]);
11694         }
11695
11696         if ((i % 2) == 1) {
11697             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11698             i++;
11699         } else {
11700             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11701             i++;
11702             if (commentList[i] != NULL) {
11703                 fprintf(f, "\n");
11704                 continue;
11705             }
11706             if (i >= forwardMostMove) {
11707                 fprintf(f, "\n");
11708                 break;
11709             }
11710             fprintf(f, "%s\n", parseList[i]);
11711             i++;
11712         }
11713     }
11714
11715     if (commentList[i] != NULL) {
11716         fprintf(f, "[%s]\n", commentList[i]);
11717     }
11718
11719     /* This isn't really the old style, but it's close enough */
11720     if (gameInfo.resultDetails != NULL &&
11721         gameInfo.resultDetails[0] != NULLCHAR) {
11722         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11723                 gameInfo.resultDetails);
11724     } else {
11725         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11726     }
11727
11728     fclose(f);
11729     return TRUE;
11730 }
11731
11732 /* Save the current game to open file f and close the file */
11733 int
11734 SaveGame(f, dummy, dummy2)
11735      FILE *f;
11736      int dummy;
11737      char *dummy2;
11738 {
11739     if (gameMode == EditPosition) EditPositionDone(TRUE);
11740     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11741     if (appData.oldSaveStyle)
11742       return SaveGameOldStyle(f);
11743     else
11744       return SaveGamePGN(f);
11745 }
11746
11747 /* Save the current position to the given file */
11748 int
11749 SavePositionToFile(filename)
11750      char *filename;
11751 {
11752     FILE *f;
11753     char buf[MSG_SIZ];
11754
11755     if (strcmp(filename, "-") == 0) {
11756         return SavePosition(stdout, 0, NULL);
11757     } else {
11758         f = fopen(filename, "a");
11759         if (f == NULL) {
11760             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11761             DisplayError(buf, errno);
11762             return FALSE;
11763         } else {
11764             safeStrCpy(buf, lastMsg, MSG_SIZ);
11765             DisplayMessage(_("Waiting for access to save file"), "");
11766             flock(fileno(f), LOCK_EX); // [HGM] lock
11767             DisplayMessage(_("Saving position"), "");
11768             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11769             SavePosition(f, 0, NULL);
11770             DisplayMessage(buf, "");
11771             return TRUE;
11772         }
11773     }
11774 }
11775
11776 /* Save the current position to the given open file and close the file */
11777 int
11778 SavePosition(f, dummy, dummy2)
11779      FILE *f;
11780      int dummy;
11781      char *dummy2;
11782 {
11783     time_t tm;
11784     char *fen;
11785
11786     if (gameMode == EditPosition) EditPositionDone(TRUE);
11787     if (appData.oldSaveStyle) {
11788         tm = time((time_t *) NULL);
11789
11790         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11791         PrintOpponents(f);
11792         fprintf(f, "[--------------\n");
11793         PrintPosition(f, currentMove);
11794         fprintf(f, "--------------]\n");
11795     } else {
11796         fen = PositionToFEN(currentMove, NULL);
11797         fprintf(f, "%s\n", fen);
11798         free(fen);
11799     }
11800     fclose(f);
11801     return TRUE;
11802 }
11803
11804 void
11805 ReloadCmailMsgEvent(unregister)
11806      int unregister;
11807 {
11808 #if !WIN32
11809     static char *inFilename = NULL;
11810     static char *outFilename;
11811     int i;
11812     struct stat inbuf, outbuf;
11813     int status;
11814
11815     /* Any registered moves are unregistered if unregister is set, */
11816     /* i.e. invoked by the signal handler */
11817     if (unregister) {
11818         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11819             cmailMoveRegistered[i] = FALSE;
11820             if (cmailCommentList[i] != NULL) {
11821                 free(cmailCommentList[i]);
11822                 cmailCommentList[i] = NULL;
11823             }
11824         }
11825         nCmailMovesRegistered = 0;
11826     }
11827
11828     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11829         cmailResult[i] = CMAIL_NOT_RESULT;
11830     }
11831     nCmailResults = 0;
11832
11833     if (inFilename == NULL) {
11834         /* Because the filenames are static they only get malloced once  */
11835         /* and they never get freed                                      */
11836         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11837         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11838
11839         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11840         sprintf(outFilename, "%s.out", appData.cmailGameName);
11841     }
11842
11843     status = stat(outFilename, &outbuf);
11844     if (status < 0) {
11845         cmailMailedMove = FALSE;
11846     } else {
11847         status = stat(inFilename, &inbuf);
11848         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11849     }
11850
11851     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11852        counts the games, notes how each one terminated, etc.
11853
11854        It would be nice to remove this kludge and instead gather all
11855        the information while building the game list.  (And to keep it
11856        in the game list nodes instead of having a bunch of fixed-size
11857        parallel arrays.)  Note this will require getting each game's
11858        termination from the PGN tags, as the game list builder does
11859        not process the game moves.  --mann
11860        */
11861     cmailMsgLoaded = TRUE;
11862     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11863
11864     /* Load first game in the file or popup game menu */
11865     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11866
11867 #endif /* !WIN32 */
11868     return;
11869 }
11870
11871 int
11872 RegisterMove()
11873 {
11874     FILE *f;
11875     char string[MSG_SIZ];
11876
11877     if (   cmailMailedMove
11878         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11879         return TRUE;            /* Allow free viewing  */
11880     }
11881
11882     /* Unregister move to ensure that we don't leave RegisterMove        */
11883     /* with the move registered when the conditions for registering no   */
11884     /* longer hold                                                       */
11885     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11886         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11887         nCmailMovesRegistered --;
11888
11889         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11890           {
11891               free(cmailCommentList[lastLoadGameNumber - 1]);
11892               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11893           }
11894     }
11895
11896     if (cmailOldMove == -1) {
11897         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11898         return FALSE;
11899     }
11900
11901     if (currentMove > cmailOldMove + 1) {
11902         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11903         return FALSE;
11904     }
11905
11906     if (currentMove < cmailOldMove) {
11907         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11908         return FALSE;
11909     }
11910
11911     if (forwardMostMove > currentMove) {
11912         /* Silently truncate extra moves */
11913         TruncateGame();
11914     }
11915
11916     if (   (currentMove == cmailOldMove + 1)
11917         || (   (currentMove == cmailOldMove)
11918             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11919                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11920         if (gameInfo.result != GameUnfinished) {
11921             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11922         }
11923
11924         if (commentList[currentMove] != NULL) {
11925             cmailCommentList[lastLoadGameNumber - 1]
11926               = StrSave(commentList[currentMove]);
11927         }
11928         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11929
11930         if (appData.debugMode)
11931           fprintf(debugFP, "Saving %s for game %d\n",
11932                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11933
11934         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11935
11936         f = fopen(string, "w");
11937         if (appData.oldSaveStyle) {
11938             SaveGameOldStyle(f); /* also closes the file */
11939
11940             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11941             f = fopen(string, "w");
11942             SavePosition(f, 0, NULL); /* also closes the file */
11943         } else {
11944             fprintf(f, "{--------------\n");
11945             PrintPosition(f, currentMove);
11946             fprintf(f, "--------------}\n\n");
11947
11948             SaveGame(f, 0, NULL); /* also closes the file*/
11949         }
11950
11951         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11952         nCmailMovesRegistered ++;
11953     } else if (nCmailGames == 1) {
11954         DisplayError(_("You have not made a move yet"), 0);
11955         return FALSE;
11956     }
11957
11958     return TRUE;
11959 }
11960
11961 void
11962 MailMoveEvent()
11963 {
11964 #if !WIN32
11965     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11966     FILE *commandOutput;
11967     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11968     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11969     int nBuffers;
11970     int i;
11971     int archived;
11972     char *arcDir;
11973
11974     if (! cmailMsgLoaded) {
11975         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11976         return;
11977     }
11978
11979     if (nCmailGames == nCmailResults) {
11980         DisplayError(_("No unfinished games"), 0);
11981         return;
11982     }
11983
11984 #if CMAIL_PROHIBIT_REMAIL
11985     if (cmailMailedMove) {
11986       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);
11987         DisplayError(msg, 0);
11988         return;
11989     }
11990 #endif
11991
11992     if (! (cmailMailedMove || RegisterMove())) return;
11993
11994     if (   cmailMailedMove
11995         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11996       snprintf(string, MSG_SIZ, partCommandString,
11997                appData.debugMode ? " -v" : "", appData.cmailGameName);
11998         commandOutput = popen(string, "r");
11999
12000         if (commandOutput == NULL) {
12001             DisplayError(_("Failed to invoke cmail"), 0);
12002         } else {
12003             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12004                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12005             }
12006             if (nBuffers > 1) {
12007                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12008                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12009                 nBytes = MSG_SIZ - 1;
12010             } else {
12011                 (void) memcpy(msg, buffer, nBytes);
12012             }
12013             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12014
12015             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12016                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12017
12018                 archived = TRUE;
12019                 for (i = 0; i < nCmailGames; i ++) {
12020                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12021                         archived = FALSE;
12022                     }
12023                 }
12024                 if (   archived
12025                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12026                         != NULL)) {
12027                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12028                            arcDir,
12029                            appData.cmailGameName,
12030                            gameInfo.date);
12031                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12032                     cmailMsgLoaded = FALSE;
12033                 }
12034             }
12035
12036             DisplayInformation(msg);
12037             pclose(commandOutput);
12038         }
12039     } else {
12040         if ((*cmailMsg) != '\0') {
12041             DisplayInformation(cmailMsg);
12042         }
12043     }
12044
12045     return;
12046 #endif /* !WIN32 */
12047 }
12048
12049 char *
12050 CmailMsg()
12051 {
12052 #if WIN32
12053     return NULL;
12054 #else
12055     int  prependComma = 0;
12056     char number[5];
12057     char string[MSG_SIZ];       /* Space for game-list */
12058     int  i;
12059
12060     if (!cmailMsgLoaded) return "";
12061
12062     if (cmailMailedMove) {
12063       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12064     } else {
12065         /* Create a list of games left */
12066       snprintf(string, MSG_SIZ, "[");
12067         for (i = 0; i < nCmailGames; i ++) {
12068             if (! (   cmailMoveRegistered[i]
12069                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12070                 if (prependComma) {
12071                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12072                 } else {
12073                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12074                     prependComma = 1;
12075                 }
12076
12077                 strcat(string, number);
12078             }
12079         }
12080         strcat(string, "]");
12081
12082         if (nCmailMovesRegistered + nCmailResults == 0) {
12083             switch (nCmailGames) {
12084               case 1:
12085                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12086                 break;
12087
12088               case 2:
12089                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12090                 break;
12091
12092               default:
12093                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12094                          nCmailGames);
12095                 break;
12096             }
12097         } else {
12098             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12099               case 1:
12100                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12101                          string);
12102                 break;
12103
12104               case 0:
12105                 if (nCmailResults == nCmailGames) {
12106                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12107                 } else {
12108                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12109                 }
12110                 break;
12111
12112               default:
12113                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12114                          string);
12115             }
12116         }
12117     }
12118     return cmailMsg;
12119 #endif /* WIN32 */
12120 }
12121
12122 void
12123 ResetGameEvent()
12124 {
12125     if (gameMode == Training)
12126       SetTrainingModeOff();
12127
12128     Reset(TRUE, TRUE);
12129     cmailMsgLoaded = FALSE;
12130     if (appData.icsActive) {
12131       SendToICS(ics_prefix);
12132       SendToICS("refresh\n");
12133     }
12134 }
12135
12136 void
12137 ExitEvent(status)
12138      int status;
12139 {
12140     exiting++;
12141     if (exiting > 2) {
12142       /* Give up on clean exit */
12143       exit(status);
12144     }
12145     if (exiting > 1) {
12146       /* Keep trying for clean exit */
12147       return;
12148     }
12149
12150     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12151
12152     if (telnetISR != NULL) {
12153       RemoveInputSource(telnetISR);
12154     }
12155     if (icsPR != NoProc) {
12156       DestroyChildProcess(icsPR, TRUE);
12157     }
12158
12159     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12160     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12161
12162     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12163     /* make sure this other one finishes before killing it!                  */
12164     if(endingGame) { int count = 0;
12165         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12166         while(endingGame && count++ < 10) DoSleep(1);
12167         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12168     }
12169
12170     /* Kill off chess programs */
12171     if (first.pr != NoProc) {
12172         ExitAnalyzeMode();
12173
12174         DoSleep( appData.delayBeforeQuit );
12175         SendToProgram("quit\n", &first);
12176         DoSleep( appData.delayAfterQuit );
12177         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12178     }
12179     if (second.pr != NoProc) {
12180         DoSleep( appData.delayBeforeQuit );
12181         SendToProgram("quit\n", &second);
12182         DoSleep( appData.delayAfterQuit );
12183         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12184     }
12185     if (first.isr != NULL) {
12186         RemoveInputSource(first.isr);
12187     }
12188     if (second.isr != NULL) {
12189         RemoveInputSource(second.isr);
12190     }
12191
12192     ShutDownFrontEnd();
12193     exit(status);
12194 }
12195
12196 void
12197 PauseEvent()
12198 {
12199     if (appData.debugMode)
12200         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12201     if (pausing) {
12202         pausing = FALSE;
12203         ModeHighlight();
12204         if (gameMode == MachinePlaysWhite ||
12205             gameMode == MachinePlaysBlack) {
12206             StartClocks();
12207         } else {
12208             DisplayBothClocks();
12209         }
12210         if (gameMode == PlayFromGameFile) {
12211             if (appData.timeDelay >= 0)
12212                 AutoPlayGameLoop();
12213         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12214             Reset(FALSE, TRUE);
12215             SendToICS(ics_prefix);
12216             SendToICS("refresh\n");
12217         } else if (currentMove < forwardMostMove) {
12218             ForwardInner(forwardMostMove);
12219         }
12220         pauseExamInvalid = FALSE;
12221     } else {
12222         switch (gameMode) {
12223           default:
12224             return;
12225           case IcsExamining:
12226             pauseExamForwardMostMove = forwardMostMove;
12227             pauseExamInvalid = FALSE;
12228             /* fall through */
12229           case IcsObserving:
12230           case IcsPlayingWhite:
12231           case IcsPlayingBlack:
12232             pausing = TRUE;
12233             ModeHighlight();
12234             return;
12235           case PlayFromGameFile:
12236             (void) StopLoadGameTimer();
12237             pausing = TRUE;
12238             ModeHighlight();
12239             break;
12240           case BeginningOfGame:
12241             if (appData.icsActive) return;
12242             /* else fall through */
12243           case MachinePlaysWhite:
12244           case MachinePlaysBlack:
12245           case TwoMachinesPlay:
12246             if (forwardMostMove == 0)
12247               return;           /* don't pause if no one has moved */
12248             if ((gameMode == MachinePlaysWhite &&
12249                  !WhiteOnMove(forwardMostMove)) ||
12250                 (gameMode == MachinePlaysBlack &&
12251                  WhiteOnMove(forwardMostMove))) {
12252                 StopClocks();
12253             }
12254             pausing = TRUE;
12255             ModeHighlight();
12256             break;
12257         }
12258     }
12259 }
12260
12261 void
12262 EditCommentEvent()
12263 {
12264     char title[MSG_SIZ];
12265
12266     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12267       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12268     } else {
12269       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12270                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12271                parseList[currentMove - 1]);
12272     }
12273
12274     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12275 }
12276
12277
12278 void
12279 EditTagsEvent()
12280 {
12281     char *tags = PGNTags(&gameInfo);
12282     EditTagsPopUp(tags, NULL);
12283     free(tags);
12284 }
12285
12286 void
12287 AnalyzeModeEvent()
12288 {
12289     if (appData.noChessProgram || gameMode == AnalyzeMode)
12290       return;
12291
12292     if (gameMode != AnalyzeFile) {
12293         if (!appData.icsEngineAnalyze) {
12294                EditGameEvent();
12295                if (gameMode != EditGame) return;
12296         }
12297         ResurrectChessProgram();
12298         SendToProgram("analyze\n", &first);
12299         first.analyzing = TRUE;
12300         /*first.maybeThinking = TRUE;*/
12301         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12302         EngineOutputPopUp();
12303     }
12304     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12305     pausing = FALSE;
12306     ModeHighlight();
12307     SetGameInfo();
12308
12309     StartAnalysisClock();
12310     GetTimeMark(&lastNodeCountTime);
12311     lastNodeCount = 0;
12312 }
12313
12314 void
12315 AnalyzeFileEvent()
12316 {
12317     if (appData.noChessProgram || gameMode == AnalyzeFile)
12318       return;
12319
12320     if (gameMode != AnalyzeMode) {
12321         EditGameEvent();
12322         if (gameMode != EditGame) return;
12323         ResurrectChessProgram();
12324         SendToProgram("analyze\n", &first);
12325         first.analyzing = TRUE;
12326         /*first.maybeThinking = TRUE;*/
12327         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12328         EngineOutputPopUp();
12329     }
12330     gameMode = AnalyzeFile;
12331     pausing = FALSE;
12332     ModeHighlight();
12333     SetGameInfo();
12334
12335     StartAnalysisClock();
12336     GetTimeMark(&lastNodeCountTime);
12337     lastNodeCount = 0;
12338 }
12339
12340 void
12341 MachineWhiteEvent()
12342 {
12343     char buf[MSG_SIZ];
12344     char *bookHit = NULL;
12345
12346     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12347       return;
12348
12349
12350     if (gameMode == PlayFromGameFile ||
12351         gameMode == TwoMachinesPlay  ||
12352         gameMode == Training         ||
12353         gameMode == AnalyzeMode      ||
12354         gameMode == EndOfGame)
12355         EditGameEvent();
12356
12357     if (gameMode == EditPosition)
12358         EditPositionDone(TRUE);
12359
12360     if (!WhiteOnMove(currentMove)) {
12361         DisplayError(_("It is not White's turn"), 0);
12362         return;
12363     }
12364
12365     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12366       ExitAnalyzeMode();
12367
12368     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12369         gameMode == AnalyzeFile)
12370         TruncateGame();
12371
12372     ResurrectChessProgram();    /* in case it isn't running */
12373     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12374         gameMode = MachinePlaysWhite;
12375         ResetClocks();
12376     } else
12377     gameMode = MachinePlaysWhite;
12378     pausing = FALSE;
12379     ModeHighlight();
12380     SetGameInfo();
12381     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12382     DisplayTitle(buf);
12383     if (first.sendName) {
12384       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12385       SendToProgram(buf, &first);
12386     }
12387     if (first.sendTime) {
12388       if (first.useColors) {
12389         SendToProgram("black\n", &first); /*gnu kludge*/
12390       }
12391       SendTimeRemaining(&first, TRUE);
12392     }
12393     if (first.useColors) {
12394       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12395     }
12396     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12397     SetMachineThinkingEnables();
12398     first.maybeThinking = TRUE;
12399     StartClocks();
12400     firstMove = FALSE;
12401
12402     if (appData.autoFlipView && !flipView) {
12403       flipView = !flipView;
12404       DrawPosition(FALSE, NULL);
12405       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12406     }
12407
12408     if(bookHit) { // [HGM] book: simulate book reply
12409         static char bookMove[MSG_SIZ]; // a bit generous?
12410
12411         programStats.nodes = programStats.depth = programStats.time =
12412         programStats.score = programStats.got_only_move = 0;
12413         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12414
12415         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12416         strcat(bookMove, bookHit);
12417         HandleMachineMove(bookMove, &first);
12418     }
12419 }
12420
12421 void
12422 MachineBlackEvent()
12423 {
12424   char buf[MSG_SIZ];
12425   char *bookHit = NULL;
12426
12427     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12428         return;
12429
12430
12431     if (gameMode == PlayFromGameFile ||
12432         gameMode == TwoMachinesPlay  ||
12433         gameMode == Training         ||
12434         gameMode == AnalyzeMode      ||
12435         gameMode == EndOfGame)
12436         EditGameEvent();
12437
12438     if (gameMode == EditPosition)
12439         EditPositionDone(TRUE);
12440
12441     if (WhiteOnMove(currentMove)) {
12442         DisplayError(_("It is not Black's turn"), 0);
12443         return;
12444     }
12445
12446     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12447       ExitAnalyzeMode();
12448
12449     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12450         gameMode == AnalyzeFile)
12451         TruncateGame();
12452
12453     ResurrectChessProgram();    /* in case it isn't running */
12454     gameMode = MachinePlaysBlack;
12455     pausing = FALSE;
12456     ModeHighlight();
12457     SetGameInfo();
12458     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12459     DisplayTitle(buf);
12460     if (first.sendName) {
12461       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12462       SendToProgram(buf, &first);
12463     }
12464     if (first.sendTime) {
12465       if (first.useColors) {
12466         SendToProgram("white\n", &first); /*gnu kludge*/
12467       }
12468       SendTimeRemaining(&first, FALSE);
12469     }
12470     if (first.useColors) {
12471       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12472     }
12473     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12474     SetMachineThinkingEnables();
12475     first.maybeThinking = TRUE;
12476     StartClocks();
12477
12478     if (appData.autoFlipView && flipView) {
12479       flipView = !flipView;
12480       DrawPosition(FALSE, NULL);
12481       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12482     }
12483     if(bookHit) { // [HGM] book: simulate book reply
12484         static char bookMove[MSG_SIZ]; // a bit generous?
12485
12486         programStats.nodes = programStats.depth = programStats.time =
12487         programStats.score = programStats.got_only_move = 0;
12488         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12489
12490         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12491         strcat(bookMove, bookHit);
12492         HandleMachineMove(bookMove, &first);
12493     }
12494 }
12495
12496
12497 void
12498 DisplayTwoMachinesTitle()
12499 {
12500     char buf[MSG_SIZ];
12501     if (appData.matchGames > 0) {
12502         if (first.twoMachinesColor[0] == 'w') {
12503           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12504                    gameInfo.white, gameInfo.black,
12505                    first.matchWins, second.matchWins,
12506                    matchGame - 1 - (first.matchWins + second.matchWins));
12507         } else {
12508           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12509                    gameInfo.white, gameInfo.black,
12510                    second.matchWins, first.matchWins,
12511                    matchGame - 1 - (first.matchWins + second.matchWins));
12512         }
12513     } else {
12514       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12515     }
12516     DisplayTitle(buf);
12517 }
12518
12519 void
12520 SettingsMenuIfReady()
12521 {
12522   if (second.lastPing != second.lastPong) {
12523     DisplayMessage("", _("Waiting for second chess program"));
12524     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12525     return;
12526   }
12527   ThawUI();
12528   DisplayMessage("", "");
12529   SettingsPopUp(&second);
12530 }
12531
12532 int
12533 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12534 {
12535     char buf[MSG_SIZ];
12536     if (cps->pr == NULL) {
12537         StartChessProgram(cps);
12538         if (cps->protocolVersion == 1) {
12539           retry();
12540         } else {
12541           /* kludge: allow timeout for initial "feature" command */
12542           FreezeUI();
12543           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12544           DisplayMessage("", buf);
12545           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12546         }
12547         return 1;
12548     }
12549     return 0;
12550 }
12551
12552 void
12553 TwoMachinesEvent P((void))
12554 {
12555     int i;
12556     char buf[MSG_SIZ];
12557     ChessProgramState *onmove;
12558     char *bookHit = NULL;
12559     static int stalling = 0;
12560     TimeMark now;
12561     long wait;
12562
12563     if (appData.noChessProgram) return;
12564
12565     switch (gameMode) {
12566       case TwoMachinesPlay:
12567         return;
12568       case MachinePlaysWhite:
12569       case MachinePlaysBlack:
12570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12572             return;
12573         }
12574         /* fall through */
12575       case BeginningOfGame:
12576       case PlayFromGameFile:
12577       case EndOfGame:
12578         EditGameEvent();
12579         if (gameMode != EditGame) return;
12580         break;
12581       case EditPosition:
12582         EditPositionDone(TRUE);
12583         break;
12584       case AnalyzeMode:
12585       case AnalyzeFile:
12586         ExitAnalyzeMode();
12587         break;
12588       case EditGame:
12589       default:
12590         break;
12591     }
12592
12593 //    forwardMostMove = currentMove;
12594     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12595
12596     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12597
12598     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12599     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12600       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12601       return;
12602     }
12603     if(!stalling) {
12604       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12605       SendToProgram("force\n", &second);
12606       stalling = 1;
12607       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12608       return;
12609     }
12610     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12611     if(appData.matchPause>10000 || appData.matchPause<10)
12612                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12613     wait = SubtractTimeMarks(&now, &pauseStart);
12614     if(wait < appData.matchPause) {
12615         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12616         return;
12617     }
12618     stalling = 0;
12619     DisplayMessage("", "");
12620     if (startedFromSetupPosition) {
12621         SendBoard(&second, backwardMostMove);
12622     if (appData.debugMode) {
12623         fprintf(debugFP, "Two Machines\n");
12624     }
12625     }
12626     for (i = backwardMostMove; i < forwardMostMove; i++) {
12627         SendMoveToProgram(i, &second);
12628     }
12629
12630     gameMode = TwoMachinesPlay;
12631     pausing = FALSE;
12632     ModeHighlight();
12633     SetGameInfo();
12634     DisplayTwoMachinesTitle();
12635     firstMove = TRUE;
12636     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12637         onmove = &first;
12638     } else {
12639         onmove = &second;
12640     }
12641     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12642     SendToProgram(first.computerString, &first);
12643     if (first.sendName) {
12644       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12645       SendToProgram(buf, &first);
12646     }
12647     SendToProgram(second.computerString, &second);
12648     if (second.sendName) {
12649       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12650       SendToProgram(buf, &second);
12651     }
12652
12653     ResetClocks();
12654     if (!first.sendTime || !second.sendTime) {
12655         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12656         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12657     }
12658     if (onmove->sendTime) {
12659       if (onmove->useColors) {
12660         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12661       }
12662       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12663     }
12664     if (onmove->useColors) {
12665       SendToProgram(onmove->twoMachinesColor, onmove);
12666     }
12667     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12668 //    SendToProgram("go\n", onmove);
12669     onmove->maybeThinking = TRUE;
12670     SetMachineThinkingEnables();
12671
12672     StartClocks();
12673
12674     if(bookHit) { // [HGM] book: simulate book reply
12675         static char bookMove[MSG_SIZ]; // a bit generous?
12676
12677         programStats.nodes = programStats.depth = programStats.time =
12678         programStats.score = programStats.got_only_move = 0;
12679         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12680
12681         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12682         strcat(bookMove, bookHit);
12683         savedMessage = bookMove; // args for deferred call
12684         savedState = onmove;
12685         ScheduleDelayedEvent(DeferredBookMove, 1);
12686     }
12687 }
12688
12689 void
12690 TrainingEvent()
12691 {
12692     if (gameMode == Training) {
12693       SetTrainingModeOff();
12694       gameMode = PlayFromGameFile;
12695       DisplayMessage("", _("Training mode off"));
12696     } else {
12697       gameMode = Training;
12698       animateTraining = appData.animate;
12699
12700       /* make sure we are not already at the end of the game */
12701       if (currentMove < forwardMostMove) {
12702         SetTrainingModeOn();
12703         DisplayMessage("", _("Training mode on"));
12704       } else {
12705         gameMode = PlayFromGameFile;
12706         DisplayError(_("Already at end of game"), 0);
12707       }
12708     }
12709     ModeHighlight();
12710 }
12711
12712 void
12713 IcsClientEvent()
12714 {
12715     if (!appData.icsActive) return;
12716     switch (gameMode) {
12717       case IcsPlayingWhite:
12718       case IcsPlayingBlack:
12719       case IcsObserving:
12720       case IcsIdle:
12721       case BeginningOfGame:
12722       case IcsExamining:
12723         return;
12724
12725       case EditGame:
12726         break;
12727
12728       case EditPosition:
12729         EditPositionDone(TRUE);
12730         break;
12731
12732       case AnalyzeMode:
12733       case AnalyzeFile:
12734         ExitAnalyzeMode();
12735         break;
12736
12737       default:
12738         EditGameEvent();
12739         break;
12740     }
12741
12742     gameMode = IcsIdle;
12743     ModeHighlight();
12744     return;
12745 }
12746
12747
12748 void
12749 EditGameEvent()
12750 {
12751     int i;
12752
12753     switch (gameMode) {
12754       case Training:
12755         SetTrainingModeOff();
12756         break;
12757       case MachinePlaysWhite:
12758       case MachinePlaysBlack:
12759       case BeginningOfGame:
12760         SendToProgram("force\n", &first);
12761         SetUserThinkingEnables();
12762         break;
12763       case PlayFromGameFile:
12764         (void) StopLoadGameTimer();
12765         if (gameFileFP != NULL) {
12766             gameFileFP = NULL;
12767         }
12768         break;
12769       case EditPosition:
12770         EditPositionDone(TRUE);
12771         break;
12772       case AnalyzeMode:
12773       case AnalyzeFile:
12774         ExitAnalyzeMode();
12775         SendToProgram("force\n", &first);
12776         break;
12777       case TwoMachinesPlay:
12778         GameEnds(EndOfFile, NULL, GE_PLAYER);
12779         ResurrectChessProgram();
12780         SetUserThinkingEnables();
12781         break;
12782       case EndOfGame:
12783         ResurrectChessProgram();
12784         break;
12785       case IcsPlayingBlack:
12786       case IcsPlayingWhite:
12787         DisplayError(_("Warning: You are still playing a game"), 0);
12788         break;
12789       case IcsObserving:
12790         DisplayError(_("Warning: You are still observing a game"), 0);
12791         break;
12792       case IcsExamining:
12793         DisplayError(_("Warning: You are still examining a game"), 0);
12794         break;
12795       case IcsIdle:
12796         break;
12797       case EditGame:
12798       default:
12799         return;
12800     }
12801
12802     pausing = FALSE;
12803     StopClocks();
12804     first.offeredDraw = second.offeredDraw = 0;
12805
12806     if (gameMode == PlayFromGameFile) {
12807         whiteTimeRemaining = timeRemaining[0][currentMove];
12808         blackTimeRemaining = timeRemaining[1][currentMove];
12809         DisplayTitle("");
12810     }
12811
12812     if (gameMode == MachinePlaysWhite ||
12813         gameMode == MachinePlaysBlack ||
12814         gameMode == TwoMachinesPlay ||
12815         gameMode == EndOfGame) {
12816         i = forwardMostMove;
12817         while (i > currentMove) {
12818             SendToProgram("undo\n", &first);
12819             i--;
12820         }
12821         whiteTimeRemaining = timeRemaining[0][currentMove];
12822         blackTimeRemaining = timeRemaining[1][currentMove];
12823         DisplayBothClocks();
12824         if (whiteFlag || blackFlag) {
12825             whiteFlag = blackFlag = 0;
12826         }
12827         DisplayTitle("");
12828     }
12829
12830     gameMode = EditGame;
12831     ModeHighlight();
12832     SetGameInfo();
12833 }
12834
12835
12836 void
12837 EditPositionEvent()
12838 {
12839     if (gameMode == EditPosition) {
12840         EditGameEvent();
12841         return;
12842     }
12843
12844     EditGameEvent();
12845     if (gameMode != EditGame) return;
12846
12847     gameMode = EditPosition;
12848     ModeHighlight();
12849     SetGameInfo();
12850     if (currentMove > 0)
12851       CopyBoard(boards[0], boards[currentMove]);
12852
12853     blackPlaysFirst = !WhiteOnMove(currentMove);
12854     ResetClocks();
12855     currentMove = forwardMostMove = backwardMostMove = 0;
12856     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12857     DisplayMove(-1);
12858 }
12859
12860 void
12861 ExitAnalyzeMode()
12862 {
12863     /* [DM] icsEngineAnalyze - possible call from other functions */
12864     if (appData.icsEngineAnalyze) {
12865         appData.icsEngineAnalyze = FALSE;
12866
12867         DisplayMessage("",_("Close ICS engine analyze..."));
12868     }
12869     if (first.analysisSupport && first.analyzing) {
12870       SendToProgram("exit\n", &first);
12871       first.analyzing = FALSE;
12872     }
12873     thinkOutput[0] = NULLCHAR;
12874 }
12875
12876 void
12877 EditPositionDone(Boolean fakeRights)
12878 {
12879     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12880
12881     startedFromSetupPosition = TRUE;
12882     InitChessProgram(&first, FALSE);
12883     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12884       boards[0][EP_STATUS] = EP_NONE;
12885       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12886     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12887         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12888         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12889       } else boards[0][CASTLING][2] = NoRights;
12890     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12891         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12892         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12893       } else boards[0][CASTLING][5] = NoRights;
12894     }
12895     SendToProgram("force\n", &first);
12896     if (blackPlaysFirst) {
12897         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12898         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12899         currentMove = forwardMostMove = backwardMostMove = 1;
12900         CopyBoard(boards[1], boards[0]);
12901     } else {
12902         currentMove = forwardMostMove = backwardMostMove = 0;
12903     }
12904     SendBoard(&first, forwardMostMove);
12905     if (appData.debugMode) {
12906         fprintf(debugFP, "EditPosDone\n");
12907     }
12908     DisplayTitle("");
12909     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12910     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12911     gameMode = EditGame;
12912     ModeHighlight();
12913     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12914     ClearHighlights(); /* [AS] */
12915 }
12916
12917 /* Pause for `ms' milliseconds */
12918 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12919 void
12920 TimeDelay(ms)
12921      long ms;
12922 {
12923     TimeMark m1, m2;
12924
12925     GetTimeMark(&m1);
12926     do {
12927         GetTimeMark(&m2);
12928     } while (SubtractTimeMarks(&m2, &m1) < ms);
12929 }
12930
12931 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12932 void
12933 SendMultiLineToICS(buf)
12934      char *buf;
12935 {
12936     char temp[MSG_SIZ+1], *p;
12937     int len;
12938
12939     len = strlen(buf);
12940     if (len > MSG_SIZ)
12941       len = MSG_SIZ;
12942
12943     strncpy(temp, buf, len);
12944     temp[len] = 0;
12945
12946     p = temp;
12947     while (*p) {
12948         if (*p == '\n' || *p == '\r')
12949           *p = ' ';
12950         ++p;
12951     }
12952
12953     strcat(temp, "\n");
12954     SendToICS(temp);
12955     SendToPlayer(temp, strlen(temp));
12956 }
12957
12958 void
12959 SetWhiteToPlayEvent()
12960 {
12961     if (gameMode == EditPosition) {
12962         blackPlaysFirst = FALSE;
12963         DisplayBothClocks();    /* works because currentMove is 0 */
12964     } else if (gameMode == IcsExamining) {
12965         SendToICS(ics_prefix);
12966         SendToICS("tomove white\n");
12967     }
12968 }
12969
12970 void
12971 SetBlackToPlayEvent()
12972 {
12973     if (gameMode == EditPosition) {
12974         blackPlaysFirst = TRUE;
12975         currentMove = 1;        /* kludge */
12976         DisplayBothClocks();
12977         currentMove = 0;
12978     } else if (gameMode == IcsExamining) {
12979         SendToICS(ics_prefix);
12980         SendToICS("tomove black\n");
12981     }
12982 }
12983
12984 void
12985 EditPositionMenuEvent(selection, x, y)
12986      ChessSquare selection;
12987      int x, y;
12988 {
12989     char buf[MSG_SIZ];
12990     ChessSquare piece = boards[0][y][x];
12991
12992     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12993
12994     switch (selection) {
12995       case ClearBoard:
12996         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12997             SendToICS(ics_prefix);
12998             SendToICS("bsetup clear\n");
12999         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13000             SendToICS(ics_prefix);
13001             SendToICS("clearboard\n");
13002         } else {
13003             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13004                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13005                 for (y = 0; y < BOARD_HEIGHT; y++) {
13006                     if (gameMode == IcsExamining) {
13007                         if (boards[currentMove][y][x] != EmptySquare) {
13008                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13009                                     AAA + x, ONE + y);
13010                             SendToICS(buf);
13011                         }
13012                     } else {
13013                         boards[0][y][x] = p;
13014                     }
13015                 }
13016             }
13017         }
13018         if (gameMode == EditPosition) {
13019             DrawPosition(FALSE, boards[0]);
13020         }
13021         break;
13022
13023       case WhitePlay:
13024         SetWhiteToPlayEvent();
13025         break;
13026
13027       case BlackPlay:
13028         SetBlackToPlayEvent();
13029         break;
13030
13031       case EmptySquare:
13032         if (gameMode == IcsExamining) {
13033             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13034             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13035             SendToICS(buf);
13036         } else {
13037             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13038                 if(x == BOARD_LEFT-2) {
13039                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13040                     boards[0][y][1] = 0;
13041                 } else
13042                 if(x == BOARD_RGHT+1) {
13043                     if(y >= gameInfo.holdingsSize) break;
13044                     boards[0][y][BOARD_WIDTH-2] = 0;
13045                 } else break;
13046             }
13047             boards[0][y][x] = EmptySquare;
13048             DrawPosition(FALSE, boards[0]);
13049         }
13050         break;
13051
13052       case PromotePiece:
13053         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13054            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13055             selection = (ChessSquare) (PROMOTED piece);
13056         } else if(piece == EmptySquare) selection = WhiteSilver;
13057         else selection = (ChessSquare)((int)piece - 1);
13058         goto defaultlabel;
13059
13060       case DemotePiece:
13061         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13062            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13063             selection = (ChessSquare) (DEMOTED piece);
13064         } else if(piece == EmptySquare) selection = BlackSilver;
13065         else selection = (ChessSquare)((int)piece + 1);
13066         goto defaultlabel;
13067
13068       case WhiteQueen:
13069       case BlackQueen:
13070         if(gameInfo.variant == VariantShatranj ||
13071            gameInfo.variant == VariantXiangqi  ||
13072            gameInfo.variant == VariantCourier  ||
13073            gameInfo.variant == VariantMakruk     )
13074             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13075         goto defaultlabel;
13076
13077       case WhiteKing:
13078       case BlackKing:
13079         if(gameInfo.variant == VariantXiangqi)
13080             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13081         if(gameInfo.variant == VariantKnightmate)
13082             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13083       default:
13084         defaultlabel:
13085         if (gameMode == IcsExamining) {
13086             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13087             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13088                      PieceToChar(selection), AAA + x, ONE + y);
13089             SendToICS(buf);
13090         } else {
13091             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13092                 int n;
13093                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13094                     n = PieceToNumber(selection - BlackPawn);
13095                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13096                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13097                     boards[0][BOARD_HEIGHT-1-n][1]++;
13098                 } else
13099                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13100                     n = PieceToNumber(selection);
13101                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13102                     boards[0][n][BOARD_WIDTH-1] = selection;
13103                     boards[0][n][BOARD_WIDTH-2]++;
13104                 }
13105             } else
13106             boards[0][y][x] = selection;
13107             DrawPosition(TRUE, boards[0]);
13108         }
13109         break;
13110     }
13111 }
13112
13113
13114 void
13115 DropMenuEvent(selection, x, y)
13116      ChessSquare selection;
13117      int x, y;
13118 {
13119     ChessMove moveType;
13120
13121     switch (gameMode) {
13122       case IcsPlayingWhite:
13123       case MachinePlaysBlack:
13124         if (!WhiteOnMove(currentMove)) {
13125             DisplayMoveError(_("It is Black's turn"));
13126             return;
13127         }
13128         moveType = WhiteDrop;
13129         break;
13130       case IcsPlayingBlack:
13131       case MachinePlaysWhite:
13132         if (WhiteOnMove(currentMove)) {
13133             DisplayMoveError(_("It is White's turn"));
13134             return;
13135         }
13136         moveType = BlackDrop;
13137         break;
13138       case EditGame:
13139         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13140         break;
13141       default:
13142         return;
13143     }
13144
13145     if (moveType == BlackDrop && selection < BlackPawn) {
13146       selection = (ChessSquare) ((int) selection
13147                                  + (int) BlackPawn - (int) WhitePawn);
13148     }
13149     if (boards[currentMove][y][x] != EmptySquare) {
13150         DisplayMoveError(_("That square is occupied"));
13151         return;
13152     }
13153
13154     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13155 }
13156
13157 void
13158 AcceptEvent()
13159 {
13160     /* Accept a pending offer of any kind from opponent */
13161
13162     if (appData.icsActive) {
13163         SendToICS(ics_prefix);
13164         SendToICS("accept\n");
13165     } else if (cmailMsgLoaded) {
13166         if (currentMove == cmailOldMove &&
13167             commentList[cmailOldMove] != NULL &&
13168             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13169                    "Black offers a draw" : "White offers a draw")) {
13170             TruncateGame();
13171             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13172             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13173         } else {
13174             DisplayError(_("There is no pending offer on this move"), 0);
13175             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13176         }
13177     } else {
13178         /* Not used for offers from chess program */
13179     }
13180 }
13181
13182 void
13183 DeclineEvent()
13184 {
13185     /* Decline a pending offer of any kind from opponent */
13186
13187     if (appData.icsActive) {
13188         SendToICS(ics_prefix);
13189         SendToICS("decline\n");
13190     } else if (cmailMsgLoaded) {
13191         if (currentMove == cmailOldMove &&
13192             commentList[cmailOldMove] != NULL &&
13193             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13194                    "Black offers a draw" : "White offers a draw")) {
13195 #ifdef NOTDEF
13196             AppendComment(cmailOldMove, "Draw declined", TRUE);
13197             DisplayComment(cmailOldMove - 1, "Draw declined");
13198 #endif /*NOTDEF*/
13199         } else {
13200             DisplayError(_("There is no pending offer on this move"), 0);
13201         }
13202     } else {
13203         /* Not used for offers from chess program */
13204     }
13205 }
13206
13207 void
13208 RematchEvent()
13209 {
13210     /* Issue ICS rematch command */
13211     if (appData.icsActive) {
13212         SendToICS(ics_prefix);
13213         SendToICS("rematch\n");
13214     }
13215 }
13216
13217 void
13218 CallFlagEvent()
13219 {
13220     /* Call your opponent's flag (claim a win on time) */
13221     if (appData.icsActive) {
13222         SendToICS(ics_prefix);
13223         SendToICS("flag\n");
13224     } else {
13225         switch (gameMode) {
13226           default:
13227             return;
13228           case MachinePlaysWhite:
13229             if (whiteFlag) {
13230                 if (blackFlag)
13231                   GameEnds(GameIsDrawn, "Both players ran out of time",
13232                            GE_PLAYER);
13233                 else
13234                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13235             } else {
13236                 DisplayError(_("Your opponent is not out of time"), 0);
13237             }
13238             break;
13239           case MachinePlaysBlack:
13240             if (blackFlag) {
13241                 if (whiteFlag)
13242                   GameEnds(GameIsDrawn, "Both players ran out of time",
13243                            GE_PLAYER);
13244                 else
13245                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13246             } else {
13247                 DisplayError(_("Your opponent is not out of time"), 0);
13248             }
13249             break;
13250         }
13251     }
13252 }
13253
13254 void
13255 ClockClick(int which)
13256 {       // [HGM] code moved to back-end from winboard.c
13257         if(which) { // black clock
13258           if (gameMode == EditPosition || gameMode == IcsExamining) {
13259             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13260             SetBlackToPlayEvent();
13261           } else if (gameMode == EditGame || shiftKey) {
13262             AdjustClock(which, -1);
13263           } else if (gameMode == IcsPlayingWhite ||
13264                      gameMode == MachinePlaysBlack) {
13265             CallFlagEvent();
13266           }
13267         } else { // white clock
13268           if (gameMode == EditPosition || gameMode == IcsExamining) {
13269             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13270             SetWhiteToPlayEvent();
13271           } else if (gameMode == EditGame || shiftKey) {
13272             AdjustClock(which, -1);
13273           } else if (gameMode == IcsPlayingBlack ||
13274                    gameMode == MachinePlaysWhite) {
13275             CallFlagEvent();
13276           }
13277         }
13278 }
13279
13280 void
13281 DrawEvent()
13282 {
13283     /* Offer draw or accept pending draw offer from opponent */
13284
13285     if (appData.icsActive) {
13286         /* Note: tournament rules require draw offers to be
13287            made after you make your move but before you punch
13288            your clock.  Currently ICS doesn't let you do that;
13289            instead, you immediately punch your clock after making
13290            a move, but you can offer a draw at any time. */
13291
13292         SendToICS(ics_prefix);
13293         SendToICS("draw\n");
13294         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13295     } else if (cmailMsgLoaded) {
13296         if (currentMove == cmailOldMove &&
13297             commentList[cmailOldMove] != NULL &&
13298             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13299                    "Black offers a draw" : "White offers a draw")) {
13300             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13301             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13302         } else if (currentMove == cmailOldMove + 1) {
13303             char *offer = WhiteOnMove(cmailOldMove) ?
13304               "White offers a draw" : "Black offers a draw";
13305             AppendComment(currentMove, offer, TRUE);
13306             DisplayComment(currentMove - 1, offer);
13307             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13308         } else {
13309             DisplayError(_("You must make your move before offering a draw"), 0);
13310             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13311         }
13312     } else if (first.offeredDraw) {
13313         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13314     } else {
13315         if (first.sendDrawOffers) {
13316             SendToProgram("draw\n", &first);
13317             userOfferedDraw = TRUE;
13318         }
13319     }
13320 }
13321
13322 void
13323 AdjournEvent()
13324 {
13325     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13326
13327     if (appData.icsActive) {
13328         SendToICS(ics_prefix);
13329         SendToICS("adjourn\n");
13330     } else {
13331         /* Currently GNU Chess doesn't offer or accept Adjourns */
13332     }
13333 }
13334
13335
13336 void
13337 AbortEvent()
13338 {
13339     /* Offer Abort or accept pending Abort offer from opponent */
13340
13341     if (appData.icsActive) {
13342         SendToICS(ics_prefix);
13343         SendToICS("abort\n");
13344     } else {
13345         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13346     }
13347 }
13348
13349 void
13350 ResignEvent()
13351 {
13352     /* Resign.  You can do this even if it's not your turn. */
13353
13354     if (appData.icsActive) {
13355         SendToICS(ics_prefix);
13356         SendToICS("resign\n");
13357     } else {
13358         switch (gameMode) {
13359           case MachinePlaysWhite:
13360             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13361             break;
13362           case MachinePlaysBlack:
13363             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13364             break;
13365           case EditGame:
13366             if (cmailMsgLoaded) {
13367                 TruncateGame();
13368                 if (WhiteOnMove(cmailOldMove)) {
13369                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13370                 } else {
13371                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13372                 }
13373                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13374             }
13375             break;
13376           default:
13377             break;
13378         }
13379     }
13380 }
13381
13382
13383 void
13384 StopObservingEvent()
13385 {
13386     /* Stop observing current games */
13387     SendToICS(ics_prefix);
13388     SendToICS("unobserve\n");
13389 }
13390
13391 void
13392 StopExaminingEvent()
13393 {
13394     /* Stop observing current game */
13395     SendToICS(ics_prefix);
13396     SendToICS("unexamine\n");
13397 }
13398
13399 void
13400 ForwardInner(target)
13401      int target;
13402 {
13403     int limit;
13404
13405     if (appData.debugMode)
13406         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13407                 target, currentMove, forwardMostMove);
13408
13409     if (gameMode == EditPosition)
13410       return;
13411
13412     if (gameMode == PlayFromGameFile && !pausing)
13413       PauseEvent();
13414
13415     if (gameMode == IcsExamining && pausing)
13416       limit = pauseExamForwardMostMove;
13417     else
13418       limit = forwardMostMove;
13419
13420     if (target > limit) target = limit;
13421
13422     if (target > 0 && moveList[target - 1][0]) {
13423         int fromX, fromY, toX, toY;
13424         toX = moveList[target - 1][2] - AAA;
13425         toY = moveList[target - 1][3] - ONE;
13426         if (moveList[target - 1][1] == '@') {
13427             if (appData.highlightLastMove) {
13428                 SetHighlights(-1, -1, toX, toY);
13429             }
13430         } else {
13431             fromX = moveList[target - 1][0] - AAA;
13432             fromY = moveList[target - 1][1] - ONE;
13433             if (target == currentMove + 1) {
13434                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13435             }
13436             if (appData.highlightLastMove) {
13437                 SetHighlights(fromX, fromY, toX, toY);
13438             }
13439         }
13440     }
13441     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13442         gameMode == Training || gameMode == PlayFromGameFile ||
13443         gameMode == AnalyzeFile) {
13444         while (currentMove < target) {
13445             SendMoveToProgram(currentMove++, &first);
13446         }
13447     } else {
13448         currentMove = target;
13449     }
13450
13451     if (gameMode == EditGame || gameMode == EndOfGame) {
13452         whiteTimeRemaining = timeRemaining[0][currentMove];
13453         blackTimeRemaining = timeRemaining[1][currentMove];
13454     }
13455     DisplayBothClocks();
13456     DisplayMove(currentMove - 1);
13457     DrawPosition(FALSE, boards[currentMove]);
13458     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13459     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13460         DisplayComment(currentMove - 1, commentList[currentMove]);
13461     }
13462 }
13463
13464
13465 void
13466 ForwardEvent()
13467 {
13468     if (gameMode == IcsExamining && !pausing) {
13469         SendToICS(ics_prefix);
13470         SendToICS("forward\n");
13471     } else {
13472         ForwardInner(currentMove + 1);
13473     }
13474 }
13475
13476 void
13477 ToEndEvent()
13478 {
13479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13480         /* to optimze, we temporarily turn off analysis mode while we feed
13481          * the remaining moves to the engine. Otherwise we get analysis output
13482          * after each move.
13483          */
13484         if (first.analysisSupport) {
13485           SendToProgram("exit\nforce\n", &first);
13486           first.analyzing = FALSE;
13487         }
13488     }
13489
13490     if (gameMode == IcsExamining && !pausing) {
13491         SendToICS(ics_prefix);
13492         SendToICS("forward 999999\n");
13493     } else {
13494         ForwardInner(forwardMostMove);
13495     }
13496
13497     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13498         /* we have fed all the moves, so reactivate analysis mode */
13499         SendToProgram("analyze\n", &first);
13500         first.analyzing = TRUE;
13501         /*first.maybeThinking = TRUE;*/
13502         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13503     }
13504 }
13505
13506 void
13507 BackwardInner(target)
13508      int target;
13509 {
13510     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13511
13512     if (appData.debugMode)
13513         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13514                 target, currentMove, forwardMostMove);
13515
13516     if (gameMode == EditPosition) return;
13517     if (currentMove <= backwardMostMove) {
13518         ClearHighlights();
13519         DrawPosition(full_redraw, boards[currentMove]);
13520         return;
13521     }
13522     if (gameMode == PlayFromGameFile && !pausing)
13523       PauseEvent();
13524
13525     if (moveList[target][0]) {
13526         int fromX, fromY, toX, toY;
13527         toX = moveList[target][2] - AAA;
13528         toY = moveList[target][3] - ONE;
13529         if (moveList[target][1] == '@') {
13530             if (appData.highlightLastMove) {
13531                 SetHighlights(-1, -1, toX, toY);
13532             }
13533         } else {
13534             fromX = moveList[target][0] - AAA;
13535             fromY = moveList[target][1] - ONE;
13536             if (target == currentMove - 1) {
13537                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13538             }
13539             if (appData.highlightLastMove) {
13540                 SetHighlights(fromX, fromY, toX, toY);
13541             }
13542         }
13543     }
13544     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13545         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13546         while (currentMove > target) {
13547             SendToProgram("undo\n", &first);
13548             currentMove--;
13549         }
13550     } else {
13551         currentMove = target;
13552     }
13553
13554     if (gameMode == EditGame || gameMode == EndOfGame) {
13555         whiteTimeRemaining = timeRemaining[0][currentMove];
13556         blackTimeRemaining = timeRemaining[1][currentMove];
13557     }
13558     DisplayBothClocks();
13559     DisplayMove(currentMove - 1);
13560     DrawPosition(full_redraw, boards[currentMove]);
13561     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13562     // [HGM] PV info: routine tests if comment empty
13563     DisplayComment(currentMove - 1, commentList[currentMove]);
13564 }
13565
13566 void
13567 BackwardEvent()
13568 {
13569     if (gameMode == IcsExamining && !pausing) {
13570         SendToICS(ics_prefix);
13571         SendToICS("backward\n");
13572     } else {
13573         BackwardInner(currentMove - 1);
13574     }
13575 }
13576
13577 void
13578 ToStartEvent()
13579 {
13580     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13581         /* to optimize, we temporarily turn off analysis mode while we undo
13582          * all the moves. Otherwise we get analysis output after each undo.
13583          */
13584         if (first.analysisSupport) {
13585           SendToProgram("exit\nforce\n", &first);
13586           first.analyzing = FALSE;
13587         }
13588     }
13589
13590     if (gameMode == IcsExamining && !pausing) {
13591         SendToICS(ics_prefix);
13592         SendToICS("backward 999999\n");
13593     } else {
13594         BackwardInner(backwardMostMove);
13595     }
13596
13597     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13598         /* we have fed all the moves, so reactivate analysis mode */
13599         SendToProgram("analyze\n", &first);
13600         first.analyzing = TRUE;
13601         /*first.maybeThinking = TRUE;*/
13602         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13603     }
13604 }
13605
13606 void
13607 ToNrEvent(int to)
13608 {
13609   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13610   if (to >= forwardMostMove) to = forwardMostMove;
13611   if (to <= backwardMostMove) to = backwardMostMove;
13612   if (to < currentMove) {
13613     BackwardInner(to);
13614   } else {
13615     ForwardInner(to);
13616   }
13617 }
13618
13619 void
13620 RevertEvent(Boolean annotate)
13621 {
13622     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13623         return;
13624     }
13625     if (gameMode != IcsExamining) {
13626         DisplayError(_("You are not examining a game"), 0);
13627         return;
13628     }
13629     if (pausing) {
13630         DisplayError(_("You can't revert while pausing"), 0);
13631         return;
13632     }
13633     SendToICS(ics_prefix);
13634     SendToICS("revert\n");
13635 }
13636
13637 void
13638 RetractMoveEvent()
13639 {
13640     switch (gameMode) {
13641       case MachinePlaysWhite:
13642       case MachinePlaysBlack:
13643         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13644             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13645             return;
13646         }
13647         if (forwardMostMove < 2) return;
13648         currentMove = forwardMostMove = forwardMostMove - 2;
13649         whiteTimeRemaining = timeRemaining[0][currentMove];
13650         blackTimeRemaining = timeRemaining[1][currentMove];
13651         DisplayBothClocks();
13652         DisplayMove(currentMove - 1);
13653         ClearHighlights();/*!! could figure this out*/
13654         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13655         SendToProgram("remove\n", &first);
13656         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13657         break;
13658
13659       case BeginningOfGame:
13660       default:
13661         break;
13662
13663       case IcsPlayingWhite:
13664       case IcsPlayingBlack:
13665         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13666             SendToICS(ics_prefix);
13667             SendToICS("takeback 2\n");
13668         } else {
13669             SendToICS(ics_prefix);
13670             SendToICS("takeback 1\n");
13671         }
13672         break;
13673     }
13674 }
13675
13676 void
13677 MoveNowEvent()
13678 {
13679     ChessProgramState *cps;
13680
13681     switch (gameMode) {
13682       case MachinePlaysWhite:
13683         if (!WhiteOnMove(forwardMostMove)) {
13684             DisplayError(_("It is your turn"), 0);
13685             return;
13686         }
13687         cps = &first;
13688         break;
13689       case MachinePlaysBlack:
13690         if (WhiteOnMove(forwardMostMove)) {
13691             DisplayError(_("It is your turn"), 0);
13692             return;
13693         }
13694         cps = &first;
13695         break;
13696       case TwoMachinesPlay:
13697         if (WhiteOnMove(forwardMostMove) ==
13698             (first.twoMachinesColor[0] == 'w')) {
13699             cps = &first;
13700         } else {
13701             cps = &second;
13702         }
13703         break;
13704       case BeginningOfGame:
13705       default:
13706         return;
13707     }
13708     SendToProgram("?\n", cps);
13709 }
13710
13711 void
13712 TruncateGameEvent()
13713 {
13714     EditGameEvent();
13715     if (gameMode != EditGame) return;
13716     TruncateGame();
13717 }
13718
13719 void
13720 TruncateGame()
13721 {
13722     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13723     if (forwardMostMove > currentMove) {
13724         if (gameInfo.resultDetails != NULL) {
13725             free(gameInfo.resultDetails);
13726             gameInfo.resultDetails = NULL;
13727             gameInfo.result = GameUnfinished;
13728         }
13729         forwardMostMove = currentMove;
13730         HistorySet(parseList, backwardMostMove, forwardMostMove,
13731                    currentMove-1);
13732     }
13733 }
13734
13735 void
13736 HintEvent()
13737 {
13738     if (appData.noChessProgram) return;
13739     switch (gameMode) {
13740       case MachinePlaysWhite:
13741         if (WhiteOnMove(forwardMostMove)) {
13742             DisplayError(_("Wait until your turn"), 0);
13743             return;
13744         }
13745         break;
13746       case BeginningOfGame:
13747       case MachinePlaysBlack:
13748         if (!WhiteOnMove(forwardMostMove)) {
13749             DisplayError(_("Wait until your turn"), 0);
13750             return;
13751         }
13752         break;
13753       default:
13754         DisplayError(_("No hint available"), 0);
13755         return;
13756     }
13757     SendToProgram("hint\n", &first);
13758     hintRequested = TRUE;
13759 }
13760
13761 void
13762 BookEvent()
13763 {
13764     if (appData.noChessProgram) return;
13765     switch (gameMode) {
13766       case MachinePlaysWhite:
13767         if (WhiteOnMove(forwardMostMove)) {
13768             DisplayError(_("Wait until your turn"), 0);
13769             return;
13770         }
13771         break;
13772       case BeginningOfGame:
13773       case MachinePlaysBlack:
13774         if (!WhiteOnMove(forwardMostMove)) {
13775             DisplayError(_("Wait until your turn"), 0);
13776             return;
13777         }
13778         break;
13779       case EditPosition:
13780         EditPositionDone(TRUE);
13781         break;
13782       case TwoMachinesPlay:
13783         return;
13784       default:
13785         break;
13786     }
13787     SendToProgram("bk\n", &first);
13788     bookOutput[0] = NULLCHAR;
13789     bookRequested = TRUE;
13790 }
13791
13792 void
13793 AboutGameEvent()
13794 {
13795     char *tags = PGNTags(&gameInfo);
13796     TagsPopUp(tags, CmailMsg());
13797     free(tags);
13798 }
13799
13800 /* end button procedures */
13801
13802 void
13803 PrintPosition(fp, move)
13804      FILE *fp;
13805      int move;
13806 {
13807     int i, j;
13808
13809     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13810         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13811             char c = PieceToChar(boards[move][i][j]);
13812             fputc(c == 'x' ? '.' : c, fp);
13813             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13814         }
13815     }
13816     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13817       fprintf(fp, "white to play\n");
13818     else
13819       fprintf(fp, "black to play\n");
13820 }
13821
13822 void
13823 PrintOpponents(fp)
13824      FILE *fp;
13825 {
13826     if (gameInfo.white != NULL) {
13827         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13828     } else {
13829         fprintf(fp, "\n");
13830     }
13831 }
13832
13833 /* Find last component of program's own name, using some heuristics */
13834 void
13835 TidyProgramName(prog, host, buf)
13836      char *prog, *host, buf[MSG_SIZ];
13837 {
13838     char *p, *q;
13839     int local = (strcmp(host, "localhost") == 0);
13840     while (!local && (p = strchr(prog, ';')) != NULL) {
13841         p++;
13842         while (*p == ' ') p++;
13843         prog = p;
13844     }
13845     if (*prog == '"' || *prog == '\'') {
13846         q = strchr(prog + 1, *prog);
13847     } else {
13848         q = strchr(prog, ' ');
13849     }
13850     if (q == NULL) q = prog + strlen(prog);
13851     p = q;
13852     while (p >= prog && *p != '/' && *p != '\\') p--;
13853     p++;
13854     if(p == prog && *p == '"') p++;
13855     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13856     memcpy(buf, p, q - p);
13857     buf[q - p] = NULLCHAR;
13858     if (!local) {
13859         strcat(buf, "@");
13860         strcat(buf, host);
13861     }
13862 }
13863
13864 char *
13865 TimeControlTagValue()
13866 {
13867     char buf[MSG_SIZ];
13868     if (!appData.clockMode) {
13869       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13870     } else if (movesPerSession > 0) {
13871       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13872     } else if (timeIncrement == 0) {
13873       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13874     } else {
13875       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13876     }
13877     return StrSave(buf);
13878 }
13879
13880 void
13881 SetGameInfo()
13882 {
13883     /* This routine is used only for certain modes */
13884     VariantClass v = gameInfo.variant;
13885     ChessMove r = GameUnfinished;
13886     char *p = NULL;
13887
13888     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13889         r = gameInfo.result;
13890         p = gameInfo.resultDetails;
13891         gameInfo.resultDetails = NULL;
13892     }
13893     ClearGameInfo(&gameInfo);
13894     gameInfo.variant = v;
13895
13896     switch (gameMode) {
13897       case MachinePlaysWhite:
13898         gameInfo.event = StrSave( appData.pgnEventHeader );
13899         gameInfo.site = StrSave(HostName());
13900         gameInfo.date = PGNDate();
13901         gameInfo.round = StrSave("-");
13902         gameInfo.white = StrSave(first.tidy);
13903         gameInfo.black = StrSave(UserName());
13904         gameInfo.timeControl = TimeControlTagValue();
13905         break;
13906
13907       case MachinePlaysBlack:
13908         gameInfo.event = StrSave( appData.pgnEventHeader );
13909         gameInfo.site = StrSave(HostName());
13910         gameInfo.date = PGNDate();
13911         gameInfo.round = StrSave("-");
13912         gameInfo.white = StrSave(UserName());
13913         gameInfo.black = StrSave(first.tidy);
13914         gameInfo.timeControl = TimeControlTagValue();
13915         break;
13916
13917       case TwoMachinesPlay:
13918         gameInfo.event = StrSave( appData.pgnEventHeader );
13919         gameInfo.site = StrSave(HostName());
13920         gameInfo.date = PGNDate();
13921         if (roundNr > 0) {
13922             char buf[MSG_SIZ];
13923             snprintf(buf, MSG_SIZ, "%d", roundNr);
13924             gameInfo.round = StrSave(buf);
13925         } else {
13926             gameInfo.round = StrSave("-");
13927         }
13928         if (first.twoMachinesColor[0] == 'w') {
13929             gameInfo.white = StrSave(first.tidy);
13930             gameInfo.black = StrSave(second.tidy);
13931         } else {
13932             gameInfo.white = StrSave(second.tidy);
13933             gameInfo.black = StrSave(first.tidy);
13934         }
13935         gameInfo.timeControl = TimeControlTagValue();
13936         break;
13937
13938       case EditGame:
13939         gameInfo.event = StrSave("Edited game");
13940         gameInfo.site = StrSave(HostName());
13941         gameInfo.date = PGNDate();
13942         gameInfo.round = StrSave("-");
13943         gameInfo.white = StrSave("-");
13944         gameInfo.black = StrSave("-");
13945         gameInfo.result = r;
13946         gameInfo.resultDetails = p;
13947         break;
13948
13949       case EditPosition:
13950         gameInfo.event = StrSave("Edited position");
13951         gameInfo.site = StrSave(HostName());
13952         gameInfo.date = PGNDate();
13953         gameInfo.round = StrSave("-");
13954         gameInfo.white = StrSave("-");
13955         gameInfo.black = StrSave("-");
13956         break;
13957
13958       case IcsPlayingWhite:
13959       case IcsPlayingBlack:
13960       case IcsObserving:
13961       case IcsExamining:
13962         break;
13963
13964       case PlayFromGameFile:
13965         gameInfo.event = StrSave("Game from non-PGN file");
13966         gameInfo.site = StrSave(HostName());
13967         gameInfo.date = PGNDate();
13968         gameInfo.round = StrSave("-");
13969         gameInfo.white = StrSave("?");
13970         gameInfo.black = StrSave("?");
13971         break;
13972
13973       default:
13974         break;
13975     }
13976 }
13977
13978 void
13979 ReplaceComment(index, text)
13980      int index;
13981      char *text;
13982 {
13983     int len;
13984     char *p;
13985     float score;
13986
13987     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13988        pvInfoList[index-1].depth == len &&
13989        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13990        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13991     while (*text == '\n') text++;
13992     len = strlen(text);
13993     while (len > 0 && text[len - 1] == '\n') len--;
13994
13995     if (commentList[index] != NULL)
13996       free(commentList[index]);
13997
13998     if (len == 0) {
13999         commentList[index] = NULL;
14000         return;
14001     }
14002   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14003       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14004       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14005     commentList[index] = (char *) malloc(len + 2);
14006     strncpy(commentList[index], text, len);
14007     commentList[index][len] = '\n';
14008     commentList[index][len + 1] = NULLCHAR;
14009   } else {
14010     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14011     char *p;
14012     commentList[index] = (char *) malloc(len + 7);
14013     safeStrCpy(commentList[index], "{\n", 3);
14014     safeStrCpy(commentList[index]+2, text, len+1);
14015     commentList[index][len+2] = NULLCHAR;
14016     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14017     strcat(commentList[index], "\n}\n");
14018   }
14019 }
14020
14021 void
14022 CrushCRs(text)
14023      char *text;
14024 {
14025   char *p = text;
14026   char *q = text;
14027   char ch;
14028
14029   do {
14030     ch = *p++;
14031     if (ch == '\r') continue;
14032     *q++ = ch;
14033   } while (ch != '\0');
14034 }
14035
14036 void
14037 AppendComment(index, text, addBraces)
14038      int index;
14039      char *text;
14040      Boolean addBraces; // [HGM] braces: tells if we should add {}
14041 {
14042     int oldlen, len;
14043     char *old;
14044
14045 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14046     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14047
14048     CrushCRs(text);
14049     while (*text == '\n') text++;
14050     len = strlen(text);
14051     while (len > 0 && text[len - 1] == '\n') len--;
14052
14053     if (len == 0) return;
14054
14055     if (commentList[index] != NULL) {
14056         old = commentList[index];
14057         oldlen = strlen(old);
14058         while(commentList[index][oldlen-1] ==  '\n')
14059           commentList[index][--oldlen] = NULLCHAR;
14060         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14061         safeStrCpy(commentList[index], old, oldlen + len + 6);
14062         free(old);
14063         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14064         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14065           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14066           while (*text == '\n') { text++; len--; }
14067           commentList[index][--oldlen] = NULLCHAR;
14068       }
14069         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14070         else          strcat(commentList[index], "\n");
14071         strcat(commentList[index], text);
14072         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14073         else          strcat(commentList[index], "\n");
14074     } else {
14075         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14076         if(addBraces)
14077           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14078         else commentList[index][0] = NULLCHAR;
14079         strcat(commentList[index], text);
14080         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14081         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14082     }
14083 }
14084
14085 static char * FindStr( char * text, char * sub_text )
14086 {
14087     char * result = strstr( text, sub_text );
14088
14089     if( result != NULL ) {
14090         result += strlen( sub_text );
14091     }
14092
14093     return result;
14094 }
14095
14096 /* [AS] Try to extract PV info from PGN comment */
14097 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14098 char *GetInfoFromComment( int index, char * text )
14099 {
14100     char * sep = text, *p;
14101
14102     if( text != NULL && index > 0 ) {
14103         int score = 0;
14104         int depth = 0;
14105         int time = -1, sec = 0, deci;
14106         char * s_eval = FindStr( text, "[%eval " );
14107         char * s_emt = FindStr( text, "[%emt " );
14108
14109         if( s_eval != NULL || s_emt != NULL ) {
14110             /* New style */
14111             char delim;
14112
14113             if( s_eval != NULL ) {
14114                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14115                     return text;
14116                 }
14117
14118                 if( delim != ']' ) {
14119                     return text;
14120                 }
14121             }
14122
14123             if( s_emt != NULL ) {
14124             }
14125                 return text;
14126         }
14127         else {
14128             /* We expect something like: [+|-]nnn.nn/dd */
14129             int score_lo = 0;
14130
14131             if(*text != '{') return text; // [HGM] braces: must be normal comment
14132
14133             sep = strchr( text, '/' );
14134             if( sep == NULL || sep < (text+4) ) {
14135                 return text;
14136             }
14137
14138             p = text;
14139             if(p[1] == '(') { // comment starts with PV
14140                p = strchr(p, ')'); // locate end of PV
14141                if(p == NULL || sep < p+5) return text;
14142                // at this point we have something like "{(.*) +0.23/6 ..."
14143                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14144                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14145                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14146             }
14147             time = -1; sec = -1; deci = -1;
14148             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14149                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14150                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14151                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14152                 return text;
14153             }
14154
14155             if( score_lo < 0 || score_lo >= 100 ) {
14156                 return text;
14157             }
14158
14159             if(sec >= 0) time = 600*time + 10*sec; else
14160             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14161
14162             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14163
14164             /* [HGM] PV time: now locate end of PV info */
14165             while( *++sep >= '0' && *sep <= '9'); // strip depth
14166             if(time >= 0)
14167             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14168             if(sec >= 0)
14169             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14170             if(deci >= 0)
14171             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14172             while(*sep == ' ') sep++;
14173         }
14174
14175         if( depth <= 0 ) {
14176             return text;
14177         }
14178
14179         if( time < 0 ) {
14180             time = -1;
14181         }
14182
14183         pvInfoList[index-1].depth = depth;
14184         pvInfoList[index-1].score = score;
14185         pvInfoList[index-1].time  = 10*time; // centi-sec
14186         if(*sep == '}') *sep = 0; else *--sep = '{';
14187         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14188     }
14189     return sep;
14190 }
14191
14192 void
14193 SendToProgram(message, cps)
14194      char *message;
14195      ChessProgramState *cps;
14196 {
14197     int count, outCount, error;
14198     char buf[MSG_SIZ];
14199
14200     if (cps->pr == NULL) return;
14201     Attention(cps);
14202
14203     if (appData.debugMode) {
14204         TimeMark now;
14205         GetTimeMark(&now);
14206         fprintf(debugFP, "%ld >%-6s: %s",
14207                 SubtractTimeMarks(&now, &programStartTime),
14208                 cps->which, message);
14209     }
14210
14211     count = strlen(message);
14212     outCount = OutputToProcess(cps->pr, message, count, &error);
14213     if (outCount < count && !exiting
14214                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14215       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14216       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14217         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14218             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14219                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14220                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14221                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14222             } else {
14223                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14224                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14225                 gameInfo.result = res;
14226             }
14227             gameInfo.resultDetails = StrSave(buf);
14228         }
14229         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14230         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14231     }
14232 }
14233
14234 void
14235 ReceiveFromProgram(isr, closure, message, count, error)
14236      InputSourceRef isr;
14237      VOIDSTAR closure;
14238      char *message;
14239      int count;
14240      int error;
14241 {
14242     char *end_str;
14243     char buf[MSG_SIZ];
14244     ChessProgramState *cps = (ChessProgramState *)closure;
14245
14246     if (isr != cps->isr) return; /* Killed intentionally */
14247     if (count <= 0) {
14248         if (count == 0) {
14249             RemoveInputSource(cps->isr);
14250             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14251                     _(cps->which), cps->program);
14252         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14253                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14254                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14255                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14256                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14257                 } else {
14258                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14259                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14260                     gameInfo.result = res;
14261                 }
14262                 gameInfo.resultDetails = StrSave(buf);
14263             }
14264             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14265             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14266         } else {
14267             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14268                     _(cps->which), cps->program);
14269             RemoveInputSource(cps->isr);
14270
14271             /* [AS] Program is misbehaving badly... kill it */
14272             if( count == -2 ) {
14273                 DestroyChildProcess( cps->pr, 9 );
14274                 cps->pr = NoProc;
14275             }
14276
14277             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14278         }
14279         return;
14280     }
14281
14282     if ((end_str = strchr(message, '\r')) != NULL)
14283       *end_str = NULLCHAR;
14284     if ((end_str = strchr(message, '\n')) != NULL)
14285       *end_str = NULLCHAR;
14286
14287     if (appData.debugMode) {
14288         TimeMark now; int print = 1;
14289         char *quote = ""; char c; int i;
14290
14291         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14292                 char start = message[0];
14293                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14294                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14295                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14296                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14297                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14298                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14299                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14300                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14301                    sscanf(message, "hint: %c", &c)!=1 && 
14302                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14303                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14304                     print = (appData.engineComments >= 2);
14305                 }
14306                 message[0] = start; // restore original message
14307         }
14308         if(print) {
14309                 GetTimeMark(&now);
14310                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14311                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14312                         quote,
14313                         message);
14314         }
14315     }
14316
14317     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14318     if (appData.icsEngineAnalyze) {
14319         if (strstr(message, "whisper") != NULL ||
14320              strstr(message, "kibitz") != NULL ||
14321             strstr(message, "tellics") != NULL) return;
14322     }
14323
14324     HandleMachineMove(message, cps);
14325 }
14326
14327
14328 void
14329 SendTimeControl(cps, mps, tc, inc, sd, st)
14330      ChessProgramState *cps;
14331      int mps, inc, sd, st;
14332      long tc;
14333 {
14334     char buf[MSG_SIZ];
14335     int seconds;
14336
14337     if( timeControl_2 > 0 ) {
14338         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14339             tc = timeControl_2;
14340         }
14341     }
14342     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14343     inc /= cps->timeOdds;
14344     st  /= cps->timeOdds;
14345
14346     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14347
14348     if (st > 0) {
14349       /* Set exact time per move, normally using st command */
14350       if (cps->stKludge) {
14351         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14352         seconds = st % 60;
14353         if (seconds == 0) {
14354           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14355         } else {
14356           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14357         }
14358       } else {
14359         snprintf(buf, MSG_SIZ, "st %d\n", st);
14360       }
14361     } else {
14362       /* Set conventional or incremental time control, using level command */
14363       if (seconds == 0) {
14364         /* Note old gnuchess bug -- minutes:seconds used to not work.
14365            Fixed in later versions, but still avoid :seconds
14366            when seconds is 0. */
14367         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14368       } else {
14369         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14370                  seconds, inc/1000.);
14371       }
14372     }
14373     SendToProgram(buf, cps);
14374
14375     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14376     /* Orthogonally, limit search to given depth */
14377     if (sd > 0) {
14378       if (cps->sdKludge) {
14379         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14380       } else {
14381         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14382       }
14383       SendToProgram(buf, cps);
14384     }
14385
14386     if(cps->nps >= 0) { /* [HGM] nps */
14387         if(cps->supportsNPS == FALSE)
14388           cps->nps = -1; // don't use if engine explicitly says not supported!
14389         else {
14390           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14391           SendToProgram(buf, cps);
14392         }
14393     }
14394 }
14395
14396 ChessProgramState *WhitePlayer()
14397 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14398 {
14399     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14400        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14401         return &second;
14402     return &first;
14403 }
14404
14405 void
14406 SendTimeRemaining(cps, machineWhite)
14407      ChessProgramState *cps;
14408      int /*boolean*/ machineWhite;
14409 {
14410     char message[MSG_SIZ];
14411     long time, otime;
14412
14413     /* Note: this routine must be called when the clocks are stopped
14414        or when they have *just* been set or switched; otherwise
14415        it will be off by the time since the current tick started.
14416     */
14417     if (machineWhite) {
14418         time = whiteTimeRemaining / 10;
14419         otime = blackTimeRemaining / 10;
14420     } else {
14421         time = blackTimeRemaining / 10;
14422         otime = whiteTimeRemaining / 10;
14423     }
14424     /* [HGM] translate opponent's time by time-odds factor */
14425     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14426     if (appData.debugMode) {
14427         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14428     }
14429
14430     if (time <= 0) time = 1;
14431     if (otime <= 0) otime = 1;
14432
14433     snprintf(message, MSG_SIZ, "time %ld\n", time);
14434     SendToProgram(message, cps);
14435
14436     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14437     SendToProgram(message, cps);
14438 }
14439
14440 int
14441 BoolFeature(p, name, loc, cps)
14442      char **p;
14443      char *name;
14444      int *loc;
14445      ChessProgramState *cps;
14446 {
14447   char buf[MSG_SIZ];
14448   int len = strlen(name);
14449   int val;
14450
14451   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14452     (*p) += len + 1;
14453     sscanf(*p, "%d", &val);
14454     *loc = (val != 0);
14455     while (**p && **p != ' ')
14456       (*p)++;
14457     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14458     SendToProgram(buf, cps);
14459     return TRUE;
14460   }
14461   return FALSE;
14462 }
14463
14464 int
14465 IntFeature(p, name, loc, cps)
14466      char **p;
14467      char *name;
14468      int *loc;
14469      ChessProgramState *cps;
14470 {
14471   char buf[MSG_SIZ];
14472   int len = strlen(name);
14473   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14474     (*p) += len + 1;
14475     sscanf(*p, "%d", loc);
14476     while (**p && **p != ' ') (*p)++;
14477     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14478     SendToProgram(buf, cps);
14479     return TRUE;
14480   }
14481   return FALSE;
14482 }
14483
14484 int
14485 StringFeature(p, name, loc, cps)
14486      char **p;
14487      char *name;
14488      char loc[];
14489      ChessProgramState *cps;
14490 {
14491   char buf[MSG_SIZ];
14492   int len = strlen(name);
14493   if (strncmp((*p), name, len) == 0
14494       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14495     (*p) += len + 2;
14496     sscanf(*p, "%[^\"]", loc);
14497     while (**p && **p != '\"') (*p)++;
14498     if (**p == '\"') (*p)++;
14499     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14500     SendToProgram(buf, cps);
14501     return TRUE;
14502   }
14503   return FALSE;
14504 }
14505
14506 int
14507 ParseOption(Option *opt, ChessProgramState *cps)
14508 // [HGM] options: process the string that defines an engine option, and determine
14509 // name, type, default value, and allowed value range
14510 {
14511         char *p, *q, buf[MSG_SIZ];
14512         int n, min = (-1)<<31, max = 1<<31, def;
14513
14514         if(p = strstr(opt->name, " -spin ")) {
14515             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14516             if(max < min) max = min; // enforce consistency
14517             if(def < min) def = min;
14518             if(def > max) def = max;
14519             opt->value = def;
14520             opt->min = min;
14521             opt->max = max;
14522             opt->type = Spin;
14523         } else if((p = strstr(opt->name, " -slider "))) {
14524             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14525             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14526             if(max < min) max = min; // enforce consistency
14527             if(def < min) def = min;
14528             if(def > max) def = max;
14529             opt->value = def;
14530             opt->min = min;
14531             opt->max = max;
14532             opt->type = Spin; // Slider;
14533         } else if((p = strstr(opt->name, " -string "))) {
14534             opt->textValue = p+9;
14535             opt->type = TextBox;
14536         } else if((p = strstr(opt->name, " -file "))) {
14537             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14538             opt->textValue = p+7;
14539             opt->type = FileName; // FileName;
14540         } else if((p = strstr(opt->name, " -path "))) {
14541             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14542             opt->textValue = p+7;
14543             opt->type = PathName; // PathName;
14544         } else if(p = strstr(opt->name, " -check ")) {
14545             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14546             opt->value = (def != 0);
14547             opt->type = CheckBox;
14548         } else if(p = strstr(opt->name, " -combo ")) {
14549             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14550             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14551             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14552             opt->value = n = 0;
14553             while(q = StrStr(q, " /// ")) {
14554                 n++; *q = 0;    // count choices, and null-terminate each of them
14555                 q += 5;
14556                 if(*q == '*') { // remember default, which is marked with * prefix
14557                     q++;
14558                     opt->value = n;
14559                 }
14560                 cps->comboList[cps->comboCnt++] = q;
14561             }
14562             cps->comboList[cps->comboCnt++] = NULL;
14563             opt->max = n + 1;
14564             opt->type = ComboBox;
14565         } else if(p = strstr(opt->name, " -button")) {
14566             opt->type = Button;
14567         } else if(p = strstr(opt->name, " -save")) {
14568             opt->type = SaveButton;
14569         } else return FALSE;
14570         *p = 0; // terminate option name
14571         // now look if the command-line options define a setting for this engine option.
14572         if(cps->optionSettings && cps->optionSettings[0])
14573             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14574         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14575           snprintf(buf, MSG_SIZ, "option %s", p);
14576                 if(p = strstr(buf, ",")) *p = 0;
14577                 if(q = strchr(buf, '=')) switch(opt->type) {
14578                     case ComboBox:
14579                         for(n=0; n<opt->max; n++)
14580                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14581                         break;
14582                     case TextBox:
14583                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14584                         break;
14585                     case Spin:
14586                     case CheckBox:
14587                         opt->value = atoi(q+1);
14588                     default:
14589                         break;
14590                 }
14591                 strcat(buf, "\n");
14592                 SendToProgram(buf, cps);
14593         }
14594         return TRUE;
14595 }
14596
14597 void
14598 FeatureDone(cps, val)
14599      ChessProgramState* cps;
14600      int val;
14601 {
14602   DelayedEventCallback cb = GetDelayedEvent();
14603   if ((cb == InitBackEnd3 && cps == &first) ||
14604       (cb == SettingsMenuIfReady && cps == &second) ||
14605       (cb == LoadEngine) ||
14606       (cb == TwoMachinesEventIfReady)) {
14607     CancelDelayedEvent();
14608     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14609   }
14610   cps->initDone = val;
14611 }
14612
14613 /* Parse feature command from engine */
14614 void
14615 ParseFeatures(args, cps)
14616      char* args;
14617      ChessProgramState *cps;
14618 {
14619   char *p = args;
14620   char *q;
14621   int val;
14622   char buf[MSG_SIZ];
14623
14624   for (;;) {
14625     while (*p == ' ') p++;
14626     if (*p == NULLCHAR) return;
14627
14628     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14629     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14630     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14631     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14632     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14633     if (BoolFeature(&p, "reuse", &val, cps)) {
14634       /* Engine can disable reuse, but can't enable it if user said no */
14635       if (!val) cps->reuse = FALSE;
14636       continue;
14637     }
14638     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14639     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14640       if (gameMode == TwoMachinesPlay) {
14641         DisplayTwoMachinesTitle();
14642       } else {
14643         DisplayTitle("");
14644       }
14645       continue;
14646     }
14647     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14648     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14649     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14650     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14651     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14652     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14653     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14654     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14655     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14656     if (IntFeature(&p, "done", &val, cps)) {
14657       FeatureDone(cps, val);
14658       continue;
14659     }
14660     /* Added by Tord: */
14661     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14662     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14663     /* End of additions by Tord */
14664
14665     /* [HGM] added features: */
14666     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14667     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14668     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14669     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14670     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14671     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14672     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14673         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14674           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14675             SendToProgram(buf, cps);
14676             continue;
14677         }
14678         if(cps->nrOptions >= MAX_OPTIONS) {
14679             cps->nrOptions--;
14680             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14681             DisplayError(buf, 0);
14682         }
14683         continue;
14684     }
14685     /* End of additions by HGM */
14686
14687     /* unknown feature: complain and skip */
14688     q = p;
14689     while (*q && *q != '=') q++;
14690     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14691     SendToProgram(buf, cps);
14692     p = q;
14693     if (*p == '=') {
14694       p++;
14695       if (*p == '\"') {
14696         p++;
14697         while (*p && *p != '\"') p++;
14698         if (*p == '\"') p++;
14699       } else {
14700         while (*p && *p != ' ') p++;
14701       }
14702     }
14703   }
14704
14705 }
14706
14707 void
14708 PeriodicUpdatesEvent(newState)
14709      int newState;
14710 {
14711     if (newState == appData.periodicUpdates)
14712       return;
14713
14714     appData.periodicUpdates=newState;
14715
14716     /* Display type changes, so update it now */
14717 //    DisplayAnalysis();
14718
14719     /* Get the ball rolling again... */
14720     if (newState) {
14721         AnalysisPeriodicEvent(1);
14722         StartAnalysisClock();
14723     }
14724 }
14725
14726 void
14727 PonderNextMoveEvent(newState)
14728      int newState;
14729 {
14730     if (newState == appData.ponderNextMove) return;
14731     if (gameMode == EditPosition) EditPositionDone(TRUE);
14732     if (newState) {
14733         SendToProgram("hard\n", &first);
14734         if (gameMode == TwoMachinesPlay) {
14735             SendToProgram("hard\n", &second);
14736         }
14737     } else {
14738         SendToProgram("easy\n", &first);
14739         thinkOutput[0] = NULLCHAR;
14740         if (gameMode == TwoMachinesPlay) {
14741             SendToProgram("easy\n", &second);
14742         }
14743     }
14744     appData.ponderNextMove = newState;
14745 }
14746
14747 void
14748 NewSettingEvent(option, feature, command, value)
14749      char *command;
14750      int option, value, *feature;
14751 {
14752     char buf[MSG_SIZ];
14753
14754     if (gameMode == EditPosition) EditPositionDone(TRUE);
14755     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14756     if(feature == NULL || *feature) SendToProgram(buf, &first);
14757     if (gameMode == TwoMachinesPlay) {
14758         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14759     }
14760 }
14761
14762 void
14763 ShowThinkingEvent()
14764 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14765 {
14766     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14767     int newState = appData.showThinking
14768         // [HGM] thinking: other features now need thinking output as well
14769         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14770
14771     if (oldState == newState) return;
14772     oldState = newState;
14773     if (gameMode == EditPosition) EditPositionDone(TRUE);
14774     if (oldState) {
14775         SendToProgram("post\n", &first);
14776         if (gameMode == TwoMachinesPlay) {
14777             SendToProgram("post\n", &second);
14778         }
14779     } else {
14780         SendToProgram("nopost\n", &first);
14781         thinkOutput[0] = NULLCHAR;
14782         if (gameMode == TwoMachinesPlay) {
14783             SendToProgram("nopost\n", &second);
14784         }
14785     }
14786 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14787 }
14788
14789 void
14790 AskQuestionEvent(title, question, replyPrefix, which)
14791      char *title; char *question; char *replyPrefix; char *which;
14792 {
14793   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14794   if (pr == NoProc) return;
14795   AskQuestion(title, question, replyPrefix, pr);
14796 }
14797
14798 void
14799 TypeInEvent(char firstChar)
14800 {
14801     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14802         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14803         gameMode == AnalyzeMode || gameMode == EditGame || \r
14804         gameMode == EditPosition || gameMode == IcsExamining ||\r
14805         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14806         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14807                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14808                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14809         gameMode == Training) PopUpMoveDialog(firstChar);
14810 }
14811
14812 void
14813 TypeInDoneEvent(char *move)
14814 {
14815         Board board;
14816         int n, fromX, fromY, toX, toY;
14817         char promoChar;
14818         ChessMove moveType;\r
14819
14820         // [HGM] FENedit\r
14821         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14822                 EditPositionPasteFEN(move);\r
14823                 return;\r
14824         }\r
14825         // [HGM] movenum: allow move number to be typed in any mode\r
14826         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14827           ToNrEvent(2*n-1);\r
14828           return;\r
14829         }\r
14830
14831       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14832         gameMode != Training) {\r
14833         DisplayMoveError(_("Displayed move is not current"));\r
14834       } else {\r
14835         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14836           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14837         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14838         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14839           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14840           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14841         } else {\r
14842           DisplayMoveError(_("Could not parse move"));\r
14843         }
14844       }\r
14845 }\r
14846
14847 void
14848 DisplayMove(moveNumber)
14849      int moveNumber;
14850 {
14851     char message[MSG_SIZ];
14852     char res[MSG_SIZ];
14853     char cpThinkOutput[MSG_SIZ];
14854
14855     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14856
14857     if (moveNumber == forwardMostMove - 1 ||
14858         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14859
14860         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14861
14862         if (strchr(cpThinkOutput, '\n')) {
14863             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14864         }
14865     } else {
14866         *cpThinkOutput = NULLCHAR;
14867     }
14868
14869     /* [AS] Hide thinking from human user */
14870     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14871         *cpThinkOutput = NULLCHAR;
14872         if( thinkOutput[0] != NULLCHAR ) {
14873             int i;
14874
14875             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14876                 cpThinkOutput[i] = '.';
14877             }
14878             cpThinkOutput[i] = NULLCHAR;
14879             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14880         }
14881     }
14882
14883     if (moveNumber == forwardMostMove - 1 &&
14884         gameInfo.resultDetails != NULL) {
14885         if (gameInfo.resultDetails[0] == NULLCHAR) {
14886           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14887         } else {
14888           snprintf(res, MSG_SIZ, " {%s} %s",
14889                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14890         }
14891     } else {
14892         res[0] = NULLCHAR;
14893     }
14894
14895     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14896         DisplayMessage(res, cpThinkOutput);
14897     } else {
14898       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14899                 WhiteOnMove(moveNumber) ? " " : ".. ",
14900                 parseList[moveNumber], res);
14901         DisplayMessage(message, cpThinkOutput);
14902     }
14903 }
14904
14905 void
14906 DisplayComment(moveNumber, text)
14907      int moveNumber;
14908      char *text;
14909 {
14910     char title[MSG_SIZ];
14911     char buf[8000]; // comment can be long!
14912     int score, depth;
14913
14914     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14915       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14916     } else {
14917       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14918               WhiteOnMove(moveNumber) ? " " : ".. ",
14919               parseList[moveNumber]);
14920     }
14921     // [HGM] PV info: display PV info together with (or as) comment
14922     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14923       if(text == NULL) text = "";
14924       score = pvInfoList[moveNumber].score;
14925       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14926               depth, (pvInfoList[moveNumber].time+50)/100, text);
14927       text = buf;
14928     }
14929     if (text != NULL && (appData.autoDisplayComment || commentUp))
14930         CommentPopUp(title, text);
14931 }
14932
14933 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14934  * might be busy thinking or pondering.  It can be omitted if your
14935  * gnuchess is configured to stop thinking immediately on any user
14936  * input.  However, that gnuchess feature depends on the FIONREAD
14937  * ioctl, which does not work properly on some flavors of Unix.
14938  */
14939 void
14940 Attention(cps)
14941      ChessProgramState *cps;
14942 {
14943 #if ATTENTION
14944     if (!cps->useSigint) return;
14945     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14946     switch (gameMode) {
14947       case MachinePlaysWhite:
14948       case MachinePlaysBlack:
14949       case TwoMachinesPlay:
14950       case IcsPlayingWhite:
14951       case IcsPlayingBlack:
14952       case AnalyzeMode:
14953       case AnalyzeFile:
14954         /* Skip if we know it isn't thinking */
14955         if (!cps->maybeThinking) return;
14956         if (appData.debugMode)
14957           fprintf(debugFP, "Interrupting %s\n", cps->which);
14958         InterruptChildProcess(cps->pr);
14959         cps->maybeThinking = FALSE;
14960         break;
14961       default:
14962         break;
14963     }
14964 #endif /*ATTENTION*/
14965 }
14966
14967 int
14968 CheckFlags()
14969 {
14970     if (whiteTimeRemaining <= 0) {
14971         if (!whiteFlag) {
14972             whiteFlag = TRUE;
14973             if (appData.icsActive) {
14974                 if (appData.autoCallFlag &&
14975                     gameMode == IcsPlayingBlack && !blackFlag) {
14976                   SendToICS(ics_prefix);
14977                   SendToICS("flag\n");
14978                 }
14979             } else {
14980                 if (blackFlag) {
14981                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14982                 } else {
14983                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14984                     if (appData.autoCallFlag) {
14985                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14986                         return TRUE;
14987                     }
14988                 }
14989             }
14990         }
14991     }
14992     if (blackTimeRemaining <= 0) {
14993         if (!blackFlag) {
14994             blackFlag = TRUE;
14995             if (appData.icsActive) {
14996                 if (appData.autoCallFlag &&
14997                     gameMode == IcsPlayingWhite && !whiteFlag) {
14998                   SendToICS(ics_prefix);
14999                   SendToICS("flag\n");
15000                 }
15001             } else {
15002                 if (whiteFlag) {
15003                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15004                 } else {
15005                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15006                     if (appData.autoCallFlag) {
15007                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15008                         return TRUE;
15009                     }
15010                 }
15011             }
15012         }
15013     }
15014     return FALSE;
15015 }
15016
15017 void
15018 CheckTimeControl()
15019 {
15020     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15021         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15022
15023     /*
15024      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15025      */
15026     if ( !WhiteOnMove(forwardMostMove) ) {
15027         /* White made time control */
15028         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15029         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15030         /* [HGM] time odds: correct new time quota for time odds! */
15031                                             / WhitePlayer()->timeOdds;
15032         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15033     } else {
15034         lastBlack -= blackTimeRemaining;
15035         /* Black made time control */
15036         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15037                                             / WhitePlayer()->other->timeOdds;
15038         lastWhite = whiteTimeRemaining;
15039     }
15040 }
15041
15042 void
15043 DisplayBothClocks()
15044 {
15045     int wom = gameMode == EditPosition ?
15046       !blackPlaysFirst : WhiteOnMove(currentMove);
15047     DisplayWhiteClock(whiteTimeRemaining, wom);
15048     DisplayBlackClock(blackTimeRemaining, !wom);
15049 }
15050
15051
15052 /* Timekeeping seems to be a portability nightmare.  I think everyone
15053    has ftime(), but I'm really not sure, so I'm including some ifdefs
15054    to use other calls if you don't.  Clocks will be less accurate if
15055    you have neither ftime nor gettimeofday.
15056 */
15057
15058 /* VS 2008 requires the #include outside of the function */
15059 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15060 #include <sys/timeb.h>
15061 #endif
15062
15063 /* Get the current time as a TimeMark */
15064 void
15065 GetTimeMark(tm)
15066      TimeMark *tm;
15067 {
15068 #if HAVE_GETTIMEOFDAY
15069
15070     struct timeval timeVal;
15071     struct timezone timeZone;
15072
15073     gettimeofday(&timeVal, &timeZone);
15074     tm->sec = (long) timeVal.tv_sec;
15075     tm->ms = (int) (timeVal.tv_usec / 1000L);
15076
15077 #else /*!HAVE_GETTIMEOFDAY*/
15078 #if HAVE_FTIME
15079
15080 // include <sys/timeb.h> / moved to just above start of function
15081     struct timeb timeB;
15082
15083     ftime(&timeB);
15084     tm->sec = (long) timeB.time;
15085     tm->ms = (int) timeB.millitm;
15086
15087 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15088     tm->sec = (long) time(NULL);
15089     tm->ms = 0;
15090 #endif
15091 #endif
15092 }
15093
15094 /* Return the difference in milliseconds between two
15095    time marks.  We assume the difference will fit in a long!
15096 */
15097 long
15098 SubtractTimeMarks(tm2, tm1)
15099      TimeMark *tm2, *tm1;
15100 {
15101     return 1000L*(tm2->sec - tm1->sec) +
15102            (long) (tm2->ms - tm1->ms);
15103 }
15104
15105
15106 /*
15107  * Code to manage the game clocks.
15108  *
15109  * In tournament play, black starts the clock and then white makes a move.
15110  * We give the human user a slight advantage if he is playing white---the
15111  * clocks don't run until he makes his first move, so it takes zero time.
15112  * Also, we don't account for network lag, so we could get out of sync
15113  * with GNU Chess's clock -- but then, referees are always right.
15114  */
15115
15116 static TimeMark tickStartTM;
15117 static long intendedTickLength;
15118
15119 long
15120 NextTickLength(timeRemaining)
15121      long timeRemaining;
15122 {
15123     long nominalTickLength, nextTickLength;
15124
15125     if (timeRemaining > 0L && timeRemaining <= 10000L)
15126       nominalTickLength = 100L;
15127     else
15128       nominalTickLength = 1000L;
15129     nextTickLength = timeRemaining % nominalTickLength;
15130     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15131
15132     return nextTickLength;
15133 }
15134
15135 /* Adjust clock one minute up or down */
15136 void
15137 AdjustClock(Boolean which, int dir)
15138 {
15139     if(which) blackTimeRemaining += 60000*dir;
15140     else      whiteTimeRemaining += 60000*dir;
15141     DisplayBothClocks();
15142 }
15143
15144 /* Stop clocks and reset to a fresh time control */
15145 void
15146 ResetClocks()
15147 {
15148     (void) StopClockTimer();
15149     if (appData.icsActive) {
15150         whiteTimeRemaining = blackTimeRemaining = 0;
15151     } else if (searchTime) {
15152         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15153         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15154     } else { /* [HGM] correct new time quote for time odds */
15155         whiteTC = blackTC = fullTimeControlString;
15156         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15157         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15158     }
15159     if (whiteFlag || blackFlag) {
15160         DisplayTitle("");
15161         whiteFlag = blackFlag = FALSE;
15162     }
15163     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15164     DisplayBothClocks();
15165 }
15166
15167 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15168
15169 /* Decrement running clock by amount of time that has passed */
15170 void
15171 DecrementClocks()
15172 {
15173     long timeRemaining;
15174     long lastTickLength, fudge;
15175     TimeMark now;
15176
15177     if (!appData.clockMode) return;
15178     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15179
15180     GetTimeMark(&now);
15181
15182     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15183
15184     /* Fudge if we woke up a little too soon */
15185     fudge = intendedTickLength - lastTickLength;
15186     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15187
15188     if (WhiteOnMove(forwardMostMove)) {
15189         if(whiteNPS >= 0) lastTickLength = 0;
15190         timeRemaining = whiteTimeRemaining -= lastTickLength;
15191         if(timeRemaining < 0 && !appData.icsActive) {
15192             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15193             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15194                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15195                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15196             }
15197         }
15198         DisplayWhiteClock(whiteTimeRemaining - fudge,
15199                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15200     } else {
15201         if(blackNPS >= 0) lastTickLength = 0;
15202         timeRemaining = blackTimeRemaining -= lastTickLength;
15203         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15204             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15205             if(suddenDeath) {
15206                 blackStartMove = forwardMostMove;
15207                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15208             }
15209         }
15210         DisplayBlackClock(blackTimeRemaining - fudge,
15211                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15212     }
15213     if (CheckFlags()) return;
15214
15215     tickStartTM = now;
15216     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15217     StartClockTimer(intendedTickLength);
15218
15219     /* if the time remaining has fallen below the alarm threshold, sound the
15220      * alarm. if the alarm has sounded and (due to a takeback or time control
15221      * with increment) the time remaining has increased to a level above the
15222      * threshold, reset the alarm so it can sound again.
15223      */
15224
15225     if (appData.icsActive && appData.icsAlarm) {
15226
15227         /* make sure we are dealing with the user's clock */
15228         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15229                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15230            )) return;
15231
15232         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15233             alarmSounded = FALSE;
15234         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15235             PlayAlarmSound();
15236             alarmSounded = TRUE;
15237         }
15238     }
15239 }
15240
15241
15242 /* A player has just moved, so stop the previously running
15243    clock and (if in clock mode) start the other one.
15244    We redisplay both clocks in case we're in ICS mode, because
15245    ICS gives us an update to both clocks after every move.
15246    Note that this routine is called *after* forwardMostMove
15247    is updated, so the last fractional tick must be subtracted
15248    from the color that is *not* on move now.
15249 */
15250 void
15251 SwitchClocks(int newMoveNr)
15252 {
15253     long lastTickLength;
15254     TimeMark now;
15255     int flagged = FALSE;
15256
15257     GetTimeMark(&now);
15258
15259     if (StopClockTimer() && appData.clockMode) {
15260         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15261         if (!WhiteOnMove(forwardMostMove)) {
15262             if(blackNPS >= 0) lastTickLength = 0;
15263             blackTimeRemaining -= lastTickLength;
15264            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15265 //         if(pvInfoList[forwardMostMove].time == -1)
15266                  pvInfoList[forwardMostMove].time =               // use GUI time
15267                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15268         } else {
15269            if(whiteNPS >= 0) lastTickLength = 0;
15270            whiteTimeRemaining -= lastTickLength;
15271            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15272 //         if(pvInfoList[forwardMostMove].time == -1)
15273                  pvInfoList[forwardMostMove].time =
15274                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15275         }
15276         flagged = CheckFlags();
15277     }
15278     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15279     CheckTimeControl();
15280
15281     if (flagged || !appData.clockMode) return;
15282
15283     switch (gameMode) {
15284       case MachinePlaysBlack:
15285       case MachinePlaysWhite:
15286       case BeginningOfGame:
15287         if (pausing) return;
15288         break;
15289
15290       case EditGame:
15291       case PlayFromGameFile:
15292       case IcsExamining:
15293         return;
15294
15295       default:
15296         break;
15297     }
15298
15299     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15300         if(WhiteOnMove(forwardMostMove))
15301              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15302         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15303     }
15304
15305     tickStartTM = now;
15306     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15307       whiteTimeRemaining : blackTimeRemaining);
15308     StartClockTimer(intendedTickLength);
15309 }
15310
15311
15312 /* Stop both clocks */
15313 void
15314 StopClocks()
15315 {
15316     long lastTickLength;
15317     TimeMark now;
15318
15319     if (!StopClockTimer()) return;
15320     if (!appData.clockMode) return;
15321
15322     GetTimeMark(&now);
15323
15324     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15325     if (WhiteOnMove(forwardMostMove)) {
15326         if(whiteNPS >= 0) lastTickLength = 0;
15327         whiteTimeRemaining -= lastTickLength;
15328         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15329     } else {
15330         if(blackNPS >= 0) lastTickLength = 0;
15331         blackTimeRemaining -= lastTickLength;
15332         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15333     }
15334     CheckFlags();
15335 }
15336
15337 /* Start clock of player on move.  Time may have been reset, so
15338    if clock is already running, stop and restart it. */
15339 void
15340 StartClocks()
15341 {
15342     (void) StopClockTimer(); /* in case it was running already */
15343     DisplayBothClocks();
15344     if (CheckFlags()) return;
15345
15346     if (!appData.clockMode) return;
15347     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15348
15349     GetTimeMark(&tickStartTM);
15350     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15351       whiteTimeRemaining : blackTimeRemaining);
15352
15353    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15354     whiteNPS = blackNPS = -1;
15355     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15356        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15357         whiteNPS = first.nps;
15358     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15359        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15360         blackNPS = first.nps;
15361     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15362         whiteNPS = second.nps;
15363     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15364         blackNPS = second.nps;
15365     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15366
15367     StartClockTimer(intendedTickLength);
15368 }
15369
15370 char *
15371 TimeString(ms)
15372      long ms;
15373 {
15374     long second, minute, hour, day;
15375     char *sign = "";
15376     static char buf[32];
15377
15378     if (ms > 0 && ms <= 9900) {
15379       /* convert milliseconds to tenths, rounding up */
15380       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15381
15382       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15383       return buf;
15384     }
15385
15386     /* convert milliseconds to seconds, rounding up */
15387     /* use floating point to avoid strangeness of integer division
15388        with negative dividends on many machines */
15389     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15390
15391     if (second < 0) {
15392         sign = "-";
15393         second = -second;
15394     }
15395
15396     day = second / (60 * 60 * 24);
15397     second = second % (60 * 60 * 24);
15398     hour = second / (60 * 60);
15399     second = second % (60 * 60);
15400     minute = second / 60;
15401     second = second % 60;
15402
15403     if (day > 0)
15404       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15405               sign, day, hour, minute, second);
15406     else if (hour > 0)
15407       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15408     else
15409       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15410
15411     return buf;
15412 }
15413
15414
15415 /*
15416  * This is necessary because some C libraries aren't ANSI C compliant yet.
15417  */
15418 char *
15419 StrStr(string, match)
15420      char *string, *match;
15421 {
15422     int i, length;
15423
15424     length = strlen(match);
15425
15426     for (i = strlen(string) - length; i >= 0; i--, string++)
15427       if (!strncmp(match, string, length))
15428         return string;
15429
15430     return NULL;
15431 }
15432
15433 char *
15434 StrCaseStr(string, match)
15435      char *string, *match;
15436 {
15437     int i, j, length;
15438
15439     length = strlen(match);
15440
15441     for (i = strlen(string) - length; i >= 0; i--, string++) {
15442         for (j = 0; j < length; j++) {
15443             if (ToLower(match[j]) != ToLower(string[j]))
15444               break;
15445         }
15446         if (j == length) return string;
15447     }
15448
15449     return NULL;
15450 }
15451
15452 #ifndef _amigados
15453 int
15454 StrCaseCmp(s1, s2)
15455      char *s1, *s2;
15456 {
15457     char c1, c2;
15458
15459     for (;;) {
15460         c1 = ToLower(*s1++);
15461         c2 = ToLower(*s2++);
15462         if (c1 > c2) return 1;
15463         if (c1 < c2) return -1;
15464         if (c1 == NULLCHAR) return 0;
15465     }
15466 }
15467
15468
15469 int
15470 ToLower(c)
15471      int c;
15472 {
15473     return isupper(c) ? tolower(c) : c;
15474 }
15475
15476
15477 int
15478 ToUpper(c)
15479      int c;
15480 {
15481     return islower(c) ? toupper(c) : c;
15482 }
15483 #endif /* !_amigados    */
15484
15485 char *
15486 StrSave(s)
15487      char *s;
15488 {
15489   char *ret;
15490
15491   if ((ret = (char *) malloc(strlen(s) + 1)))
15492     {
15493       safeStrCpy(ret, s, strlen(s)+1);
15494     }
15495   return ret;
15496 }
15497
15498 char *
15499 StrSavePtr(s, savePtr)
15500      char *s, **savePtr;
15501 {
15502     if (*savePtr) {
15503         free(*savePtr);
15504     }
15505     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15506       safeStrCpy(*savePtr, s, strlen(s)+1);
15507     }
15508     return(*savePtr);
15509 }
15510
15511 char *
15512 PGNDate()
15513 {
15514     time_t clock;
15515     struct tm *tm;
15516     char buf[MSG_SIZ];
15517
15518     clock = time((time_t *)NULL);
15519     tm = localtime(&clock);
15520     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15521             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15522     return StrSave(buf);
15523 }
15524
15525
15526 char *
15527 PositionToFEN(move, overrideCastling)
15528      int move;
15529      char *overrideCastling;
15530 {
15531     int i, j, fromX, fromY, toX, toY;
15532     int whiteToPlay;
15533     char buf[128];
15534     char *p, *q;
15535     int emptycount;
15536     ChessSquare piece;
15537
15538     whiteToPlay = (gameMode == EditPosition) ?
15539       !blackPlaysFirst : (move % 2 == 0);
15540     p = buf;
15541
15542     /* Piece placement data */
15543     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15544         emptycount = 0;
15545         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15546             if (boards[move][i][j] == EmptySquare) {
15547                 emptycount++;
15548             } else { ChessSquare piece = boards[move][i][j];
15549                 if (emptycount > 0) {
15550                     if(emptycount<10) /* [HGM] can be >= 10 */
15551                         *p++ = '0' + emptycount;
15552                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15553                     emptycount = 0;
15554                 }
15555                 if(PieceToChar(piece) == '+') {
15556                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15557                     *p++ = '+';
15558                     piece = (ChessSquare)(DEMOTED piece);
15559                 }
15560                 *p++ = PieceToChar(piece);
15561                 if(p[-1] == '~') {
15562                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15563                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15564                     *p++ = '~';
15565                 }
15566             }
15567         }
15568         if (emptycount > 0) {
15569             if(emptycount<10) /* [HGM] can be >= 10 */
15570                 *p++ = '0' + emptycount;
15571             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15572             emptycount = 0;
15573         }
15574         *p++ = '/';
15575     }
15576     *(p - 1) = ' ';
15577
15578     /* [HGM] print Crazyhouse or Shogi holdings */
15579     if( gameInfo.holdingsWidth ) {
15580         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15581         q = p;
15582         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15583             piece = boards[move][i][BOARD_WIDTH-1];
15584             if( piece != EmptySquare )
15585               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15586                   *p++ = PieceToChar(piece);
15587         }
15588         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15589             piece = boards[move][BOARD_HEIGHT-i-1][0];
15590             if( piece != EmptySquare )
15591               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15592                   *p++ = PieceToChar(piece);
15593         }
15594
15595         if( q == p ) *p++ = '-';
15596         *p++ = ']';
15597         *p++ = ' ';
15598     }
15599
15600     /* Active color */
15601     *p++ = whiteToPlay ? 'w' : 'b';
15602     *p++ = ' ';
15603
15604   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15605     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15606   } else {
15607   if(nrCastlingRights) {
15608      q = p;
15609      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15610        /* [HGM] write directly from rights */
15611            if(boards[move][CASTLING][2] != NoRights &&
15612               boards[move][CASTLING][0] != NoRights   )
15613                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15614            if(boards[move][CASTLING][2] != NoRights &&
15615               boards[move][CASTLING][1] != NoRights   )
15616                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15617            if(boards[move][CASTLING][5] != NoRights &&
15618               boards[move][CASTLING][3] != NoRights   )
15619                 *p++ = boards[move][CASTLING][3] + AAA;
15620            if(boards[move][CASTLING][5] != NoRights &&
15621               boards[move][CASTLING][4] != NoRights   )
15622                 *p++ = boards[move][CASTLING][4] + AAA;
15623      } else {
15624
15625         /* [HGM] write true castling rights */
15626         if( nrCastlingRights == 6 ) {
15627             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15628                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15629             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15630                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15631             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15632                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15633             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15634                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15635         }
15636      }
15637      if (q == p) *p++ = '-'; /* No castling rights */
15638      *p++ = ' ';
15639   }
15640
15641   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15642      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15643     /* En passant target square */
15644     if (move > backwardMostMove) {
15645         fromX = moveList[move - 1][0] - AAA;
15646         fromY = moveList[move - 1][1] - ONE;
15647         toX = moveList[move - 1][2] - AAA;
15648         toY = moveList[move - 1][3] - ONE;
15649         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15650             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15651             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15652             fromX == toX) {
15653             /* 2-square pawn move just happened */
15654             *p++ = toX + AAA;
15655             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15656         } else {
15657             *p++ = '-';
15658         }
15659     } else if(move == backwardMostMove) {
15660         // [HGM] perhaps we should always do it like this, and forget the above?
15661         if((signed char)boards[move][EP_STATUS] >= 0) {
15662             *p++ = boards[move][EP_STATUS] + AAA;
15663             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15664         } else {
15665             *p++ = '-';
15666         }
15667     } else {
15668         *p++ = '-';
15669     }
15670     *p++ = ' ';
15671   }
15672   }
15673
15674     /* [HGM] find reversible plies */
15675     {   int i = 0, j=move;
15676
15677         if (appData.debugMode) { int k;
15678             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15679             for(k=backwardMostMove; k<=forwardMostMove; k++)
15680                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15681
15682         }
15683
15684         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15685         if( j == backwardMostMove ) i += initialRulePlies;
15686         sprintf(p, "%d ", i);
15687         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15688     }
15689     /* Fullmove number */
15690     sprintf(p, "%d", (move / 2) + 1);
15691
15692     return StrSave(buf);
15693 }
15694
15695 Boolean
15696 ParseFEN(board, blackPlaysFirst, fen)
15697     Board board;
15698      int *blackPlaysFirst;
15699      char *fen;
15700 {
15701     int i, j;
15702     char *p, c;
15703     int emptycount;
15704     ChessSquare piece;
15705
15706     p = fen;
15707
15708     /* [HGM] by default clear Crazyhouse holdings, if present */
15709     if(gameInfo.holdingsWidth) {
15710        for(i=0; i<BOARD_HEIGHT; i++) {
15711            board[i][0]             = EmptySquare; /* black holdings */
15712            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15713            board[i][1]             = (ChessSquare) 0; /* black counts */
15714            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15715        }
15716     }
15717
15718     /* Piece placement data */
15719     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15720         j = 0;
15721         for (;;) {
15722             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15723                 if (*p == '/') p++;
15724                 emptycount = gameInfo.boardWidth - j;
15725                 while (emptycount--)
15726                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15727                 break;
15728 #if(BOARD_FILES >= 10)
15729             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15730                 p++; emptycount=10;
15731                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15732                 while (emptycount--)
15733                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15734 #endif
15735             } else if (isdigit(*p)) {
15736                 emptycount = *p++ - '0';
15737                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15738                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15739                 while (emptycount--)
15740                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15741             } else if (*p == '+' || isalpha(*p)) {
15742                 if (j >= gameInfo.boardWidth) return FALSE;
15743                 if(*p=='+') {
15744                     piece = CharToPiece(*++p);
15745                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15746                     piece = (ChessSquare) (PROMOTED piece ); p++;
15747                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15748                 } else piece = CharToPiece(*p++);
15749
15750                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15751                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15752                     piece = (ChessSquare) (PROMOTED piece);
15753                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15754                     p++;
15755                 }
15756                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15757             } else {
15758                 return FALSE;
15759             }
15760         }
15761     }
15762     while (*p == '/' || *p == ' ') p++;
15763
15764     /* [HGM] look for Crazyhouse holdings here */
15765     while(*p==' ') p++;
15766     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15767         if(*p == '[') p++;
15768         if(*p == '-' ) p++; /* empty holdings */ else {
15769             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15770             /* if we would allow FEN reading to set board size, we would   */
15771             /* have to add holdings and shift the board read so far here   */
15772             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15773                 p++;
15774                 if((int) piece >= (int) BlackPawn ) {
15775                     i = (int)piece - (int)BlackPawn;
15776                     i = PieceToNumber((ChessSquare)i);
15777                     if( i >= gameInfo.holdingsSize ) return FALSE;
15778                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15779                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15780                 } else {
15781                     i = (int)piece - (int)WhitePawn;
15782                     i = PieceToNumber((ChessSquare)i);
15783                     if( i >= gameInfo.holdingsSize ) return FALSE;
15784                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15785                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15786                 }
15787             }
15788         }
15789         if(*p == ']') p++;
15790     }
15791
15792     while(*p == ' ') p++;
15793
15794     /* Active color */
15795     c = *p++;
15796     if(appData.colorNickNames) {
15797       if( c == appData.colorNickNames[0] ) c = 'w'; else
15798       if( c == appData.colorNickNames[1] ) c = 'b';
15799     }
15800     switch (c) {
15801       case 'w':
15802         *blackPlaysFirst = FALSE;
15803         break;
15804       case 'b':
15805         *blackPlaysFirst = TRUE;
15806         break;
15807       default:
15808         return FALSE;
15809     }
15810
15811     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15812     /* return the extra info in global variiables             */
15813
15814     /* set defaults in case FEN is incomplete */
15815     board[EP_STATUS] = EP_UNKNOWN;
15816     for(i=0; i<nrCastlingRights; i++ ) {
15817         board[CASTLING][i] =
15818             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15819     }   /* assume possible unless obviously impossible */
15820     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15821     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15822     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15823                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15824     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15825     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15826     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15827                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15828     FENrulePlies = 0;
15829
15830     while(*p==' ') p++;
15831     if(nrCastlingRights) {
15832       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15833           /* castling indicator present, so default becomes no castlings */
15834           for(i=0; i<nrCastlingRights; i++ ) {
15835                  board[CASTLING][i] = NoRights;
15836           }
15837       }
15838       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15839              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15840              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15841              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15842         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15843
15844         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15845             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15846             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15847         }
15848         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15849             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15850         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15851                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15852         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15853                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15854         switch(c) {
15855           case'K':
15856               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15857               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15858               board[CASTLING][2] = whiteKingFile;
15859               break;
15860           case'Q':
15861               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15862               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15863               board[CASTLING][2] = whiteKingFile;
15864               break;
15865           case'k':
15866               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15867               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15868               board[CASTLING][5] = blackKingFile;
15869               break;
15870           case'q':
15871               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15872               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15873               board[CASTLING][5] = blackKingFile;
15874           case '-':
15875               break;
15876           default: /* FRC castlings */
15877               if(c >= 'a') { /* black rights */
15878                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15879                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15880                   if(i == BOARD_RGHT) break;
15881                   board[CASTLING][5] = i;
15882                   c -= AAA;
15883                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15884                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15885                   if(c > i)
15886                       board[CASTLING][3] = c;
15887                   else
15888                       board[CASTLING][4] = c;
15889               } else { /* white rights */
15890                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15891                     if(board[0][i] == WhiteKing) break;
15892                   if(i == BOARD_RGHT) break;
15893                   board[CASTLING][2] = i;
15894                   c -= AAA - 'a' + 'A';
15895                   if(board[0][c] >= WhiteKing) break;
15896                   if(c > i)
15897                       board[CASTLING][0] = c;
15898                   else
15899                       board[CASTLING][1] = c;
15900               }
15901         }
15902       }
15903       for(i=0; i<nrCastlingRights; i++)
15904         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15905     if (appData.debugMode) {
15906         fprintf(debugFP, "FEN castling rights:");
15907         for(i=0; i<nrCastlingRights; i++)
15908         fprintf(debugFP, " %d", board[CASTLING][i]);
15909         fprintf(debugFP, "\n");
15910     }
15911
15912       while(*p==' ') p++;
15913     }
15914
15915     /* read e.p. field in games that know e.p. capture */
15916     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15917        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15918       if(*p=='-') {
15919         p++; board[EP_STATUS] = EP_NONE;
15920       } else {
15921          char c = *p++ - AAA;
15922
15923          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15924          if(*p >= '0' && *p <='9') p++;
15925          board[EP_STATUS] = c;
15926       }
15927     }
15928
15929
15930     if(sscanf(p, "%d", &i) == 1) {
15931         FENrulePlies = i; /* 50-move ply counter */
15932         /* (The move number is still ignored)    */
15933     }
15934
15935     return TRUE;
15936 }
15937
15938 void
15939 EditPositionPasteFEN(char *fen)
15940 {
15941   if (fen != NULL) {
15942     Board initial_position;
15943
15944     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15945       DisplayError(_("Bad FEN position in clipboard"), 0);
15946       return ;
15947     } else {
15948       int savedBlackPlaysFirst = blackPlaysFirst;
15949       EditPositionEvent();
15950       blackPlaysFirst = savedBlackPlaysFirst;
15951       CopyBoard(boards[0], initial_position);
15952       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15953       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15954       DisplayBothClocks();
15955       DrawPosition(FALSE, boards[currentMove]);
15956     }
15957   }
15958 }
15959
15960 static char cseq[12] = "\\   ";
15961
15962 Boolean set_cont_sequence(char *new_seq)
15963 {
15964     int len;
15965     Boolean ret;
15966
15967     // handle bad attempts to set the sequence
15968         if (!new_seq)
15969                 return 0; // acceptable error - no debug
15970
15971     len = strlen(new_seq);
15972     ret = (len > 0) && (len < sizeof(cseq));
15973     if (ret)
15974       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15975     else if (appData.debugMode)
15976       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15977     return ret;
15978 }
15979
15980 /*
15981     reformat a source message so words don't cross the width boundary.  internal
15982     newlines are not removed.  returns the wrapped size (no null character unless
15983     included in source message).  If dest is NULL, only calculate the size required
15984     for the dest buffer.  lp argument indicats line position upon entry, and it's
15985     passed back upon exit.
15986 */
15987 int wrap(char *dest, char *src, int count, int width, int *lp)
15988 {
15989     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15990
15991     cseq_len = strlen(cseq);
15992     old_line = line = *lp;
15993     ansi = len = clen = 0;
15994
15995     for (i=0; i < count; i++)
15996     {
15997         if (src[i] == '\033')
15998             ansi = 1;
15999
16000         // if we hit the width, back up
16001         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16002         {
16003             // store i & len in case the word is too long
16004             old_i = i, old_len = len;
16005
16006             // find the end of the last word
16007             while (i && src[i] != ' ' && src[i] != '\n')
16008             {
16009                 i--;
16010                 len--;
16011             }
16012
16013             // word too long?  restore i & len before splitting it
16014             if ((old_i-i+clen) >= width)
16015             {
16016                 i = old_i;
16017                 len = old_len;
16018             }
16019
16020             // extra space?
16021             if (i && src[i-1] == ' ')
16022                 len--;
16023
16024             if (src[i] != ' ' && src[i] != '\n')
16025             {
16026                 i--;
16027                 if (len)
16028                     len--;
16029             }
16030
16031             // now append the newline and continuation sequence
16032             if (dest)
16033                 dest[len] = '\n';
16034             len++;
16035             if (dest)
16036                 strncpy(dest+len, cseq, cseq_len);
16037             len += cseq_len;
16038             line = cseq_len;
16039             clen = cseq_len;
16040             continue;
16041         }
16042
16043         if (dest)
16044             dest[len] = src[i];
16045         len++;
16046         if (!ansi)
16047             line++;
16048         if (src[i] == '\n')
16049             line = 0;
16050         if (src[i] == 'm')
16051             ansi = 0;
16052     }
16053     if (dest && appData.debugMode)
16054     {
16055         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16056             count, width, line, len, *lp);
16057         show_bytes(debugFP, src, count);
16058         fprintf(debugFP, "\ndest: ");
16059         show_bytes(debugFP, dest, len);
16060         fprintf(debugFP, "\n");
16061     }
16062     *lp = dest ? line : old_line;
16063
16064     return len;
16065 }
16066
16067 // [HGM] vari: routines for shelving variations
16068
16069 void
16070 PushInner(int firstMove, int lastMove)
16071 {
16072         int i, j, nrMoves = lastMove - firstMove;
16073
16074         // push current tail of game on stack
16075         savedResult[storedGames] = gameInfo.result;
16076         savedDetails[storedGames] = gameInfo.resultDetails;
16077         gameInfo.resultDetails = NULL;
16078         savedFirst[storedGames] = firstMove;
16079         savedLast [storedGames] = lastMove;
16080         savedFramePtr[storedGames] = framePtr;
16081         framePtr -= nrMoves; // reserve space for the boards
16082         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16083             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16084             for(j=0; j<MOVE_LEN; j++)
16085                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16086             for(j=0; j<2*MOVE_LEN; j++)
16087                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16088             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16089             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16090             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16091             pvInfoList[firstMove+i-1].depth = 0;
16092             commentList[framePtr+i] = commentList[firstMove+i];
16093             commentList[firstMove+i] = NULL;
16094         }
16095
16096         storedGames++;
16097         forwardMostMove = firstMove; // truncate game so we can start variation
16098 }
16099
16100 void
16101 PushTail(int firstMove, int lastMove)
16102 {
16103         if(appData.icsActive) { // only in local mode
16104                 forwardMostMove = currentMove; // mimic old ICS behavior
16105                 return;
16106         }
16107         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16108
16109         PushInner(firstMove, lastMove);
16110         if(storedGames == 1) GreyRevert(FALSE);
16111 }
16112
16113 void
16114 PopInner(Boolean annotate)
16115 {
16116         int i, j, nrMoves;
16117         char buf[8000], moveBuf[20];
16118
16119         storedGames--;
16120         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16121         nrMoves = savedLast[storedGames] - currentMove;
16122         if(annotate) {
16123                 int cnt = 10;
16124                 if(!WhiteOnMove(currentMove))
16125                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16126                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16127                 for(i=currentMove; i<forwardMostMove; i++) {
16128                         if(WhiteOnMove(i))
16129                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16130                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16131                         strcat(buf, moveBuf);
16132                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16133                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16134                 }
16135                 strcat(buf, ")");
16136         }
16137         for(i=1; i<=nrMoves; i++) { // copy last variation back
16138             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16139             for(j=0; j<MOVE_LEN; j++)
16140                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16141             for(j=0; j<2*MOVE_LEN; j++)
16142                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16143             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16144             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16145             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16146             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16147             commentList[currentMove+i] = commentList[framePtr+i];
16148             commentList[framePtr+i] = NULL;
16149         }
16150         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16151         framePtr = savedFramePtr[storedGames];
16152         gameInfo.result = savedResult[storedGames];
16153         if(gameInfo.resultDetails != NULL) {
16154             free(gameInfo.resultDetails);
16155       }
16156         gameInfo.resultDetails = savedDetails[storedGames];
16157         forwardMostMove = currentMove + nrMoves;
16158 }
16159
16160 Boolean
16161 PopTail(Boolean annotate)
16162 {
16163         if(appData.icsActive) return FALSE; // only in local mode
16164         if(!storedGames) return FALSE; // sanity
16165         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16166
16167         PopInner(annotate);
16168
16169         if(storedGames == 0) GreyRevert(TRUE);
16170         return TRUE;
16171 }
16172
16173 void
16174 CleanupTail()
16175 {       // remove all shelved variations
16176         int i;
16177         for(i=0; i<storedGames; i++) {
16178             if(savedDetails[i])
16179                 free(savedDetails[i]);
16180             savedDetails[i] = NULL;
16181         }
16182         for(i=framePtr; i<MAX_MOVES; i++) {
16183                 if(commentList[i]) free(commentList[i]);
16184                 commentList[i] = NULL;
16185         }
16186         framePtr = MAX_MOVES-1;
16187         storedGames = 0;
16188 }
16189
16190 void
16191 LoadVariation(int index, char *text)
16192 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16193         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16194         int level = 0, move;
16195
16196         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16197         // first find outermost bracketing variation
16198         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16199             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16200                 if(*p == '{') wait = '}'; else
16201                 if(*p == '[') wait = ']'; else
16202                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16203                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16204             }
16205             if(*p == wait) wait = NULLCHAR; // closing ]} found
16206             p++;
16207         }
16208         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16209         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16210         end[1] = NULLCHAR; // clip off comment beyond variation
16211         ToNrEvent(currentMove-1);
16212         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16213         // kludge: use ParsePV() to append variation to game
16214         move = currentMove;
16215         ParsePV(start, TRUE);
16216         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16217         ClearPremoveHighlights();
16218         CommentPopDown();
16219         ToNrEvent(currentMove+1);
16220 }
16221