Calculate and display tourney result
[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 CleanupTail P((void));
510
511 ChessSquare  FIDEArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackBishop, BlackKnight, BlackRook }
516 };
517
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackKing, BlackKnight, BlackRook }
523 };
524
525 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528     { BlackRook, BlackMan, BlackBishop, BlackQueen,
529         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 };
531
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 };
538
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 };
545
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 };
552
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackMan, BlackFerz,
557         BlackKing, BlackMan, BlackKnight, BlackRook }
558 };
559
560
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 };
568
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 };
582
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 };
589
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 };
596
597 #ifdef GOTHIC
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
603 };
604 #else // !GOTHIC
605 #define GothicArray CapablancaArray
606 #endif // !GOTHIC
607
608 #ifdef FALCON
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !FALCON
616 #define FalconArray CapablancaArray
617 #endif // !FALCON
618
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
625
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
632 };
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
636
637
638 Board initialPosition;
639
640
641 /* Convert str to a rating. Checks for special cases of "----",
642
643    "++++", etc. Also strips ()'s */
644 int
645 string_to_rating(str)
646   char *str;
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine(ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions(ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine(ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796     cps->supportsNPS = UNKNOWN;
797
798     /* [HGM] options */
799     cps->optionSettings  = appData.engOptions[n];
800
801     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802     cps->isUCI = appData.isUCI[n]; /* [AS] */
803     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
804
805     if (appData.protocolVersion[n] > PROTOVER
806         || appData.protocolVersion[n] < 1)
807       {
808         char buf[MSG_SIZ];
809         int len;
810
811         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812                        appData.protocolVersion[n]);
813         if( (len > MSG_SIZ) && appData.debugMode )
814           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
815
816         DisplayFatalError(buf, 0, 2);
817       }
818     else
819       {
820         cps->protocolVersion = appData.protocolVersion[n];
821       }
822
823     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
824 }
825
826 ChessProgramState *savCps;
827
828 void
829 LoadEngine()
830 {
831     int i;
832     if(WaitForEngine(savCps, LoadEngine)) return;
833     CommonEngineInit(); // recalculate time odds
834     if(gameInfo.variant != StringToVariant(appData.variant)) {
835         // we changed variant when loading the engine; this forces us to reset
836         Reset(TRUE, savCps != &first);
837         EditGameEvent(); // for consistency with other path, as Reset changes mode
838     }
839     InitChessProgram(savCps, FALSE);
840     SendToProgram("force\n", savCps);
841     DisplayMessage("", "");
842     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
844     ThawUI();
845     SetGNUMode();
846 }
847
848 void
849 ReplaceEngine(ChessProgramState *cps, int n)
850 {
851     EditGameEvent();
852     UnloadEngine(cps);
853     appData.noChessProgram = FALSE;
854     appData.clockMode = TRUE;
855     InitEngine(cps, n);
856     if(n) return; // only startup first engine immediately; second can wait
857     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
858     LoadEngine();
859 }
860
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
863
864 void Load(ChessProgramState *cps, int i)
865 {
866     char *p, *q, buf[MSG_SIZ];
867     if(engineLine[0]) { // an engine was selected from the combo box
868         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
869         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
870         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
871         ParseArgsFromString(buf);
872         SwapEngines(i);
873         ReplaceEngine(cps, i);
874         return;
875     }
876     p = engineName;
877     while(q = strchr(p, SLASH)) p = q+1;
878     if(*p== NULLCHAR) return;
879     appData.chessProgram[i] = strdup(p);
880     if(engineDir[0] != NULLCHAR)
881         appData.directory[i] = engineDir;
882     else if(p != engineName) { // derive directory from engine path, when not given
883         p[-1] = 0;
884         appData.directory[i] = strdup(engineName);
885         p[-1] = '/';
886     } else appData.directory[i] = ".";
887     appData.isUCI[i] = isUCI;
888     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
889     appData.hasOwnBookUCI[i] = hasBook;
890     if(addToList) {
891         int len;
892         q = firstChessProgramNames;
893         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
894         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
895                         v1 ? " -firstProtocolVersion 1" : "",
896                         hasBook ? "" : " -fNoOwnBookUCI",
897                         isUCI ? " -fUCI" : "",
898                         storeVariant ? " -variant " : "",
899                         storeVariant ? VariantName(gameInfo.variant) : "");
900 fprintf(debugFP, "new line: %s", buf);
901         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
902         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
903         if(q)   free(q);
904     }
905     ReplaceEngine(cps, i);
906 }
907
908 void
909 InitBackEnd1()
910 {
911     int matched, min, sec;
912
913     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
914     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
915
916     GetTimeMark(&programStartTime);
917     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
918     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
919
920     ClearProgramStats();
921     programStats.ok_to_send = 1;
922     programStats.seen_stat = 0;
923
924     /*
925      * Initialize game list
926      */
927     ListNew(&gameList);
928
929
930     /*
931      * Internet chess server status
932      */
933     if (appData.icsActive) {
934         appData.matchMode = FALSE;
935         appData.matchGames = 0;
936 #if ZIPPY
937         appData.noChessProgram = !appData.zippyPlay;
938 #else
939         appData.zippyPlay = FALSE;
940         appData.zippyTalk = FALSE;
941         appData.noChessProgram = TRUE;
942 #endif
943         if (*appData.icsHelper != NULLCHAR) {
944             appData.useTelnet = TRUE;
945             appData.telnetProgram = appData.icsHelper;
946         }
947     } else {
948         appData.zippyTalk = appData.zippyPlay = FALSE;
949     }
950
951     /* [AS] Initialize pv info list [HGM] and game state */
952     {
953         int i, j;
954
955         for( i=0; i<=framePtr; i++ ) {
956             pvInfoList[i].depth = -1;
957             boards[i][EP_STATUS] = EP_NONE;
958             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
959         }
960     }
961
962     /*
963      * Parse timeControl resource
964      */
965     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
966                           appData.movesPerSession)) {
967         char buf[MSG_SIZ];
968         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
969         DisplayFatalError(buf, 0, 2);
970     }
971
972     /*
973      * Parse searchTime resource
974      */
975     if (*appData.searchTime != NULLCHAR) {
976         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
977         if (matched == 1) {
978             searchTime = min * 60;
979         } else if (matched == 2) {
980             searchTime = min * 60 + sec;
981         } else {
982             char buf[MSG_SIZ];
983             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
984             DisplayFatalError(buf, 0, 2);
985         }
986     }
987
988     /* [AS] Adjudication threshold */
989     adjudicateLossThreshold = appData.adjudicateLossThreshold;
990
991     InitEngine(&first, 0);
992     InitEngine(&second, 1);
993     CommonEngineInit();
994
995     if (appData.icsActive) {
996         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
997     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
998         appData.clockMode = FALSE;
999         first.sendTime = second.sendTime = 0;
1000     }
1001
1002 #if ZIPPY
1003     /* Override some settings from environment variables, for backward
1004        compatibility.  Unfortunately it's not feasible to have the env
1005        vars just set defaults, at least in xboard.  Ugh.
1006     */
1007     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1008       ZippyInit();
1009     }
1010 #endif
1011
1012     if (!appData.icsActive) {
1013       char buf[MSG_SIZ];
1014       int len;
1015
1016       /* Check for variants that are supported only in ICS mode,
1017          or not at all.  Some that are accepted here nevertheless
1018          have bugs; see comments below.
1019       */
1020       VariantClass variant = StringToVariant(appData.variant);
1021       switch (variant) {
1022       case VariantBughouse:     /* need four players and two boards */
1023       case VariantKriegspiel:   /* need to hide pieces and move details */
1024         /* case VariantFischeRandom: (Fabien: moved below) */
1025         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1026         if( (len > MSG_SIZ) && appData.debugMode )
1027           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1028
1029         DisplayFatalError(buf, 0, 2);
1030         return;
1031
1032       case VariantUnknown:
1033       case VariantLoadable:
1034       case Variant29:
1035       case Variant30:
1036       case Variant31:
1037       case Variant32:
1038       case Variant33:
1039       case Variant34:
1040       case Variant35:
1041       case Variant36:
1042       default:
1043         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1044         if( (len > MSG_SIZ) && appData.debugMode )
1045           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1046
1047         DisplayFatalError(buf, 0, 2);
1048         return;
1049
1050       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1051       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1052       case VariantGothic:     /* [HGM] should work */
1053       case VariantCapablanca: /* [HGM] should work */
1054       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1055       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1056       case VariantKnightmate: /* [HGM] should work */
1057       case VariantCylinder:   /* [HGM] untested */
1058       case VariantFalcon:     /* [HGM] untested */
1059       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1060                                  offboard interposition not understood */
1061       case VariantNormal:     /* definitely works! */
1062       case VariantWildCastle: /* pieces not automatically shuffled */
1063       case VariantNoCastle:   /* pieces not automatically shuffled */
1064       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1065       case VariantLosers:     /* should work except for win condition,
1066                                  and doesn't know captures are mandatory */
1067       case VariantSuicide:    /* should work except for win condition,
1068                                  and doesn't know captures are mandatory */
1069       case VariantGiveaway:   /* should work except for win condition,
1070                                  and doesn't know captures are mandatory */
1071       case VariantTwoKings:   /* should work */
1072       case VariantAtomic:     /* should work except for win condition */
1073       case Variant3Check:     /* should work except for win condition */
1074       case VariantShatranj:   /* should work except for all win conditions */
1075       case VariantMakruk:     /* should work except for daw countdown */
1076       case VariantBerolina:   /* might work if TestLegality is off */
1077       case VariantCapaRandom: /* should work */
1078       case VariantJanus:      /* should work */
1079       case VariantSuper:      /* experimental */
1080       case VariantGreat:      /* experimental, requires legality testing to be off */
1081       case VariantSChess:     /* S-Chess, should work */
1082       case VariantSpartan:    /* should work */
1083         break;
1084       }
1085     }
1086
1087 }
1088
1089 int NextIntegerFromString( char ** str, long * value )
1090 {
1091     int result = -1;
1092     char * s = *str;
1093
1094     while( *s == ' ' || *s == '\t' ) {
1095         s++;
1096     }
1097
1098     *value = 0;
1099
1100     if( *s >= '0' && *s <= '9' ) {
1101         while( *s >= '0' && *s <= '9' ) {
1102             *value = *value * 10 + (*s - '0');
1103             s++;
1104         }
1105
1106         result = 0;
1107     }
1108
1109     *str = s;
1110
1111     return result;
1112 }
1113
1114 int NextTimeControlFromString( char ** str, long * value )
1115 {
1116     long temp;
1117     int result = NextIntegerFromString( str, &temp );
1118
1119     if( result == 0 ) {
1120         *value = temp * 60; /* Minutes */
1121         if( **str == ':' ) {
1122             (*str)++;
1123             result = NextIntegerFromString( str, &temp );
1124             *value += temp; /* Seconds */
1125         }
1126     }
1127
1128     return result;
1129 }
1130
1131 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1132 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1133     int result = -1, type = 0; long temp, temp2;
1134
1135     if(**str != ':') return -1; // old params remain in force!
1136     (*str)++;
1137     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1138     if( NextIntegerFromString( str, &temp ) ) return -1;
1139     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1140
1141     if(**str != '/') {
1142         /* time only: incremental or sudden-death time control */
1143         if(**str == '+') { /* increment follows; read it */
1144             (*str)++;
1145             if(**str == '!') type = *(*str)++; // Bronstein TC
1146             if(result = NextIntegerFromString( str, &temp2)) return -1;
1147             *inc = temp2 * 1000;
1148             if(**str == '.') { // read fraction of increment
1149                 char *start = ++(*str);
1150                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1151                 temp2 *= 1000;
1152                 while(start++ < *str) temp2 /= 10;
1153                 *inc += temp2;
1154             }
1155         } else *inc = 0;
1156         *moves = 0; *tc = temp * 1000; *incType = type;
1157         return 0;
1158     }
1159
1160     (*str)++; /* classical time control */
1161     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1162
1163     if(result == 0) {
1164         *moves = temp;
1165         *tc    = temp2 * 1000;
1166         *inc   = 0;
1167         *incType = type;
1168     }
1169     return result;
1170 }
1171
1172 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1173 {   /* [HGM] get time to add from the multi-session time-control string */
1174     int incType, moves=1; /* kludge to force reading of first session */
1175     long time, increment;
1176     char *s = tcString;
1177
1178     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1179     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1180     do {
1181         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1182         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1183         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1184         if(movenr == -1) return time;    /* last move before new session     */
1185         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1186         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1187         if(!moves) return increment;     /* current session is incremental   */
1188         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1189     } while(movenr >= -1);               /* try again for next session       */
1190
1191     return 0; // no new time quota on this move
1192 }
1193
1194 int
1195 ParseTimeControl(tc, ti, mps)
1196      char *tc;
1197      float ti;
1198      int mps;
1199 {
1200   long tc1;
1201   long tc2;
1202   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1203   int min, sec=0;
1204
1205   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1206   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1207       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1208   if(ti > 0) {
1209
1210     if(mps)
1211       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1212     else 
1213       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1214   } else {
1215     if(mps)
1216       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1217     else 
1218       snprintf(buf, MSG_SIZ, ":%s", mytc);
1219   }
1220   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1221   
1222   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1223     return FALSE;
1224   }
1225
1226   if( *tc == '/' ) {
1227     /* Parse second time control */
1228     tc++;
1229
1230     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1231       return FALSE;
1232     }
1233
1234     if( tc2 == 0 ) {
1235       return FALSE;
1236     }
1237
1238     timeControl_2 = tc2 * 1000;
1239   }
1240   else {
1241     timeControl_2 = 0;
1242   }
1243
1244   if( tc1 == 0 ) {
1245     return FALSE;
1246   }
1247
1248   timeControl = tc1 * 1000;
1249
1250   if (ti >= 0) {
1251     timeIncrement = ti * 1000;  /* convert to ms */
1252     movesPerSession = 0;
1253   } else {
1254     timeIncrement = 0;
1255     movesPerSession = mps;
1256   }
1257   return TRUE;
1258 }
1259
1260 void
1261 InitBackEnd2()
1262 {
1263     if (appData.debugMode) {
1264         fprintf(debugFP, "%s\n", programVersion);
1265     }
1266
1267     set_cont_sequence(appData.wrapContSeq);
1268     if (appData.matchGames > 0) {
1269         appData.matchMode = TRUE;
1270     } else if (appData.matchMode) {
1271         appData.matchGames = 1;
1272     }
1273     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1274         appData.matchGames = appData.sameColorGames;
1275     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1276         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1277         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1278     }
1279     Reset(TRUE, FALSE);
1280     if (appData.noChessProgram || first.protocolVersion == 1) {
1281       InitBackEnd3();
1282     } else {
1283       /* kludge: allow timeout for initial "feature" commands */
1284       FreezeUI();
1285       DisplayMessage("", _("Starting chess program"));
1286       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1287     }
1288 }
1289
1290 int
1291 CalculateIndex(int index, int gameNr)
1292 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1293     int res;
1294     if(index > 0) return index; // fixed nmber
1295     if(index == 0) return 1;
1296     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1297     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1298     return res;
1299 }
1300
1301 int
1302 LoadGameOrPosition(int gameNr)
1303 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1304     if (*appData.loadGameFile != NULLCHAR) {
1305         if (!LoadGameFromFile(appData.loadGameFile,
1306                 CalculateIndex(appData.loadGameIndex, gameNr),
1307                               appData.loadGameFile, FALSE)) {
1308             DisplayFatalError(_("Bad game file"), 0, 1);
1309             return 0;
1310         }
1311     } else if (*appData.loadPositionFile != NULLCHAR) {
1312         if (!LoadPositionFromFile(appData.loadPositionFile,
1313                 CalculateIndex(appData.loadPositionIndex, gameNr),
1314                                   appData.loadPositionFile)) {
1315             DisplayFatalError(_("Bad position file"), 0, 1);
1316             return 0;
1317         }
1318     }
1319     return 1;
1320 }
1321
1322 void
1323 ReserveGame(int gameNr, char resChar)
1324 {
1325     FILE *tf = fopen(appData.tourneyFile, "r+");
1326     char *p, *q, c, buf[MSG_SIZ];
1327     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1328     safeStrCpy(buf, lastMsg, MSG_SIZ);
1329     DisplayMessage(_("Pick new game"), "");
1330     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1331     ParseArgsFromFile(tf);
1332     p = q = appData.results;
1333     if(appData.debugMode) {
1334       char *r = appData.participants;
1335       fprintf(debugFP, "results = '%s'\n", p);
1336       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1337       fprintf(debugFP, "\n");
1338     }
1339     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1340     nextGame = q - p;
1341     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1342     safeStrCpy(q, p, strlen(p) + 2);
1343     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1344     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1345     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1346         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1347         q[nextGame] = '*';
1348     }
1349     fseek(tf, -(strlen(p)+4), SEEK_END);
1350     c = fgetc(tf);
1351     if(c != '"') // depending on DOS or Unix line endings we can be one off
1352          fseek(tf, -(strlen(p)+2), SEEK_END);
1353     else fseek(tf, -(strlen(p)+3), SEEK_END);
1354     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1355     DisplayMessage(buf, "");
1356     free(p); appData.results = q;
1357     if(nextGame <= appData.matchGames && resChar != ' ' &&
1358        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1359         UnloadEngine(&first);  // next game belongs to other pairing;
1360         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1361     }
1362 }
1363
1364 void
1365 MatchEvent(int mode)
1366 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1367         int dummy;
1368         /* Set up machine vs. machine match */
1369         nextGame = 0;
1370         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1371         if(appData.tourneyFile[0]) {
1372             ReserveGame(-1, 0);
1373             if(nextGame > appData.matchGames) {
1374                 char buf[MSG_SIZ];
1375                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1376                 DisplayError(buf, 0);
1377                 appData.tourneyFile[0] = 0;
1378                 return;
1379             }
1380         } else
1381         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1382             DisplayFatalError(_("Can't have a match with no chess programs"),
1383                               0, 2);
1384             return;
1385         }
1386         matchMode = mode;
1387         matchGame = roundNr = 1;
1388         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1389         NextMatchGame();
1390 }
1391
1392 void
1393 InitBackEnd3 P((void))
1394 {
1395     GameMode initialMode;
1396     char buf[MSG_SIZ];
1397     int err, len;
1398
1399     InitChessProgram(&first, startedFromSetupPosition);
1400
1401     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1402         free(programVersion);
1403         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1404         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1405     }
1406
1407     if (appData.icsActive) {
1408 #ifdef WIN32
1409         /* [DM] Make a console window if needed [HGM] merged ifs */
1410         ConsoleCreate();
1411 #endif
1412         err = establish();
1413         if (err != 0)
1414           {
1415             if (*appData.icsCommPort != NULLCHAR)
1416               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1417                              appData.icsCommPort);
1418             else
1419               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1420                         appData.icsHost, appData.icsPort);
1421
1422             if( (len > MSG_SIZ) && appData.debugMode )
1423               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1424
1425             DisplayFatalError(buf, err, 1);
1426             return;
1427         }
1428         SetICSMode();
1429         telnetISR =
1430           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1431         fromUserISR =
1432           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1433         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1434             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435     } else if (appData.noChessProgram) {
1436         SetNCPMode();
1437     } else {
1438         SetGNUMode();
1439     }
1440
1441     if (*appData.cmailGameName != NULLCHAR) {
1442         SetCmailMode();
1443         OpenLoopback(&cmailPR);
1444         cmailISR =
1445           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1446     }
1447
1448     ThawUI();
1449     DisplayMessage("", "");
1450     if (StrCaseCmp(appData.initialMode, "") == 0) {
1451       initialMode = BeginningOfGame;
1452       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1453         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1454         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1455         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1456         ModeHighlight();
1457       }
1458     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1459       initialMode = TwoMachinesPlay;
1460     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1461       initialMode = AnalyzeFile;
1462     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1463       initialMode = AnalyzeMode;
1464     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1465       initialMode = MachinePlaysWhite;
1466     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1467       initialMode = MachinePlaysBlack;
1468     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1469       initialMode = EditGame;
1470     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1471       initialMode = EditPosition;
1472     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1473       initialMode = Training;
1474     } else {
1475       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1476       if( (len > MSG_SIZ) && appData.debugMode )
1477         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1478
1479       DisplayFatalError(buf, 0, 2);
1480       return;
1481     }
1482
1483     if (appData.matchMode) {
1484         if(appData.tourneyFile[0]) { // start tourney from command line
1485             FILE *f;
1486             if(f = fopen(appData.tourneyFile, "r")) {
1487                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1488                 fclose(f);
1489             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1490         }
1491         MatchEvent(TRUE);
1492     } else if (*appData.cmailGameName != NULLCHAR) {
1493         /* Set up cmail mode */
1494         ReloadCmailMsgEvent(TRUE);
1495     } else {
1496         /* Set up other modes */
1497         if (initialMode == AnalyzeFile) {
1498           if (*appData.loadGameFile == NULLCHAR) {
1499             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1500             return;
1501           }
1502         }
1503         if (*appData.loadGameFile != NULLCHAR) {
1504             (void) LoadGameFromFile(appData.loadGameFile,
1505                                     appData.loadGameIndex,
1506                                     appData.loadGameFile, TRUE);
1507         } else if (*appData.loadPositionFile != NULLCHAR) {
1508             (void) LoadPositionFromFile(appData.loadPositionFile,
1509                                         appData.loadPositionIndex,
1510                                         appData.loadPositionFile);
1511             /* [HGM] try to make self-starting even after FEN load */
1512             /* to allow automatic setup of fairy variants with wtm */
1513             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1514                 gameMode = BeginningOfGame;
1515                 setboardSpoiledMachineBlack = 1;
1516             }
1517             /* [HGM] loadPos: make that every new game uses the setup */
1518             /* from file as long as we do not switch variant          */
1519             if(!blackPlaysFirst) {
1520                 startedFromPositionFile = TRUE;
1521                 CopyBoard(filePosition, boards[0]);
1522             }
1523         }
1524         if (initialMode == AnalyzeMode) {
1525           if (appData.noChessProgram) {
1526             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1527             return;
1528           }
1529           if (appData.icsActive) {
1530             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1531             return;
1532           }
1533           AnalyzeModeEvent();
1534         } else if (initialMode == AnalyzeFile) {
1535           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1536           ShowThinkingEvent();
1537           AnalyzeFileEvent();
1538           AnalysisPeriodicEvent(1);
1539         } else if (initialMode == MachinePlaysWhite) {
1540           if (appData.noChessProgram) {
1541             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1542                               0, 2);
1543             return;
1544           }
1545           if (appData.icsActive) {
1546             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1547                               0, 2);
1548             return;
1549           }
1550           MachineWhiteEvent();
1551         } else if (initialMode == MachinePlaysBlack) {
1552           if (appData.noChessProgram) {
1553             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1554                               0, 2);
1555             return;
1556           }
1557           if (appData.icsActive) {
1558             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1559                               0, 2);
1560             return;
1561           }
1562           MachineBlackEvent();
1563         } else if (initialMode == TwoMachinesPlay) {
1564           if (appData.noChessProgram) {
1565             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1566                               0, 2);
1567             return;
1568           }
1569           if (appData.icsActive) {
1570             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1571                               0, 2);
1572             return;
1573           }
1574           TwoMachinesEvent();
1575         } else if (initialMode == EditGame) {
1576           EditGameEvent();
1577         } else if (initialMode == EditPosition) {
1578           EditPositionEvent();
1579         } else if (initialMode == Training) {
1580           if (*appData.loadGameFile == NULLCHAR) {
1581             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1582             return;
1583           }
1584           TrainingEvent();
1585         }
1586     }
1587 }
1588
1589 /*
1590  * Establish will establish a contact to a remote host.port.
1591  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1592  *  used to talk to the host.
1593  * Returns 0 if okay, error code if not.
1594  */
1595 int
1596 establish()
1597 {
1598     char buf[MSG_SIZ];
1599
1600     if (*appData.icsCommPort != NULLCHAR) {
1601         /* Talk to the host through a serial comm port */
1602         return OpenCommPort(appData.icsCommPort, &icsPR);
1603
1604     } else if (*appData.gateway != NULLCHAR) {
1605         if (*appData.remoteShell == NULLCHAR) {
1606             /* Use the rcmd protocol to run telnet program on a gateway host */
1607             snprintf(buf, sizeof(buf), "%s %s %s",
1608                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1609             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1610
1611         } else {
1612             /* Use the rsh program to run telnet program on a gateway host */
1613             if (*appData.remoteUser == NULLCHAR) {
1614                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1615                         appData.gateway, appData.telnetProgram,
1616                         appData.icsHost, appData.icsPort);
1617             } else {
1618                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1619                         appData.remoteShell, appData.gateway,
1620                         appData.remoteUser, appData.telnetProgram,
1621                         appData.icsHost, appData.icsPort);
1622             }
1623             return StartChildProcess(buf, "", &icsPR);
1624
1625         }
1626     } else if (appData.useTelnet) {
1627         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1628
1629     } else {
1630         /* TCP socket interface differs somewhat between
1631            Unix and NT; handle details in the front end.
1632            */
1633         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1634     }
1635 }
1636
1637 void EscapeExpand(char *p, char *q)
1638 {       // [HGM] initstring: routine to shape up string arguments
1639         while(*p++ = *q++) if(p[-1] == '\\')
1640             switch(*q++) {
1641                 case 'n': p[-1] = '\n'; break;
1642                 case 'r': p[-1] = '\r'; break;
1643                 case 't': p[-1] = '\t'; break;
1644                 case '\\': p[-1] = '\\'; break;
1645                 case 0: *p = 0; return;
1646                 default: p[-1] = q[-1]; break;
1647             }
1648 }
1649
1650 void
1651 show_bytes(fp, buf, count)
1652      FILE *fp;
1653      char *buf;
1654      int count;
1655 {
1656     while (count--) {
1657         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1658             fprintf(fp, "\\%03o", *buf & 0xff);
1659         } else {
1660             putc(*buf, fp);
1661         }
1662         buf++;
1663     }
1664     fflush(fp);
1665 }
1666
1667 /* Returns an errno value */
1668 int
1669 OutputMaybeTelnet(pr, message, count, outError)
1670      ProcRef pr;
1671      char *message;
1672      int count;
1673      int *outError;
1674 {
1675     char buf[8192], *p, *q, *buflim;
1676     int left, newcount, outcount;
1677
1678     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1679         *appData.gateway != NULLCHAR) {
1680         if (appData.debugMode) {
1681             fprintf(debugFP, ">ICS: ");
1682             show_bytes(debugFP, message, count);
1683             fprintf(debugFP, "\n");
1684         }
1685         return OutputToProcess(pr, message, count, outError);
1686     }
1687
1688     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1689     p = message;
1690     q = buf;
1691     left = count;
1692     newcount = 0;
1693     while (left) {
1694         if (q >= buflim) {
1695             if (appData.debugMode) {
1696                 fprintf(debugFP, ">ICS: ");
1697                 show_bytes(debugFP, buf, newcount);
1698                 fprintf(debugFP, "\n");
1699             }
1700             outcount = OutputToProcess(pr, buf, newcount, outError);
1701             if (outcount < newcount) return -1; /* to be sure */
1702             q = buf;
1703             newcount = 0;
1704         }
1705         if (*p == '\n') {
1706             *q++ = '\r';
1707             newcount++;
1708         } else if (((unsigned char) *p) == TN_IAC) {
1709             *q++ = (char) TN_IAC;
1710             newcount ++;
1711         }
1712         *q++ = *p++;
1713         newcount++;
1714         left--;
1715     }
1716     if (appData.debugMode) {
1717         fprintf(debugFP, ">ICS: ");
1718         show_bytes(debugFP, buf, newcount);
1719         fprintf(debugFP, "\n");
1720     }
1721     outcount = OutputToProcess(pr, buf, newcount, outError);
1722     if (outcount < newcount) return -1; /* to be sure */
1723     return count;
1724 }
1725
1726 void
1727 read_from_player(isr, closure, message, count, error)
1728      InputSourceRef isr;
1729      VOIDSTAR closure;
1730      char *message;
1731      int count;
1732      int error;
1733 {
1734     int outError, outCount;
1735     static int gotEof = 0;
1736
1737     /* Pass data read from player on to ICS */
1738     if (count > 0) {
1739         gotEof = 0;
1740         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1741         if (outCount < count) {
1742             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1743         }
1744     } else if (count < 0) {
1745         RemoveInputSource(isr);
1746         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1747     } else if (gotEof++ > 0) {
1748         RemoveInputSource(isr);
1749         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1750     }
1751 }
1752
1753 void
1754 KeepAlive()
1755 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1756     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1757     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1758     SendToICS("date\n");
1759     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1760 }
1761
1762 /* added routine for printf style output to ics */
1763 void ics_printf(char *format, ...)
1764 {
1765     char buffer[MSG_SIZ];
1766     va_list args;
1767
1768     va_start(args, format);
1769     vsnprintf(buffer, sizeof(buffer), format, args);
1770     buffer[sizeof(buffer)-1] = '\0';
1771     SendToICS(buffer);
1772     va_end(args);
1773 }
1774
1775 void
1776 SendToICS(s)
1777      char *s;
1778 {
1779     int count, outCount, outError;
1780
1781     if (icsPR == NULL) return;
1782
1783     count = strlen(s);
1784     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1785     if (outCount < count) {
1786         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1787     }
1788 }
1789
1790 /* This is used for sending logon scripts to the ICS. Sending
1791    without a delay causes problems when using timestamp on ICC
1792    (at least on my machine). */
1793 void
1794 SendToICSDelayed(s,msdelay)
1795      char *s;
1796      long msdelay;
1797 {
1798     int count, outCount, outError;
1799
1800     if (icsPR == NULL) return;
1801
1802     count = strlen(s);
1803     if (appData.debugMode) {
1804         fprintf(debugFP, ">ICS: ");
1805         show_bytes(debugFP, s, count);
1806         fprintf(debugFP, "\n");
1807     }
1808     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1809                                       msdelay);
1810     if (outCount < count) {
1811         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812     }
1813 }
1814
1815
1816 /* Remove all highlighting escape sequences in s
1817    Also deletes any suffix starting with '('
1818    */
1819 char *
1820 StripHighlightAndTitle(s)
1821      char *s;
1822 {
1823     static char retbuf[MSG_SIZ];
1824     char *p = retbuf;
1825
1826     while (*s != NULLCHAR) {
1827         while (*s == '\033') {
1828             while (*s != NULLCHAR && !isalpha(*s)) s++;
1829             if (*s != NULLCHAR) s++;
1830         }
1831         while (*s != NULLCHAR && *s != '\033') {
1832             if (*s == '(' || *s == '[') {
1833                 *p = NULLCHAR;
1834                 return retbuf;
1835             }
1836             *p++ = *s++;
1837         }
1838     }
1839     *p = NULLCHAR;
1840     return retbuf;
1841 }
1842
1843 /* Remove all highlighting escape sequences in s */
1844 char *
1845 StripHighlight(s)
1846      char *s;
1847 {
1848     static char retbuf[MSG_SIZ];
1849     char *p = retbuf;
1850
1851     while (*s != NULLCHAR) {
1852         while (*s == '\033') {
1853             while (*s != NULLCHAR && !isalpha(*s)) s++;
1854             if (*s != NULLCHAR) s++;
1855         }
1856         while (*s != NULLCHAR && *s != '\033') {
1857             *p++ = *s++;
1858         }
1859     }
1860     *p = NULLCHAR;
1861     return retbuf;
1862 }
1863
1864 char *variantNames[] = VARIANT_NAMES;
1865 char *
1866 VariantName(v)
1867      VariantClass v;
1868 {
1869     return variantNames[v];
1870 }
1871
1872
1873 /* Identify a variant from the strings the chess servers use or the
1874    PGN Variant tag names we use. */
1875 VariantClass
1876 StringToVariant(e)
1877      char *e;
1878 {
1879     char *p;
1880     int wnum = -1;
1881     VariantClass v = VariantNormal;
1882     int i, found = FALSE;
1883     char buf[MSG_SIZ];
1884     int len;
1885
1886     if (!e) return v;
1887
1888     /* [HGM] skip over optional board-size prefixes */
1889     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1890         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1891         while( *e++ != '_');
1892     }
1893
1894     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1895         v = VariantNormal;
1896         found = TRUE;
1897     } else
1898     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1899       if (StrCaseStr(e, variantNames[i])) {
1900         v = (VariantClass) i;
1901         found = TRUE;
1902         break;
1903       }
1904     }
1905
1906     if (!found) {
1907       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1908           || StrCaseStr(e, "wild/fr")
1909           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1910         v = VariantFischeRandom;
1911       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1912                  (i = 1, p = StrCaseStr(e, "w"))) {
1913         p += i;
1914         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1915         if (isdigit(*p)) {
1916           wnum = atoi(p);
1917         } else {
1918           wnum = -1;
1919         }
1920         switch (wnum) {
1921         case 0: /* FICS only, actually */
1922         case 1:
1923           /* Castling legal even if K starts on d-file */
1924           v = VariantWildCastle;
1925           break;
1926         case 2:
1927         case 3:
1928         case 4:
1929           /* Castling illegal even if K & R happen to start in
1930              normal positions. */
1931           v = VariantNoCastle;
1932           break;
1933         case 5:
1934         case 7:
1935         case 8:
1936         case 10:
1937         case 11:
1938         case 12:
1939         case 13:
1940         case 14:
1941         case 15:
1942         case 18:
1943         case 19:
1944           /* Castling legal iff K & R start in normal positions */
1945           v = VariantNormal;
1946           break;
1947         case 6:
1948         case 20:
1949         case 21:
1950           /* Special wilds for position setup; unclear what to do here */
1951           v = VariantLoadable;
1952           break;
1953         case 9:
1954           /* Bizarre ICC game */
1955           v = VariantTwoKings;
1956           break;
1957         case 16:
1958           v = VariantKriegspiel;
1959           break;
1960         case 17:
1961           v = VariantLosers;
1962           break;
1963         case 22:
1964           v = VariantFischeRandom;
1965           break;
1966         case 23:
1967           v = VariantCrazyhouse;
1968           break;
1969         case 24:
1970           v = VariantBughouse;
1971           break;
1972         case 25:
1973           v = Variant3Check;
1974           break;
1975         case 26:
1976           /* Not quite the same as FICS suicide! */
1977           v = VariantGiveaway;
1978           break;
1979         case 27:
1980           v = VariantAtomic;
1981           break;
1982         case 28:
1983           v = VariantShatranj;
1984           break;
1985
1986         /* Temporary names for future ICC types.  The name *will* change in
1987            the next xboard/WinBoard release after ICC defines it. */
1988         case 29:
1989           v = Variant29;
1990           break;
1991         case 30:
1992           v = Variant30;
1993           break;
1994         case 31:
1995           v = Variant31;
1996           break;
1997         case 32:
1998           v = Variant32;
1999           break;
2000         case 33:
2001           v = Variant33;
2002           break;
2003         case 34:
2004           v = Variant34;
2005           break;
2006         case 35:
2007           v = Variant35;
2008           break;
2009         case 36:
2010           v = Variant36;
2011           break;
2012         case 37:
2013           v = VariantShogi;
2014           break;
2015         case 38:
2016           v = VariantXiangqi;
2017           break;
2018         case 39:
2019           v = VariantCourier;
2020           break;
2021         case 40:
2022           v = VariantGothic;
2023           break;
2024         case 41:
2025           v = VariantCapablanca;
2026           break;
2027         case 42:
2028           v = VariantKnightmate;
2029           break;
2030         case 43:
2031           v = VariantFairy;
2032           break;
2033         case 44:
2034           v = VariantCylinder;
2035           break;
2036         case 45:
2037           v = VariantFalcon;
2038           break;
2039         case 46:
2040           v = VariantCapaRandom;
2041           break;
2042         case 47:
2043           v = VariantBerolina;
2044           break;
2045         case 48:
2046           v = VariantJanus;
2047           break;
2048         case 49:
2049           v = VariantSuper;
2050           break;
2051         case 50:
2052           v = VariantGreat;
2053           break;
2054         case -1:
2055           /* Found "wild" or "w" in the string but no number;
2056              must assume it's normal chess. */
2057           v = VariantNormal;
2058           break;
2059         default:
2060           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2061           if( (len > MSG_SIZ) && appData.debugMode )
2062             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2063
2064           DisplayError(buf, 0);
2065           v = VariantUnknown;
2066           break;
2067         }
2068       }
2069     }
2070     if (appData.debugMode) {
2071       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2072               e, wnum, VariantName(v));
2073     }
2074     return v;
2075 }
2076
2077 static int leftover_start = 0, leftover_len = 0;
2078 char star_match[STAR_MATCH_N][MSG_SIZ];
2079
2080 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2081    advance *index beyond it, and set leftover_start to the new value of
2082    *index; else return FALSE.  If pattern contains the character '*', it
2083    matches any sequence of characters not containing '\r', '\n', or the
2084    character following the '*' (if any), and the matched sequence(s) are
2085    copied into star_match.
2086    */
2087 int
2088 looking_at(buf, index, pattern)
2089      char *buf;
2090      int *index;
2091      char *pattern;
2092 {
2093     char *bufp = &buf[*index], *patternp = pattern;
2094     int star_count = 0;
2095     char *matchp = star_match[0];
2096
2097     for (;;) {
2098         if (*patternp == NULLCHAR) {
2099             *index = leftover_start = bufp - buf;
2100             *matchp = NULLCHAR;
2101             return TRUE;
2102         }
2103         if (*bufp == NULLCHAR) return FALSE;
2104         if (*patternp == '*') {
2105             if (*bufp == *(patternp + 1)) {
2106                 *matchp = NULLCHAR;
2107                 matchp = star_match[++star_count];
2108                 patternp += 2;
2109                 bufp++;
2110                 continue;
2111             } else if (*bufp == '\n' || *bufp == '\r') {
2112                 patternp++;
2113                 if (*patternp == NULLCHAR)
2114                   continue;
2115                 else
2116                   return FALSE;
2117             } else {
2118                 *matchp++ = *bufp++;
2119                 continue;
2120             }
2121         }
2122         if (*patternp != *bufp) return FALSE;
2123         patternp++;
2124         bufp++;
2125     }
2126 }
2127
2128 void
2129 SendToPlayer(data, length)
2130      char *data;
2131      int length;
2132 {
2133     int error, outCount;
2134     outCount = OutputToProcess(NoProc, data, length, &error);
2135     if (outCount < length) {
2136         DisplayFatalError(_("Error writing to display"), error, 1);
2137     }
2138 }
2139
2140 void
2141 PackHolding(packed, holding)
2142      char packed[];
2143      char *holding;
2144 {
2145     char *p = holding;
2146     char *q = packed;
2147     int runlength = 0;
2148     int curr = 9999;
2149     do {
2150         if (*p == curr) {
2151             runlength++;
2152         } else {
2153             switch (runlength) {
2154               case 0:
2155                 break;
2156               case 1:
2157                 *q++ = curr;
2158                 break;
2159               case 2:
2160                 *q++ = curr;
2161                 *q++ = curr;
2162                 break;
2163               default:
2164                 sprintf(q, "%d", runlength);
2165                 while (*q) q++;
2166                 *q++ = curr;
2167                 break;
2168             }
2169             runlength = 1;
2170             curr = *p;
2171         }
2172     } while (*p++);
2173     *q = NULLCHAR;
2174 }
2175
2176 /* Telnet protocol requests from the front end */
2177 void
2178 TelnetRequest(ddww, option)
2179      unsigned char ddww, option;
2180 {
2181     unsigned char msg[3];
2182     int outCount, outError;
2183
2184     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2185
2186     if (appData.debugMode) {
2187         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2188         switch (ddww) {
2189           case TN_DO:
2190             ddwwStr = "DO";
2191             break;
2192           case TN_DONT:
2193             ddwwStr = "DONT";
2194             break;
2195           case TN_WILL:
2196             ddwwStr = "WILL";
2197             break;
2198           case TN_WONT:
2199             ddwwStr = "WONT";
2200             break;
2201           default:
2202             ddwwStr = buf1;
2203             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2204             break;
2205         }
2206         switch (option) {
2207           case TN_ECHO:
2208             optionStr = "ECHO";
2209             break;
2210           default:
2211             optionStr = buf2;
2212             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2213             break;
2214         }
2215         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2216     }
2217     msg[0] = TN_IAC;
2218     msg[1] = ddww;
2219     msg[2] = option;
2220     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2221     if (outCount < 3) {
2222         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2223     }
2224 }
2225
2226 void
2227 DoEcho()
2228 {
2229     if (!appData.icsActive) return;
2230     TelnetRequest(TN_DO, TN_ECHO);
2231 }
2232
2233 void
2234 DontEcho()
2235 {
2236     if (!appData.icsActive) return;
2237     TelnetRequest(TN_DONT, TN_ECHO);
2238 }
2239
2240 void
2241 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2242 {
2243     /* put the holdings sent to us by the server on the board holdings area */
2244     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2245     char p;
2246     ChessSquare piece;
2247
2248     if(gameInfo.holdingsWidth < 2)  return;
2249     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2250         return; // prevent overwriting by pre-board holdings
2251
2252     if( (int)lowestPiece >= BlackPawn ) {
2253         holdingsColumn = 0;
2254         countsColumn = 1;
2255         holdingsStartRow = BOARD_HEIGHT-1;
2256         direction = -1;
2257     } else {
2258         holdingsColumn = BOARD_WIDTH-1;
2259         countsColumn = BOARD_WIDTH-2;
2260         holdingsStartRow = 0;
2261         direction = 1;
2262     }
2263
2264     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2265         board[i][holdingsColumn] = EmptySquare;
2266         board[i][countsColumn]   = (ChessSquare) 0;
2267     }
2268     while( (p=*holdings++) != NULLCHAR ) {
2269         piece = CharToPiece( ToUpper(p) );
2270         if(piece == EmptySquare) continue;
2271         /*j = (int) piece - (int) WhitePawn;*/
2272         j = PieceToNumber(piece);
2273         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2274         if(j < 0) continue;               /* should not happen */
2275         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2276         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2277         board[holdingsStartRow+j*direction][countsColumn]++;
2278     }
2279 }
2280
2281
2282 void
2283 VariantSwitch(Board board, VariantClass newVariant)
2284 {
2285    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2286    static Board oldBoard;
2287
2288    startedFromPositionFile = FALSE;
2289    if(gameInfo.variant == newVariant) return;
2290
2291    /* [HGM] This routine is called each time an assignment is made to
2292     * gameInfo.variant during a game, to make sure the board sizes
2293     * are set to match the new variant. If that means adding or deleting
2294     * holdings, we shift the playing board accordingly
2295     * This kludge is needed because in ICS observe mode, we get boards
2296     * of an ongoing game without knowing the variant, and learn about the
2297     * latter only later. This can be because of the move list we requested,
2298     * in which case the game history is refilled from the beginning anyway,
2299     * but also when receiving holdings of a crazyhouse game. In the latter
2300     * case we want to add those holdings to the already received position.
2301     */
2302
2303
2304    if (appData.debugMode) {
2305      fprintf(debugFP, "Switch board from %s to %s\n",
2306              VariantName(gameInfo.variant), VariantName(newVariant));
2307      setbuf(debugFP, NULL);
2308    }
2309    shuffleOpenings = 0;       /* [HGM] shuffle */
2310    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2311    switch(newVariant)
2312      {
2313      case VariantShogi:
2314        newWidth = 9;  newHeight = 9;
2315        gameInfo.holdingsSize = 7;
2316      case VariantBughouse:
2317      case VariantCrazyhouse:
2318        newHoldingsWidth = 2; break;
2319      case VariantGreat:
2320        newWidth = 10;
2321      case VariantSuper:
2322        newHoldingsWidth = 2;
2323        gameInfo.holdingsSize = 8;
2324        break;
2325      case VariantGothic:
2326      case VariantCapablanca:
2327      case VariantCapaRandom:
2328        newWidth = 10;
2329      default:
2330        newHoldingsWidth = gameInfo.holdingsSize = 0;
2331      };
2332
2333    if(newWidth  != gameInfo.boardWidth  ||
2334       newHeight != gameInfo.boardHeight ||
2335       newHoldingsWidth != gameInfo.holdingsWidth ) {
2336
2337      /* shift position to new playing area, if needed */
2338      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2339        for(i=0; i<BOARD_HEIGHT; i++)
2340          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2341            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2342              board[i][j];
2343        for(i=0; i<newHeight; i++) {
2344          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2345          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2346        }
2347      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2348        for(i=0; i<BOARD_HEIGHT; i++)
2349          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2350            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2351              board[i][j];
2352      }
2353      gameInfo.boardWidth  = newWidth;
2354      gameInfo.boardHeight = newHeight;
2355      gameInfo.holdingsWidth = newHoldingsWidth;
2356      gameInfo.variant = newVariant;
2357      InitDrawingSizes(-2, 0);
2358    } else gameInfo.variant = newVariant;
2359    CopyBoard(oldBoard, board);   // remember correctly formatted board
2360      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2361    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2362 }
2363
2364 static int loggedOn = FALSE;
2365
2366 /*-- Game start info cache: --*/
2367 int gs_gamenum;
2368 char gs_kind[MSG_SIZ];
2369 static char player1Name[128] = "";
2370 static char player2Name[128] = "";
2371 static char cont_seq[] = "\n\\   ";
2372 static int player1Rating = -1;
2373 static int player2Rating = -1;
2374 /*----------------------------*/
2375
2376 ColorClass curColor = ColorNormal;
2377 int suppressKibitz = 0;
2378
2379 // [HGM] seekgraph
2380 Boolean soughtPending = FALSE;
2381 Boolean seekGraphUp;
2382 #define MAX_SEEK_ADS 200
2383 #define SQUARE 0x80
2384 char *seekAdList[MAX_SEEK_ADS];
2385 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2386 float tcList[MAX_SEEK_ADS];
2387 char colorList[MAX_SEEK_ADS];
2388 int nrOfSeekAds = 0;
2389 int minRating = 1010, maxRating = 2800;
2390 int hMargin = 10, vMargin = 20, h, w;
2391 extern int squareSize, lineGap;
2392
2393 void
2394 PlotSeekAd(int i)
2395 {
2396         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2397         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2398         if(r < minRating+100 && r >=0 ) r = minRating+100;
2399         if(r > maxRating) r = maxRating;
2400         if(tc < 1.) tc = 1.;
2401         if(tc > 95.) tc = 95.;
2402         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2403         y = ((double)r - minRating)/(maxRating - minRating)
2404             * (h-vMargin-squareSize/8-1) + vMargin;
2405         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2406         if(strstr(seekAdList[i], " u ")) color = 1;
2407         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2408            !strstr(seekAdList[i], "bullet") &&
2409            !strstr(seekAdList[i], "blitz") &&
2410            !strstr(seekAdList[i], "standard") ) color = 2;
2411         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2412         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2413 }
2414
2415 void
2416 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2417 {
2418         char buf[MSG_SIZ], *ext = "";
2419         VariantClass v = StringToVariant(type);
2420         if(strstr(type, "wild")) {
2421             ext = type + 4; // append wild number
2422             if(v == VariantFischeRandom) type = "chess960"; else
2423             if(v == VariantLoadable) type = "setup"; else
2424             type = VariantName(v);
2425         }
2426         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2427         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2428             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2429             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2430             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2431             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2432             seekNrList[nrOfSeekAds] = nr;
2433             zList[nrOfSeekAds] = 0;
2434             seekAdList[nrOfSeekAds++] = StrSave(buf);
2435             if(plot) PlotSeekAd(nrOfSeekAds-1);
2436         }
2437 }
2438
2439 void
2440 EraseSeekDot(int i)
2441 {
2442     int x = xList[i], y = yList[i], d=squareSize/4, k;
2443     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2444     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2445     // now replot every dot that overlapped
2446     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2447         int xx = xList[k], yy = yList[k];
2448         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2449             DrawSeekDot(xx, yy, colorList[k]);
2450     }
2451 }
2452
2453 void
2454 RemoveSeekAd(int nr)
2455 {
2456         int i;
2457         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2458             EraseSeekDot(i);
2459             if(seekAdList[i]) free(seekAdList[i]);
2460             seekAdList[i] = seekAdList[--nrOfSeekAds];
2461             seekNrList[i] = seekNrList[nrOfSeekAds];
2462             ratingList[i] = ratingList[nrOfSeekAds];
2463             colorList[i]  = colorList[nrOfSeekAds];
2464             tcList[i] = tcList[nrOfSeekAds];
2465             xList[i]  = xList[nrOfSeekAds];
2466             yList[i]  = yList[nrOfSeekAds];
2467             zList[i]  = zList[nrOfSeekAds];
2468             seekAdList[nrOfSeekAds] = NULL;
2469             break;
2470         }
2471 }
2472
2473 Boolean
2474 MatchSoughtLine(char *line)
2475 {
2476     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2477     int nr, base, inc, u=0; char dummy;
2478
2479     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2480        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2481        (u=1) &&
2482        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2483         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2484         // match: compact and save the line
2485         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2486         return TRUE;
2487     }
2488     return FALSE;
2489 }
2490
2491 int
2492 DrawSeekGraph()
2493 {
2494     int i;
2495     if(!seekGraphUp) return FALSE;
2496     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2497     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2498
2499     DrawSeekBackground(0, 0, w, h);
2500     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2501     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2502     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2503         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2504         yy = h-1-yy;
2505         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2506         if(i%500 == 0) {
2507             char buf[MSG_SIZ];
2508             snprintf(buf, MSG_SIZ, "%d", i);
2509             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2510         }
2511     }
2512     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2513     for(i=1; i<100; i+=(i<10?1:5)) {
2514         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2515         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2516         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2517             char buf[MSG_SIZ];
2518             snprintf(buf, MSG_SIZ, "%d", i);
2519             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2520         }
2521     }
2522     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2523     return TRUE;
2524 }
2525
2526 int SeekGraphClick(ClickType click, int x, int y, int moving)
2527 {
2528     static int lastDown = 0, displayed = 0, lastSecond;
2529     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2530         if(click == Release || moving) return FALSE;
2531         nrOfSeekAds = 0;
2532         soughtPending = TRUE;
2533         SendToICS(ics_prefix);
2534         SendToICS("sought\n"); // should this be "sought all"?
2535     } else { // issue challenge based on clicked ad
2536         int dist = 10000; int i, closest = 0, second = 0;
2537         for(i=0; i<nrOfSeekAds; i++) {
2538             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2539             if(d < dist) { dist = d; closest = i; }
2540             second += (d - zList[i] < 120); // count in-range ads
2541             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2542         }
2543         if(dist < 120) {
2544             char buf[MSG_SIZ];
2545             second = (second > 1);
2546             if(displayed != closest || second != lastSecond) {
2547                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2548                 lastSecond = second; displayed = closest;
2549             }
2550             if(click == Press) {
2551                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2552                 lastDown = closest;
2553                 return TRUE;
2554             } // on press 'hit', only show info
2555             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2556             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2557             SendToICS(ics_prefix);
2558             SendToICS(buf);
2559             return TRUE; // let incoming board of started game pop down the graph
2560         } else if(click == Release) { // release 'miss' is ignored
2561             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2562             if(moving == 2) { // right up-click
2563                 nrOfSeekAds = 0; // refresh graph
2564                 soughtPending = TRUE;
2565                 SendToICS(ics_prefix);
2566                 SendToICS("sought\n"); // should this be "sought all"?
2567             }
2568             return TRUE;
2569         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2570         // press miss or release hit 'pop down' seek graph
2571         seekGraphUp = FALSE;
2572         DrawPosition(TRUE, NULL);
2573     }
2574     return TRUE;
2575 }
2576
2577 void
2578 read_from_ics(isr, closure, data, count, error)
2579      InputSourceRef isr;
2580      VOIDSTAR closure;
2581      char *data;
2582      int count;
2583      int error;
2584 {
2585 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2586 #define STARTED_NONE 0
2587 #define STARTED_MOVES 1
2588 #define STARTED_BOARD 2
2589 #define STARTED_OBSERVE 3
2590 #define STARTED_HOLDINGS 4
2591 #define STARTED_CHATTER 5
2592 #define STARTED_COMMENT 6
2593 #define STARTED_MOVES_NOHIDE 7
2594
2595     static int started = STARTED_NONE;
2596     static char parse[20000];
2597     static int parse_pos = 0;
2598     static char buf[BUF_SIZE + 1];
2599     static int firstTime = TRUE, intfSet = FALSE;
2600     static ColorClass prevColor = ColorNormal;
2601     static int savingComment = FALSE;
2602     static int cmatch = 0; // continuation sequence match
2603     char *bp;
2604     char str[MSG_SIZ];
2605     int i, oldi;
2606     int buf_len;
2607     int next_out;
2608     int tkind;
2609     int backup;    /* [DM] For zippy color lines */
2610     char *p;
2611     char talker[MSG_SIZ]; // [HGM] chat
2612     int channel;
2613
2614     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2615
2616     if (appData.debugMode) {
2617       if (!error) {
2618         fprintf(debugFP, "<ICS: ");
2619         show_bytes(debugFP, data, count);
2620         fprintf(debugFP, "\n");
2621       }
2622     }
2623
2624     if (appData.debugMode) { int f = forwardMostMove;
2625         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2626                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2627                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2628     }
2629     if (count > 0) {
2630         /* If last read ended with a partial line that we couldn't parse,
2631            prepend it to the new read and try again. */
2632         if (leftover_len > 0) {
2633             for (i=0; i<leftover_len; i++)
2634               buf[i] = buf[leftover_start + i];
2635         }
2636
2637     /* copy new characters into the buffer */
2638     bp = buf + leftover_len;
2639     buf_len=leftover_len;
2640     for (i=0; i<count; i++)
2641     {
2642         // ignore these
2643         if (data[i] == '\r')
2644             continue;
2645
2646         // join lines split by ICS?
2647         if (!appData.noJoin)
2648         {
2649             /*
2650                 Joining just consists of finding matches against the
2651                 continuation sequence, and discarding that sequence
2652                 if found instead of copying it.  So, until a match
2653                 fails, there's nothing to do since it might be the
2654                 complete sequence, and thus, something we don't want
2655                 copied.
2656             */
2657             if (data[i] == cont_seq[cmatch])
2658             {
2659                 cmatch++;
2660                 if (cmatch == strlen(cont_seq))
2661                 {
2662                     cmatch = 0; // complete match.  just reset the counter
2663
2664                     /*
2665                         it's possible for the ICS to not include the space
2666                         at the end of the last word, making our [correct]
2667                         join operation fuse two separate words.  the server
2668                         does this when the space occurs at the width setting.
2669                     */
2670                     if (!buf_len || buf[buf_len-1] != ' ')
2671                     {
2672                         *bp++ = ' ';
2673                         buf_len++;
2674                     }
2675                 }
2676                 continue;
2677             }
2678             else if (cmatch)
2679             {
2680                 /*
2681                     match failed, so we have to copy what matched before
2682                     falling through and copying this character.  In reality,
2683                     this will only ever be just the newline character, but
2684                     it doesn't hurt to be precise.
2685                 */
2686                 strncpy(bp, cont_seq, cmatch);
2687                 bp += cmatch;
2688                 buf_len += cmatch;
2689                 cmatch = 0;
2690             }
2691         }
2692
2693         // copy this char
2694         *bp++ = data[i];
2695         buf_len++;
2696     }
2697
2698         buf[buf_len] = NULLCHAR;
2699 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2700         next_out = 0;
2701         leftover_start = 0;
2702
2703         i = 0;
2704         while (i < buf_len) {
2705             /* Deal with part of the TELNET option negotiation
2706                protocol.  We refuse to do anything beyond the
2707                defaults, except that we allow the WILL ECHO option,
2708                which ICS uses to turn off password echoing when we are
2709                directly connected to it.  We reject this option
2710                if localLineEditing mode is on (always on in xboard)
2711                and we are talking to port 23, which might be a real
2712                telnet server that will try to keep WILL ECHO on permanently.
2713              */
2714             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2715                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2716                 unsigned char option;
2717                 oldi = i;
2718                 switch ((unsigned char) buf[++i]) {
2719                   case TN_WILL:
2720                     if (appData.debugMode)
2721                       fprintf(debugFP, "\n<WILL ");
2722                     switch (option = (unsigned char) buf[++i]) {
2723                       case TN_ECHO:
2724                         if (appData.debugMode)
2725                           fprintf(debugFP, "ECHO ");
2726                         /* Reply only if this is a change, according
2727                            to the protocol rules. */
2728                         if (remoteEchoOption) break;
2729                         if (appData.localLineEditing &&
2730                             atoi(appData.icsPort) == TN_PORT) {
2731                             TelnetRequest(TN_DONT, TN_ECHO);
2732                         } else {
2733                             EchoOff();
2734                             TelnetRequest(TN_DO, TN_ECHO);
2735                             remoteEchoOption = TRUE;
2736                         }
2737                         break;
2738                       default:
2739                         if (appData.debugMode)
2740                           fprintf(debugFP, "%d ", option);
2741                         /* Whatever this is, we don't want it. */
2742                         TelnetRequest(TN_DONT, option);
2743                         break;
2744                     }
2745                     break;
2746                   case TN_WONT:
2747                     if (appData.debugMode)
2748                       fprintf(debugFP, "\n<WONT ");
2749                     switch (option = (unsigned char) buf[++i]) {
2750                       case TN_ECHO:
2751                         if (appData.debugMode)
2752                           fprintf(debugFP, "ECHO ");
2753                         /* Reply only if this is a change, according
2754                            to the protocol rules. */
2755                         if (!remoteEchoOption) break;
2756                         EchoOn();
2757                         TelnetRequest(TN_DONT, TN_ECHO);
2758                         remoteEchoOption = FALSE;
2759                         break;
2760                       default:
2761                         if (appData.debugMode)
2762                           fprintf(debugFP, "%d ", (unsigned char) option);
2763                         /* Whatever this is, it must already be turned
2764                            off, because we never agree to turn on
2765                            anything non-default, so according to the
2766                            protocol rules, we don't reply. */
2767                         break;
2768                     }
2769                     break;
2770                   case TN_DO:
2771                     if (appData.debugMode)
2772                       fprintf(debugFP, "\n<DO ");
2773                     switch (option = (unsigned char) buf[++i]) {
2774                       default:
2775                         /* Whatever this is, we refuse to do it. */
2776                         if (appData.debugMode)
2777                           fprintf(debugFP, "%d ", option);
2778                         TelnetRequest(TN_WONT, option);
2779                         break;
2780                     }
2781                     break;
2782                   case TN_DONT:
2783                     if (appData.debugMode)
2784                       fprintf(debugFP, "\n<DONT ");
2785                     switch (option = (unsigned char) buf[++i]) {
2786                       default:
2787                         if (appData.debugMode)
2788                           fprintf(debugFP, "%d ", option);
2789                         /* Whatever this is, we are already not doing
2790                            it, because we never agree to do anything
2791                            non-default, so according to the protocol
2792                            rules, we don't reply. */
2793                         break;
2794                     }
2795                     break;
2796                   case TN_IAC:
2797                     if (appData.debugMode)
2798                       fprintf(debugFP, "\n<IAC ");
2799                     /* Doubled IAC; pass it through */
2800                     i--;
2801                     break;
2802                   default:
2803                     if (appData.debugMode)
2804                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2805                     /* Drop all other telnet commands on the floor */
2806                     break;
2807                 }
2808                 if (oldi > next_out)
2809                   SendToPlayer(&buf[next_out], oldi - next_out);
2810                 if (++i > next_out)
2811                   next_out = i;
2812                 continue;
2813             }
2814
2815             /* OK, this at least will *usually* work */
2816             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2817                 loggedOn = TRUE;
2818             }
2819
2820             if (loggedOn && !intfSet) {
2821                 if (ics_type == ICS_ICC) {
2822                   snprintf(str, MSG_SIZ,
2823                           "/set-quietly interface %s\n/set-quietly style 12\n",
2824                           programVersion);
2825                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2826                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2827                 } else if (ics_type == ICS_CHESSNET) {
2828                   snprintf(str, MSG_SIZ, "/style 12\n");
2829                 } else {
2830                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2831                   strcat(str, programVersion);
2832                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2833                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2834                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2835 #ifdef WIN32
2836                   strcat(str, "$iset nohighlight 1\n");
2837 #endif
2838                   strcat(str, "$iset lock 1\n$style 12\n");
2839                 }
2840                 SendToICS(str);
2841                 NotifyFrontendLogin();
2842                 intfSet = TRUE;
2843             }
2844
2845             if (started == STARTED_COMMENT) {
2846                 /* Accumulate characters in comment */
2847                 parse[parse_pos++] = buf[i];
2848                 if (buf[i] == '\n') {
2849                     parse[parse_pos] = NULLCHAR;
2850                     if(chattingPartner>=0) {
2851                         char mess[MSG_SIZ];
2852                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2853                         OutputChatMessage(chattingPartner, mess);
2854                         chattingPartner = -1;
2855                         next_out = i+1; // [HGM] suppress printing in ICS window
2856                     } else
2857                     if(!suppressKibitz) // [HGM] kibitz
2858                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2859                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2860                         int nrDigit = 0, nrAlph = 0, j;
2861                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2862                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2863                         parse[parse_pos] = NULLCHAR;
2864                         // try to be smart: if it does not look like search info, it should go to
2865                         // ICS interaction window after all, not to engine-output window.
2866                         for(j=0; j<parse_pos; j++) { // count letters and digits
2867                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2868                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2869                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2870                         }
2871                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2872                             int depth=0; float score;
2873                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2874                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2875                                 pvInfoList[forwardMostMove-1].depth = depth;
2876                                 pvInfoList[forwardMostMove-1].score = 100*score;
2877                             }
2878                             OutputKibitz(suppressKibitz, parse);
2879                         } else {
2880                             char tmp[MSG_SIZ];
2881                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2882                             SendToPlayer(tmp, strlen(tmp));
2883                         }
2884                         next_out = i+1; // [HGM] suppress printing in ICS window
2885                     }
2886                     started = STARTED_NONE;
2887                 } else {
2888                     /* Don't match patterns against characters in comment */
2889                     i++;
2890                     continue;
2891                 }
2892             }
2893             if (started == STARTED_CHATTER) {
2894                 if (buf[i] != '\n') {
2895                     /* Don't match patterns against characters in chatter */
2896                     i++;
2897                     continue;
2898                 }
2899                 started = STARTED_NONE;
2900                 if(suppressKibitz) next_out = i+1;
2901             }
2902
2903             /* Kludge to deal with rcmd protocol */
2904             if (firstTime && looking_at(buf, &i, "\001*")) {
2905                 DisplayFatalError(&buf[1], 0, 1);
2906                 continue;
2907             } else {
2908                 firstTime = FALSE;
2909             }
2910
2911             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2912                 ics_type = ICS_ICC;
2913                 ics_prefix = "/";
2914                 if (appData.debugMode)
2915                   fprintf(debugFP, "ics_type %d\n", ics_type);
2916                 continue;
2917             }
2918             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2919                 ics_type = ICS_FICS;
2920                 ics_prefix = "$";
2921                 if (appData.debugMode)
2922                   fprintf(debugFP, "ics_type %d\n", ics_type);
2923                 continue;
2924             }
2925             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2926                 ics_type = ICS_CHESSNET;
2927                 ics_prefix = "/";
2928                 if (appData.debugMode)
2929                   fprintf(debugFP, "ics_type %d\n", ics_type);
2930                 continue;
2931             }
2932
2933             if (!loggedOn &&
2934                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2935                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2936                  looking_at(buf, &i, "will be \"*\""))) {
2937               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2938               continue;
2939             }
2940
2941             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2942               char buf[MSG_SIZ];
2943               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2944               DisplayIcsInteractionTitle(buf);
2945               have_set_title = TRUE;
2946             }
2947
2948             /* skip finger notes */
2949             if (started == STARTED_NONE &&
2950                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2951                  (buf[i] == '1' && buf[i+1] == '0')) &&
2952                 buf[i+2] == ':' && buf[i+3] == ' ') {
2953               started = STARTED_CHATTER;
2954               i += 3;
2955               continue;
2956             }
2957
2958             oldi = i;
2959             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2960             if(appData.seekGraph) {
2961                 if(soughtPending && MatchSoughtLine(buf+i)) {
2962                     i = strstr(buf+i, "rated") - buf;
2963                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2964                     next_out = leftover_start = i;
2965                     started = STARTED_CHATTER;
2966                     suppressKibitz = TRUE;
2967                     continue;
2968                 }
2969                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2970                         && looking_at(buf, &i, "* ads displayed")) {
2971                     soughtPending = FALSE;
2972                     seekGraphUp = TRUE;
2973                     DrawSeekGraph();
2974                     continue;
2975                 }
2976                 if(appData.autoRefresh) {
2977                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2978                         int s = (ics_type == ICS_ICC); // ICC format differs
2979                         if(seekGraphUp)
2980                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2981                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2982                         looking_at(buf, &i, "*% "); // eat prompt
2983                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2984                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2985                         next_out = i; // suppress
2986                         continue;
2987                     }
2988                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2989                         char *p = star_match[0];
2990                         while(*p) {
2991                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2992                             while(*p && *p++ != ' '); // next
2993                         }
2994                         looking_at(buf, &i, "*% "); // eat prompt
2995                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2996                         next_out = i;
2997                         continue;
2998                     }
2999                 }
3000             }
3001
3002             /* skip formula vars */
3003             if (started == STARTED_NONE &&
3004                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3005               started = STARTED_CHATTER;
3006               i += 3;
3007               continue;
3008             }
3009
3010             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3011             if (appData.autoKibitz && started == STARTED_NONE &&
3012                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3013                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3014                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3015                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3016                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3017                         suppressKibitz = TRUE;
3018                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                         next_out = i;
3020                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3021                                 && (gameMode == IcsPlayingWhite)) ||
3022                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3023                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3024                             started = STARTED_CHATTER; // own kibitz we simply discard
3025                         else {
3026                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3027                             parse_pos = 0; parse[0] = NULLCHAR;
3028                             savingComment = TRUE;
3029                             suppressKibitz = gameMode != IcsObserving ? 2 :
3030                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3031                         }
3032                         continue;
3033                 } else
3034                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3035                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3036                          && atoi(star_match[0])) {
3037                     // suppress the acknowledgements of our own autoKibitz
3038                     char *p;
3039                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3041                     SendToPlayer(star_match[0], strlen(star_match[0]));
3042                     if(looking_at(buf, &i, "*% ")) // eat prompt
3043                         suppressKibitz = FALSE;
3044                     next_out = i;
3045                     continue;
3046                 }
3047             } // [HGM] kibitz: end of patch
3048
3049             // [HGM] chat: intercept tells by users for which we have an open chat window
3050             channel = -1;
3051             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3052                                            looking_at(buf, &i, "* whispers:") ||
3053                                            looking_at(buf, &i, "* kibitzes:") ||
3054                                            looking_at(buf, &i, "* shouts:") ||
3055                                            looking_at(buf, &i, "* c-shouts:") ||
3056                                            looking_at(buf, &i, "--> * ") ||
3057                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3058                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3059                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3060                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3061                 int p;
3062                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3063                 chattingPartner = -1;
3064
3065                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3066                 for(p=0; p<MAX_CHAT; p++) {
3067                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3068                     talker[0] = '['; strcat(talker, "] ");
3069                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3070                     chattingPartner = p; break;
3071                     }
3072                 } else
3073                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3074                 for(p=0; p<MAX_CHAT; p++) {
3075                     if(!strcmp("kibitzes", chatPartner[p])) {
3076                         talker[0] = '['; strcat(talker, "] ");
3077                         chattingPartner = p; break;
3078                     }
3079                 } else
3080                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3081                 for(p=0; p<MAX_CHAT; p++) {
3082                     if(!strcmp("whispers", chatPartner[p])) {
3083                         talker[0] = '['; strcat(talker, "] ");
3084                         chattingPartner = p; break;
3085                     }
3086                 } else
3087                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3088                   if(buf[i-8] == '-' && buf[i-3] == 't')
3089                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3090                     if(!strcmp("c-shouts", chatPartner[p])) {
3091                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3092                         chattingPartner = p; break;
3093                     }
3094                   }
3095                   if(chattingPartner < 0)
3096                   for(p=0; p<MAX_CHAT; p++) {
3097                     if(!strcmp("shouts", chatPartner[p])) {
3098                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3099                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3100                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3101                         chattingPartner = p; break;
3102                     }
3103                   }
3104                 }
3105                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3106                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3107                     talker[0] = 0; Colorize(ColorTell, FALSE);
3108                     chattingPartner = p; break;
3109                 }
3110                 if(chattingPartner<0) i = oldi; else {
3111                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3112                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3113                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3114                     started = STARTED_COMMENT;
3115                     parse_pos = 0; parse[0] = NULLCHAR;
3116                     savingComment = 3 + chattingPartner; // counts as TRUE
3117                     suppressKibitz = TRUE;
3118                     continue;
3119                 }
3120             } // [HGM] chat: end of patch
3121
3122           backup = i;
3123             if (appData.zippyTalk || appData.zippyPlay) {
3124                 /* [DM] Backup address for color zippy lines */
3125 #if ZIPPY
3126                if (loggedOn == TRUE)
3127                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3128                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3129 #endif
3130             } // [DM] 'else { ' deleted
3131                 if (
3132                     /* Regular tells and says */
3133                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3134                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3135                     looking_at(buf, &i, "* says: ") ||
3136                     /* Don't color "message" or "messages" output */
3137                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3138                     looking_at(buf, &i, "*. * at *:*: ") ||
3139                     looking_at(buf, &i, "--* (*:*): ") ||
3140                     /* Message notifications (same color as tells) */
3141                     looking_at(buf, &i, "* has left a message ") ||
3142                     looking_at(buf, &i, "* just sent you a message:\n") ||
3143                     /* Whispers and kibitzes */
3144                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3145                     looking_at(buf, &i, "* kibitzes: ") ||
3146                     /* Channel tells */
3147                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3148
3149                   if (tkind == 1 && strchr(star_match[0], ':')) {
3150                       /* Avoid "tells you:" spoofs in channels */
3151                      tkind = 3;
3152                   }
3153                   if (star_match[0][0] == NULLCHAR ||
3154                       strchr(star_match[0], ' ') ||
3155                       (tkind == 3 && strchr(star_match[1], ' '))) {
3156                     /* Reject bogus matches */
3157                     i = oldi;
3158                   } else {
3159                     if (appData.colorize) {
3160                       if (oldi > next_out) {
3161                         SendToPlayer(&buf[next_out], oldi - next_out);
3162                         next_out = oldi;
3163                       }
3164                       switch (tkind) {
3165                       case 1:
3166                         Colorize(ColorTell, FALSE);
3167                         curColor = ColorTell;
3168                         break;
3169                       case 2:
3170                         Colorize(ColorKibitz, FALSE);
3171                         curColor = ColorKibitz;
3172                         break;
3173                       case 3:
3174                         p = strrchr(star_match[1], '(');
3175                         if (p == NULL) {
3176                           p = star_match[1];
3177                         } else {
3178                           p++;
3179                         }
3180                         if (atoi(p) == 1) {
3181                           Colorize(ColorChannel1, FALSE);
3182                           curColor = ColorChannel1;
3183                         } else {
3184                           Colorize(ColorChannel, FALSE);
3185                           curColor = ColorChannel;
3186                         }
3187                         break;
3188                       case 5:
3189                         curColor = ColorNormal;
3190                         break;
3191                       }
3192                     }
3193                     if (started == STARTED_NONE && appData.autoComment &&
3194                         (gameMode == IcsObserving ||
3195                          gameMode == IcsPlayingWhite ||
3196                          gameMode == IcsPlayingBlack)) {
3197                       parse_pos = i - oldi;
3198                       memcpy(parse, &buf[oldi], parse_pos);
3199                       parse[parse_pos] = NULLCHAR;
3200                       started = STARTED_COMMENT;
3201                       savingComment = TRUE;
3202                     } else {
3203                       started = STARTED_CHATTER;
3204                       savingComment = FALSE;
3205                     }
3206                     loggedOn = TRUE;
3207                     continue;
3208                   }
3209                 }
3210
3211                 if (looking_at(buf, &i, "* s-shouts: ") ||
3212                     looking_at(buf, &i, "* c-shouts: ")) {
3213                     if (appData.colorize) {
3214                         if (oldi > next_out) {
3215                             SendToPlayer(&buf[next_out], oldi - next_out);
3216                             next_out = oldi;
3217                         }
3218                         Colorize(ColorSShout, FALSE);
3219                         curColor = ColorSShout;
3220                     }
3221                     loggedOn = TRUE;
3222                     started = STARTED_CHATTER;
3223                     continue;
3224                 }
3225
3226                 if (looking_at(buf, &i, "--->")) {
3227                     loggedOn = TRUE;
3228                     continue;
3229                 }
3230
3231                 if (looking_at(buf, &i, "* shouts: ") ||
3232                     looking_at(buf, &i, "--> ")) {
3233                     if (appData.colorize) {
3234                         if (oldi > next_out) {
3235                             SendToPlayer(&buf[next_out], oldi - next_out);
3236                             next_out = oldi;
3237                         }
3238                         Colorize(ColorShout, FALSE);
3239                         curColor = ColorShout;
3240                     }
3241                     loggedOn = TRUE;
3242                     started = STARTED_CHATTER;
3243                     continue;
3244                 }
3245
3246                 if (looking_at( buf, &i, "Challenge:")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorChallenge, FALSE);
3253                         curColor = ColorChallenge;
3254                     }
3255                     loggedOn = TRUE;
3256                     continue;
3257                 }
3258
3259                 if (looking_at(buf, &i, "* offers you") ||
3260                     looking_at(buf, &i, "* offers to be") ||
3261                     looking_at(buf, &i, "* would like to") ||
3262                     looking_at(buf, &i, "* requests to") ||
3263                     looking_at(buf, &i, "Your opponent offers") ||
3264                     looking_at(buf, &i, "Your opponent requests")) {
3265
3266                     if (appData.colorize) {
3267                         if (oldi > next_out) {
3268                             SendToPlayer(&buf[next_out], oldi - next_out);
3269                             next_out = oldi;
3270                         }
3271                         Colorize(ColorRequest, FALSE);
3272                         curColor = ColorRequest;
3273                     }
3274                     continue;
3275                 }
3276
3277                 if (looking_at(buf, &i, "* (*) seeking")) {
3278                     if (appData.colorize) {
3279                         if (oldi > next_out) {
3280                             SendToPlayer(&buf[next_out], oldi - next_out);
3281                             next_out = oldi;
3282                         }
3283                         Colorize(ColorSeek, FALSE);
3284                         curColor = ColorSeek;
3285                     }
3286                     continue;
3287             }
3288
3289           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3290
3291             if (looking_at(buf, &i, "\\   ")) {
3292                 if (prevColor != ColorNormal) {
3293                     if (oldi > next_out) {
3294                         SendToPlayer(&buf[next_out], oldi - next_out);
3295                         next_out = oldi;
3296                     }
3297                     Colorize(prevColor, TRUE);
3298                     curColor = prevColor;
3299                 }
3300                 if (savingComment) {
3301                     parse_pos = i - oldi;
3302                     memcpy(parse, &buf[oldi], parse_pos);
3303                     parse[parse_pos] = NULLCHAR;
3304                     started = STARTED_COMMENT;
3305                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3306                         chattingPartner = savingComment - 3; // kludge to remember the box
3307                 } else {
3308                     started = STARTED_CHATTER;
3309                 }
3310                 continue;
3311             }
3312
3313             if (looking_at(buf, &i, "Black Strength :") ||
3314                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3315                 looking_at(buf, &i, "<10>") ||
3316                 looking_at(buf, &i, "#@#")) {
3317                 /* Wrong board style */
3318                 loggedOn = TRUE;
3319                 SendToICS(ics_prefix);
3320                 SendToICS("set style 12\n");
3321                 SendToICS(ics_prefix);
3322                 SendToICS("refresh\n");
3323                 continue;
3324             }
3325
3326             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3327                 ICSInitScript();
3328                 have_sent_ICS_logon = 1;
3329                 continue;
3330             }
3331
3332             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3333                 (looking_at(buf, &i, "\n<12> ") ||
3334                  looking_at(buf, &i, "<12> "))) {
3335                 loggedOn = TRUE;
3336                 if (oldi > next_out) {
3337                     SendToPlayer(&buf[next_out], oldi - next_out);
3338                 }
3339                 next_out = i;
3340                 started = STARTED_BOARD;
3341                 parse_pos = 0;
3342                 continue;
3343             }
3344
3345             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3346                 looking_at(buf, &i, "<b1> ")) {
3347                 if (oldi > next_out) {
3348                     SendToPlayer(&buf[next_out], oldi - next_out);
3349                 }
3350                 next_out = i;
3351                 started = STARTED_HOLDINGS;
3352                 parse_pos = 0;
3353                 continue;
3354             }
3355
3356             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3357                 loggedOn = TRUE;
3358                 /* Header for a move list -- first line */
3359
3360                 switch (ics_getting_history) {
3361                   case H_FALSE:
3362                     switch (gameMode) {
3363                       case IcsIdle:
3364                       case BeginningOfGame:
3365                         /* User typed "moves" or "oldmoves" while we
3366                            were idle.  Pretend we asked for these
3367                            moves and soak them up so user can step
3368                            through them and/or save them.
3369                            */
3370                         Reset(FALSE, TRUE);
3371                         gameMode = IcsObserving;
3372                         ModeHighlight();
3373                         ics_gamenum = -1;
3374                         ics_getting_history = H_GOT_UNREQ_HEADER;
3375                         break;
3376                       case EditGame: /*?*/
3377                       case EditPosition: /*?*/
3378                         /* Should above feature work in these modes too? */
3379                         /* For now it doesn't */
3380                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3381                         break;
3382                       default:
3383                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3384                         break;
3385                     }
3386                     break;
3387                   case H_REQUESTED:
3388                     /* Is this the right one? */
3389                     if (gameInfo.white && gameInfo.black &&
3390                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3391                         strcmp(gameInfo.black, star_match[2]) == 0) {
3392                         /* All is well */
3393                         ics_getting_history = H_GOT_REQ_HEADER;
3394                     }
3395                     break;
3396                   case H_GOT_REQ_HEADER:
3397                   case H_GOT_UNREQ_HEADER:
3398                   case H_GOT_UNWANTED_HEADER:
3399                   case H_GETTING_MOVES:
3400                     /* Should not happen */
3401                     DisplayError(_("Error gathering move list: two headers"), 0);
3402                     ics_getting_history = H_FALSE;
3403                     break;
3404                 }
3405
3406                 /* Save player ratings into gameInfo if needed */
3407                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3408                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3409                     (gameInfo.whiteRating == -1 ||
3410                      gameInfo.blackRating == -1)) {
3411
3412                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3413                     gameInfo.blackRating = string_to_rating(star_match[3]);
3414                     if (appData.debugMode)
3415                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3416                               gameInfo.whiteRating, gameInfo.blackRating);
3417                 }
3418                 continue;
3419             }
3420
3421             if (looking_at(buf, &i,
3422               "* * match, initial time: * minute*, increment: * second")) {
3423                 /* Header for a move list -- second line */
3424                 /* Initial board will follow if this is a wild game */
3425                 if (gameInfo.event != NULL) free(gameInfo.event);
3426                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3427                 gameInfo.event = StrSave(str);
3428                 /* [HGM] we switched variant. Translate boards if needed. */
3429                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3430                 continue;
3431             }
3432
3433             if (looking_at(buf, &i, "Move  ")) {
3434                 /* Beginning of a move list */
3435                 switch (ics_getting_history) {
3436                   case H_FALSE:
3437                     /* Normally should not happen */
3438                     /* Maybe user hit reset while we were parsing */
3439                     break;
3440                   case H_REQUESTED:
3441                     /* Happens if we are ignoring a move list that is not
3442                      * the one we just requested.  Common if the user
3443                      * tries to observe two games without turning off
3444                      * getMoveList */
3445                     break;
3446                   case H_GETTING_MOVES:
3447                     /* Should not happen */
3448                     DisplayError(_("Error gathering move list: nested"), 0);
3449                     ics_getting_history = H_FALSE;
3450                     break;
3451                   case H_GOT_REQ_HEADER:
3452                     ics_getting_history = H_GETTING_MOVES;
3453                     started = STARTED_MOVES;
3454                     parse_pos = 0;
3455                     if (oldi > next_out) {
3456                         SendToPlayer(&buf[next_out], oldi - next_out);
3457                     }
3458                     break;
3459                   case H_GOT_UNREQ_HEADER:
3460                     ics_getting_history = H_GETTING_MOVES;
3461                     started = STARTED_MOVES_NOHIDE;
3462                     parse_pos = 0;
3463                     break;
3464                   case H_GOT_UNWANTED_HEADER:
3465                     ics_getting_history = H_FALSE;
3466                     break;
3467                 }
3468                 continue;
3469             }
3470
3471             if (looking_at(buf, &i, "% ") ||
3472                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3473                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3474                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3475                     soughtPending = FALSE;
3476                     seekGraphUp = TRUE;
3477                     DrawSeekGraph();
3478                 }
3479                 if(suppressKibitz) next_out = i;
3480                 savingComment = FALSE;
3481                 suppressKibitz = 0;
3482                 switch (started) {
3483                   case STARTED_MOVES:
3484                   case STARTED_MOVES_NOHIDE:
3485                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3486                     parse[parse_pos + i - oldi] = NULLCHAR;
3487                     ParseGameHistory(parse);
3488 #if ZIPPY
3489                     if (appData.zippyPlay && first.initDone) {
3490                         FeedMovesToProgram(&first, forwardMostMove);
3491                         if (gameMode == IcsPlayingWhite) {
3492                             if (WhiteOnMove(forwardMostMove)) {
3493                                 if (first.sendTime) {
3494                                   if (first.useColors) {
3495                                     SendToProgram("black\n", &first);
3496                                   }
3497                                   SendTimeRemaining(&first, TRUE);
3498                                 }
3499                                 if (first.useColors) {
3500                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3501                                 }
3502                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3503                                 first.maybeThinking = TRUE;
3504                             } else {
3505                                 if (first.usePlayother) {
3506                                   if (first.sendTime) {
3507                                     SendTimeRemaining(&first, TRUE);
3508                                   }
3509                                   SendToProgram("playother\n", &first);
3510                                   firstMove = FALSE;
3511                                 } else {
3512                                   firstMove = TRUE;
3513                                 }
3514                             }
3515                         } else if (gameMode == IcsPlayingBlack) {
3516                             if (!WhiteOnMove(forwardMostMove)) {
3517                                 if (first.sendTime) {
3518                                   if (first.useColors) {
3519                                     SendToProgram("white\n", &first);
3520                                   }
3521                                   SendTimeRemaining(&first, FALSE);
3522                                 }
3523                                 if (first.useColors) {
3524                                   SendToProgram("black\n", &first);
3525                                 }
3526                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3527                                 first.maybeThinking = TRUE;
3528                             } else {
3529                                 if (first.usePlayother) {
3530                                   if (first.sendTime) {
3531                                     SendTimeRemaining(&first, FALSE);
3532                                   }
3533                                   SendToProgram("playother\n", &first);
3534                                   firstMove = FALSE;
3535                                 } else {
3536                                   firstMove = TRUE;
3537                                 }
3538                             }
3539                         }
3540                     }
3541 #endif
3542                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3543                         /* Moves came from oldmoves or moves command
3544                            while we weren't doing anything else.
3545                            */
3546                         currentMove = forwardMostMove;
3547                         ClearHighlights();/*!!could figure this out*/
3548                         flipView = appData.flipView;
3549                         DrawPosition(TRUE, boards[currentMove]);
3550                         DisplayBothClocks();
3551                         snprintf(str, MSG_SIZ, "%s vs. %s",
3552                                 gameInfo.white, gameInfo.black);
3553                         DisplayTitle(str);
3554                         gameMode = IcsIdle;
3555                     } else {
3556                         /* Moves were history of an active game */
3557                         if (gameInfo.resultDetails != NULL) {
3558                             free(gameInfo.resultDetails);
3559                             gameInfo.resultDetails = NULL;
3560                         }
3561                     }
3562                     HistorySet(parseList, backwardMostMove,
3563                                forwardMostMove, currentMove-1);
3564                     DisplayMove(currentMove - 1);
3565                     if (started == STARTED_MOVES) next_out = i;
3566                     started = STARTED_NONE;
3567                     ics_getting_history = H_FALSE;
3568                     break;
3569
3570                   case STARTED_OBSERVE:
3571                     started = STARTED_NONE;
3572                     SendToICS(ics_prefix);
3573                     SendToICS("refresh\n");
3574                     break;
3575
3576                   default:
3577                     break;
3578                 }
3579                 if(bookHit) { // [HGM] book: simulate book reply
3580                     static char bookMove[MSG_SIZ]; // a bit generous?
3581
3582                     programStats.nodes = programStats.depth = programStats.time =
3583                     programStats.score = programStats.got_only_move = 0;
3584                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3585
3586                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3587                     strcat(bookMove, bookHit);
3588                     HandleMachineMove(bookMove, &first);
3589                 }
3590                 continue;
3591             }
3592
3593             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3594                  started == STARTED_HOLDINGS ||
3595                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3596                 /* Accumulate characters in move list or board */
3597                 parse[parse_pos++] = buf[i];
3598             }
3599
3600             /* Start of game messages.  Mostly we detect start of game
3601                when the first board image arrives.  On some versions
3602                of the ICS, though, we need to do a "refresh" after starting
3603                to observe in order to get the current board right away. */
3604             if (looking_at(buf, &i, "Adding game * to observation list")) {
3605                 started = STARTED_OBSERVE;
3606                 continue;
3607             }
3608
3609             /* Handle auto-observe */
3610             if (appData.autoObserve &&
3611                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3612                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3613                 char *player;
3614                 /* Choose the player that was highlighted, if any. */
3615                 if (star_match[0][0] == '\033' ||
3616                     star_match[1][0] != '\033') {
3617                     player = star_match[0];
3618                 } else {
3619                     player = star_match[2];
3620                 }
3621                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3622                         ics_prefix, StripHighlightAndTitle(player));
3623                 SendToICS(str);
3624
3625                 /* Save ratings from notify string */
3626                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3627                 player1Rating = string_to_rating(star_match[1]);
3628                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3629                 player2Rating = string_to_rating(star_match[3]);
3630
3631                 if (appData.debugMode)
3632                   fprintf(debugFP,
3633                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3634                           player1Name, player1Rating,
3635                           player2Name, player2Rating);
3636
3637                 continue;
3638             }
3639
3640             /* Deal with automatic examine mode after a game,
3641                and with IcsObserving -> IcsExamining transition */
3642             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3643                 looking_at(buf, &i, "has made you an examiner of game *")) {
3644
3645                 int gamenum = atoi(star_match[0]);
3646                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3647                     gamenum == ics_gamenum) {
3648                     /* We were already playing or observing this game;
3649                        no need to refetch history */
3650                     gameMode = IcsExamining;
3651                     if (pausing) {
3652                         pauseExamForwardMostMove = forwardMostMove;
3653                     } else if (currentMove < forwardMostMove) {
3654                         ForwardInner(forwardMostMove);
3655                     }
3656                 } else {
3657                     /* I don't think this case really can happen */
3658                     SendToICS(ics_prefix);
3659                     SendToICS("refresh\n");
3660                 }
3661                 continue;
3662             }
3663
3664             /* Error messages */
3665 //          if (ics_user_moved) {
3666             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3667                 if (looking_at(buf, &i, "Illegal move") ||
3668                     looking_at(buf, &i, "Not a legal move") ||
3669                     looking_at(buf, &i, "Your king is in check") ||
3670                     looking_at(buf, &i, "It isn't your turn") ||
3671                     looking_at(buf, &i, "It is not your move")) {
3672                     /* Illegal move */
3673                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3674                         currentMove = forwardMostMove-1;
3675                         DisplayMove(currentMove - 1); /* before DMError */
3676                         DrawPosition(FALSE, boards[currentMove]);
3677                         SwitchClocks(forwardMostMove-1); // [HGM] race
3678                         DisplayBothClocks();
3679                     }
3680                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3681                     ics_user_moved = 0;
3682                     continue;
3683                 }
3684             }
3685
3686             if (looking_at(buf, &i, "still have time") ||
3687                 looking_at(buf, &i, "not out of time") ||
3688                 looking_at(buf, &i, "either player is out of time") ||
3689                 looking_at(buf, &i, "has timeseal; checking")) {
3690                 /* We must have called his flag a little too soon */
3691                 whiteFlag = blackFlag = FALSE;
3692                 continue;
3693             }
3694
3695             if (looking_at(buf, &i, "added * seconds to") ||
3696                 looking_at(buf, &i, "seconds were added to")) {
3697                 /* Update the clocks */
3698                 SendToICS(ics_prefix);
3699                 SendToICS("refresh\n");
3700                 continue;
3701             }
3702
3703             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3704                 ics_clock_paused = TRUE;
3705                 StopClocks();
3706                 continue;
3707             }
3708
3709             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3710                 ics_clock_paused = FALSE;
3711                 StartClocks();
3712                 continue;
3713             }
3714
3715             /* Grab player ratings from the Creating: message.
3716                Note we have to check for the special case when
3717                the ICS inserts things like [white] or [black]. */
3718             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3719                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3720                 /* star_matches:
3721                    0    player 1 name (not necessarily white)
3722                    1    player 1 rating
3723                    2    empty, white, or black (IGNORED)
3724                    3    player 2 name (not necessarily black)
3725                    4    player 2 rating
3726
3727                    The names/ratings are sorted out when the game
3728                    actually starts (below).
3729                 */
3730                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3731                 player1Rating = string_to_rating(star_match[1]);
3732                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3733                 player2Rating = string_to_rating(star_match[4]);
3734
3735                 if (appData.debugMode)
3736                   fprintf(debugFP,
3737                           "Ratings from 'Creating:' %s %d, %s %d\n",
3738                           player1Name, player1Rating,
3739                           player2Name, player2Rating);
3740
3741                 continue;
3742             }
3743
3744             /* Improved generic start/end-of-game messages */
3745             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3746                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3747                 /* If tkind == 0: */
3748                 /* star_match[0] is the game number */
3749                 /*           [1] is the white player's name */
3750                 /*           [2] is the black player's name */
3751                 /* For end-of-game: */
3752                 /*           [3] is the reason for the game end */
3753                 /*           [4] is a PGN end game-token, preceded by " " */
3754                 /* For start-of-game: */
3755                 /*           [3] begins with "Creating" or "Continuing" */
3756                 /*           [4] is " *" or empty (don't care). */
3757                 int gamenum = atoi(star_match[0]);
3758                 char *whitename, *blackname, *why, *endtoken;
3759                 ChessMove endtype = EndOfFile;
3760
3761                 if (tkind == 0) {
3762                   whitename = star_match[1];
3763                   blackname = star_match[2];
3764                   why = star_match[3];
3765                   endtoken = star_match[4];
3766                 } else {
3767                   whitename = star_match[1];
3768                   blackname = star_match[3];
3769                   why = star_match[5];
3770                   endtoken = star_match[6];
3771                 }
3772
3773                 /* Game start messages */
3774                 if (strncmp(why, "Creating ", 9) == 0 ||
3775                     strncmp(why, "Continuing ", 11) == 0) {
3776                     gs_gamenum = gamenum;
3777                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3778                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3779 #if ZIPPY
3780                     if (appData.zippyPlay) {
3781                         ZippyGameStart(whitename, blackname);
3782                     }
3783 #endif /*ZIPPY*/
3784                     partnerBoardValid = FALSE; // [HGM] bughouse
3785                     continue;
3786                 }
3787
3788                 /* Game end messages */
3789                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3790                     ics_gamenum != gamenum) {
3791                     continue;
3792                 }
3793                 while (endtoken[0] == ' ') endtoken++;
3794                 switch (endtoken[0]) {
3795                   case '*':
3796                   default:
3797                     endtype = GameUnfinished;
3798                     break;
3799                   case '0':
3800                     endtype = BlackWins;
3801                     break;
3802                   case '1':
3803                     if (endtoken[1] == '/')
3804                       endtype = GameIsDrawn;
3805                     else
3806                       endtype = WhiteWins;
3807                     break;
3808                 }
3809                 GameEnds(endtype, why, GE_ICS);
3810 #if ZIPPY
3811                 if (appData.zippyPlay && first.initDone) {
3812                     ZippyGameEnd(endtype, why);
3813                     if (first.pr == NULL) {
3814                       /* Start the next process early so that we'll
3815                          be ready for the next challenge */
3816                       StartChessProgram(&first);
3817                     }
3818                     /* Send "new" early, in case this command takes
3819                        a long time to finish, so that we'll be ready
3820                        for the next challenge. */
3821                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3822                     Reset(TRUE, TRUE);
3823                 }
3824 #endif /*ZIPPY*/
3825                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3826                 continue;
3827             }
3828
3829             if (looking_at(buf, &i, "Removing game * from observation") ||
3830                 looking_at(buf, &i, "no longer observing game *") ||
3831                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3832                 if (gameMode == IcsObserving &&
3833                     atoi(star_match[0]) == ics_gamenum)
3834                   {
3835                       /* icsEngineAnalyze */
3836                       if (appData.icsEngineAnalyze) {
3837                             ExitAnalyzeMode();
3838                             ModeHighlight();
3839                       }
3840                       StopClocks();
3841                       gameMode = IcsIdle;
3842                       ics_gamenum = -1;
3843                       ics_user_moved = FALSE;
3844                   }
3845                 continue;
3846             }
3847
3848             if (looking_at(buf, &i, "no longer examining game *")) {
3849                 if (gameMode == IcsExamining &&
3850                     atoi(star_match[0]) == ics_gamenum)
3851                   {
3852                       gameMode = IcsIdle;
3853                       ics_gamenum = -1;
3854                       ics_user_moved = FALSE;
3855                   }
3856                 continue;
3857             }
3858
3859             /* Advance leftover_start past any newlines we find,
3860                so only partial lines can get reparsed */
3861             if (looking_at(buf, &i, "\n")) {
3862                 prevColor = curColor;
3863                 if (curColor != ColorNormal) {
3864                     if (oldi > next_out) {
3865                         SendToPlayer(&buf[next_out], oldi - next_out);
3866                         next_out = oldi;
3867                     }
3868                     Colorize(ColorNormal, FALSE);
3869                     curColor = ColorNormal;
3870                 }
3871                 if (started == STARTED_BOARD) {
3872                     started = STARTED_NONE;
3873                     parse[parse_pos] = NULLCHAR;
3874                     ParseBoard12(parse);
3875                     ics_user_moved = 0;
3876
3877                     /* Send premove here */
3878                     if (appData.premove) {
3879                       char str[MSG_SIZ];
3880                       if (currentMove == 0 &&
3881                           gameMode == IcsPlayingWhite &&
3882                           appData.premoveWhite) {
3883                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3884                         if (appData.debugMode)
3885                           fprintf(debugFP, "Sending premove:\n");
3886                         SendToICS(str);
3887                       } else if (currentMove == 1 &&
3888                                  gameMode == IcsPlayingBlack &&
3889                                  appData.premoveBlack) {
3890                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3891                         if (appData.debugMode)
3892                           fprintf(debugFP, "Sending premove:\n");
3893                         SendToICS(str);
3894                       } else if (gotPremove) {
3895                         gotPremove = 0;
3896                         ClearPremoveHighlights();
3897                         if (appData.debugMode)
3898                           fprintf(debugFP, "Sending premove:\n");
3899                           UserMoveEvent(premoveFromX, premoveFromY,
3900                                         premoveToX, premoveToY,
3901                                         premovePromoChar);
3902                       }
3903                     }
3904
3905                     /* Usually suppress following prompt */
3906                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3907                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3908                         if (looking_at(buf, &i, "*% ")) {
3909                             savingComment = FALSE;
3910                             suppressKibitz = 0;
3911                         }
3912                     }
3913                     next_out = i;
3914                 } else if (started == STARTED_HOLDINGS) {
3915                     int gamenum;
3916                     char new_piece[MSG_SIZ];
3917                     started = STARTED_NONE;
3918                     parse[parse_pos] = NULLCHAR;
3919                     if (appData.debugMode)
3920                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3921                                                         parse, currentMove);
3922                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3923                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3924                         if (gameInfo.variant == VariantNormal) {
3925                           /* [HGM] We seem to switch variant during a game!
3926                            * Presumably no holdings were displayed, so we have
3927                            * to move the position two files to the right to
3928                            * create room for them!
3929                            */
3930                           VariantClass newVariant;
3931                           switch(gameInfo.boardWidth) { // base guess on board width
3932                                 case 9:  newVariant = VariantShogi; break;
3933                                 case 10: newVariant = VariantGreat; break;
3934                                 default: newVariant = VariantCrazyhouse; break;
3935                           }
3936                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3937                           /* Get a move list just to see the header, which
3938                              will tell us whether this is really bug or zh */
3939                           if (ics_getting_history == H_FALSE) {
3940                             ics_getting_history = H_REQUESTED;
3941                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3942                             SendToICS(str);
3943                           }
3944                         }
3945                         new_piece[0] = NULLCHAR;
3946                         sscanf(parse, "game %d white [%s black [%s <- %s",
3947                                &gamenum, white_holding, black_holding,
3948                                new_piece);
3949                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3950                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3951                         /* [HGM] copy holdings to board holdings area */
3952                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3953                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3954                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3955 #if ZIPPY
3956                         if (appData.zippyPlay && first.initDone) {
3957                             ZippyHoldings(white_holding, black_holding,
3958                                           new_piece);
3959                         }
3960 #endif /*ZIPPY*/
3961                         if (tinyLayout || smallLayout) {
3962                             char wh[16], bh[16];
3963                             PackHolding(wh, white_holding);
3964                             PackHolding(bh, black_holding);
3965                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3966                                     gameInfo.white, gameInfo.black);
3967                         } else {
3968                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3969                                     gameInfo.white, white_holding,
3970                                     gameInfo.black, black_holding);
3971                         }
3972                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3973                         DrawPosition(FALSE, boards[currentMove]);
3974                         DisplayTitle(str);
3975                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3976                         sscanf(parse, "game %d white [%s black [%s <- %s",
3977                                &gamenum, white_holding, black_holding,
3978                                new_piece);
3979                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3980                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3981                         /* [HGM] copy holdings to partner-board holdings area */
3982                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3983                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3984                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3985                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3986                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3987                       }
3988                     }
3989                     /* Suppress following prompt */
3990                     if (looking_at(buf, &i, "*% ")) {
3991                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3992                         savingComment = FALSE;
3993                         suppressKibitz = 0;
3994                     }
3995                     next_out = i;
3996                 }
3997                 continue;
3998             }
3999
4000             i++;                /* skip unparsed character and loop back */
4001         }
4002
4003         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4004 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4005 //          SendToPlayer(&buf[next_out], i - next_out);
4006             started != STARTED_HOLDINGS && leftover_start > next_out) {
4007             SendToPlayer(&buf[next_out], leftover_start - next_out);
4008             next_out = i;
4009         }
4010
4011         leftover_len = buf_len - leftover_start;
4012         /* if buffer ends with something we couldn't parse,
4013            reparse it after appending the next read */
4014
4015     } else if (count == 0) {
4016         RemoveInputSource(isr);
4017         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4018     } else {
4019         DisplayFatalError(_("Error reading from ICS"), error, 1);
4020     }
4021 }
4022
4023
4024 /* Board style 12 looks like this:
4025
4026    <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
4027
4028  * The "<12> " is stripped before it gets to this routine.  The two
4029  * trailing 0's (flip state and clock ticking) are later addition, and
4030  * some chess servers may not have them, or may have only the first.
4031  * Additional trailing fields may be added in the future.
4032  */
4033
4034 #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"
4035
4036 #define RELATION_OBSERVING_PLAYED    0
4037 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4038 #define RELATION_PLAYING_MYMOVE      1
4039 #define RELATION_PLAYING_NOTMYMOVE  -1
4040 #define RELATION_EXAMINING           2
4041 #define RELATION_ISOLATED_BOARD     -3
4042 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4043
4044 void
4045 ParseBoard12(string)
4046      char *string;
4047 {
4048     GameMode newGameMode;
4049     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4050     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4051     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4052     char to_play, board_chars[200];
4053     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4054     char black[32], white[32];
4055     Board board;
4056     int prevMove = currentMove;
4057     int ticking = 2;
4058     ChessMove moveType;
4059     int fromX, fromY, toX, toY;
4060     char promoChar;
4061     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4062     char *bookHit = NULL; // [HGM] book
4063     Boolean weird = FALSE, reqFlag = FALSE;
4064
4065     fromX = fromY = toX = toY = -1;
4066
4067     newGame = FALSE;
4068
4069     if (appData.debugMode)
4070       fprintf(debugFP, _("Parsing board: %s\n"), string);
4071
4072     move_str[0] = NULLCHAR;
4073     elapsed_time[0] = NULLCHAR;
4074     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4075         int  i = 0, j;
4076         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4077             if(string[i] == ' ') { ranks++; files = 0; }
4078             else files++;
4079             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4080             i++;
4081         }
4082         for(j = 0; j <i; j++) board_chars[j] = string[j];
4083         board_chars[i] = '\0';
4084         string += i + 1;
4085     }
4086     n = sscanf(string, PATTERN, &to_play, &double_push,
4087                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4088                &gamenum, white, black, &relation, &basetime, &increment,
4089                &white_stren, &black_stren, &white_time, &black_time,
4090                &moveNum, str, elapsed_time, move_str, &ics_flip,
4091                &ticking);
4092
4093     if (n < 21) {
4094         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4095         DisplayError(str, 0);
4096         return;
4097     }
4098
4099     /* Convert the move number to internal form */
4100     moveNum = (moveNum - 1) * 2;
4101     if (to_play == 'B') moveNum++;
4102     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4103       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4104                         0, 1);
4105       return;
4106     }
4107
4108     switch (relation) {
4109       case RELATION_OBSERVING_PLAYED:
4110       case RELATION_OBSERVING_STATIC:
4111         if (gamenum == -1) {
4112             /* Old ICC buglet */
4113             relation = RELATION_OBSERVING_STATIC;
4114         }
4115         newGameMode = IcsObserving;
4116         break;
4117       case RELATION_PLAYING_MYMOVE:
4118       case RELATION_PLAYING_NOTMYMOVE:
4119         newGameMode =
4120           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4121             IcsPlayingWhite : IcsPlayingBlack;
4122         break;
4123       case RELATION_EXAMINING:
4124         newGameMode = IcsExamining;
4125         break;
4126       case RELATION_ISOLATED_BOARD:
4127       default:
4128         /* Just display this board.  If user was doing something else,
4129            we will forget about it until the next board comes. */
4130         newGameMode = IcsIdle;
4131         break;
4132       case RELATION_STARTING_POSITION:
4133         newGameMode = gameMode;
4134         break;
4135     }
4136
4137     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4138          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4139       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4140       char *toSqr;
4141       for (k = 0; k < ranks; k++) {
4142         for (j = 0; j < files; j++)
4143           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4144         if(gameInfo.holdingsWidth > 1) {
4145              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4146              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4147         }
4148       }
4149       CopyBoard(partnerBoard, board);
4150       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4151         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4152         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4153       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4154       if(toSqr = strchr(str, '-')) {
4155         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4156         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4157       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4158       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4159       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4160       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4161       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4162       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4163                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4164       DisplayMessage(partnerStatus, "");
4165         partnerBoardValid = TRUE;
4166       return;
4167     }
4168
4169     /* Modify behavior for initial board display on move listing
4170        of wild games.
4171        */
4172     switch (ics_getting_history) {
4173       case H_FALSE:
4174       case H_REQUESTED:
4175         break;
4176       case H_GOT_REQ_HEADER:
4177       case H_GOT_UNREQ_HEADER:
4178         /* This is the initial position of the current game */
4179         gamenum = ics_gamenum;
4180         moveNum = 0;            /* old ICS bug workaround */
4181         if (to_play == 'B') {
4182           startedFromSetupPosition = TRUE;
4183           blackPlaysFirst = TRUE;
4184           moveNum = 1;
4185           if (forwardMostMove == 0) forwardMostMove = 1;
4186           if (backwardMostMove == 0) backwardMostMove = 1;
4187           if (currentMove == 0) currentMove = 1;
4188         }
4189         newGameMode = gameMode;
4190         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4191         break;
4192       case H_GOT_UNWANTED_HEADER:
4193         /* This is an initial board that we don't want */
4194         return;
4195       case H_GETTING_MOVES:
4196         /* Should not happen */
4197         DisplayError(_("Error gathering move list: extra board"), 0);
4198         ics_getting_history = H_FALSE;
4199         return;
4200     }
4201
4202    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4203                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4204      /* [HGM] We seem to have switched variant unexpectedly
4205       * Try to guess new variant from board size
4206       */
4207           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4208           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4209           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4210           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4211           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4212           if(!weird) newVariant = VariantNormal;
4213           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4214           /* Get a move list just to see the header, which
4215              will tell us whether this is really bug or zh */
4216           if (ics_getting_history == H_FALSE) {
4217             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4218             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4219             SendToICS(str);
4220           }
4221     }
4222
4223     /* Take action if this is the first board of a new game, or of a
4224        different game than is currently being displayed.  */
4225     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4226         relation == RELATION_ISOLATED_BOARD) {
4227
4228         /* Forget the old game and get the history (if any) of the new one */
4229         if (gameMode != BeginningOfGame) {
4230           Reset(TRUE, TRUE);
4231         }
4232         newGame = TRUE;
4233         if (appData.autoRaiseBoard) BoardToTop();
4234         prevMove = -3;
4235         if (gamenum == -1) {
4236             newGameMode = IcsIdle;
4237         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4238                    appData.getMoveList && !reqFlag) {
4239             /* Need to get game history */
4240             ics_getting_history = H_REQUESTED;
4241             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242             SendToICS(str);
4243         }
4244
4245         /* Initially flip the board to have black on the bottom if playing
4246            black or if the ICS flip flag is set, but let the user change
4247            it with the Flip View button. */
4248         flipView = appData.autoFlipView ?
4249           (newGameMode == IcsPlayingBlack) || ics_flip :
4250           appData.flipView;
4251
4252         /* Done with values from previous mode; copy in new ones */
4253         gameMode = newGameMode;
4254         ModeHighlight();
4255         ics_gamenum = gamenum;
4256         if (gamenum == gs_gamenum) {
4257             int klen = strlen(gs_kind);
4258             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4259             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4260             gameInfo.event = StrSave(str);
4261         } else {
4262             gameInfo.event = StrSave("ICS game");
4263         }
4264         gameInfo.site = StrSave(appData.icsHost);
4265         gameInfo.date = PGNDate();
4266         gameInfo.round = StrSave("-");
4267         gameInfo.white = StrSave(white);
4268         gameInfo.black = StrSave(black);
4269         timeControl = basetime * 60 * 1000;
4270         timeControl_2 = 0;
4271         timeIncrement = increment * 1000;
4272         movesPerSession = 0;
4273         gameInfo.timeControl = TimeControlTagValue();
4274         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4275   if (appData.debugMode) {
4276     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4277     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4278     setbuf(debugFP, NULL);
4279   }
4280
4281         gameInfo.outOfBook = NULL;
4282
4283         /* Do we have the ratings? */
4284         if (strcmp(player1Name, white) == 0 &&
4285             strcmp(player2Name, black) == 0) {
4286             if (appData.debugMode)
4287               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4288                       player1Rating, player2Rating);
4289             gameInfo.whiteRating = player1Rating;
4290             gameInfo.blackRating = player2Rating;
4291         } else if (strcmp(player2Name, white) == 0 &&
4292                    strcmp(player1Name, black) == 0) {
4293             if (appData.debugMode)
4294               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4295                       player2Rating, player1Rating);
4296             gameInfo.whiteRating = player2Rating;
4297             gameInfo.blackRating = player1Rating;
4298         }
4299         player1Name[0] = player2Name[0] = NULLCHAR;
4300
4301         /* Silence shouts if requested */
4302         if (appData.quietPlay &&
4303             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4304             SendToICS(ics_prefix);
4305             SendToICS("set shout 0\n");
4306         }
4307     }
4308
4309     /* Deal with midgame name changes */
4310     if (!newGame) {
4311         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4312             if (gameInfo.white) free(gameInfo.white);
4313             gameInfo.white = StrSave(white);
4314         }
4315         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4316             if (gameInfo.black) free(gameInfo.black);
4317             gameInfo.black = StrSave(black);
4318         }
4319     }
4320
4321     /* Throw away game result if anything actually changes in examine mode */
4322     if (gameMode == IcsExamining && !newGame) {
4323         gameInfo.result = GameUnfinished;
4324         if (gameInfo.resultDetails != NULL) {
4325             free(gameInfo.resultDetails);
4326             gameInfo.resultDetails = NULL;
4327         }
4328     }
4329
4330     /* In pausing && IcsExamining mode, we ignore boards coming
4331        in if they are in a different variation than we are. */
4332     if (pauseExamInvalid) return;
4333     if (pausing && gameMode == IcsExamining) {
4334         if (moveNum <= pauseExamForwardMostMove) {
4335             pauseExamInvalid = TRUE;
4336             forwardMostMove = pauseExamForwardMostMove;
4337             return;
4338         }
4339     }
4340
4341   if (appData.debugMode) {
4342     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4343   }
4344     /* Parse the board */
4345     for (k = 0; k < ranks; k++) {
4346       for (j = 0; j < files; j++)
4347         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4348       if(gameInfo.holdingsWidth > 1) {
4349            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4350            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4351       }
4352     }
4353     CopyBoard(boards[moveNum], board);
4354     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4355     if (moveNum == 0) {
4356         startedFromSetupPosition =
4357           !CompareBoards(board, initialPosition);
4358         if(startedFromSetupPosition)
4359             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4360     }
4361
4362     /* [HGM] Set castling rights. Take the outermost Rooks,
4363        to make it also work for FRC opening positions. Note that board12
4364        is really defective for later FRC positions, as it has no way to
4365        indicate which Rook can castle if they are on the same side of King.
4366        For the initial position we grant rights to the outermost Rooks,
4367        and remember thos rights, and we then copy them on positions
4368        later in an FRC game. This means WB might not recognize castlings with
4369        Rooks that have moved back to their original position as illegal,
4370        but in ICS mode that is not its job anyway.
4371     */
4372     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4373     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4374
4375         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4376             if(board[0][i] == WhiteRook) j = i;
4377         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4378         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4379             if(board[0][i] == WhiteRook) j = i;
4380         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4381         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4382             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4383         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4384         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4385             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4386         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4387
4388         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4389         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4390             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4391         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4392             if(board[BOARD_HEIGHT-1][k] == bKing)
4393                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4394         if(gameInfo.variant == VariantTwoKings) {
4395             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4396             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4397             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4398         }
4399     } else { int r;
4400         r = boards[moveNum][CASTLING][0] = initialRights[0];
4401         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4402         r = boards[moveNum][CASTLING][1] = initialRights[1];
4403         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4404         r = boards[moveNum][CASTLING][3] = initialRights[3];
4405         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4406         r = boards[moveNum][CASTLING][4] = initialRights[4];
4407         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4408         /* wildcastle kludge: always assume King has rights */
4409         r = boards[moveNum][CASTLING][2] = initialRights[2];
4410         r = boards[moveNum][CASTLING][5] = initialRights[5];
4411     }
4412     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4413     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4414
4415
4416     if (ics_getting_history == H_GOT_REQ_HEADER ||
4417         ics_getting_history == H_GOT_UNREQ_HEADER) {
4418         /* This was an initial position from a move list, not
4419            the current position */
4420         return;
4421     }
4422
4423     /* Update currentMove and known move number limits */
4424     newMove = newGame || moveNum > forwardMostMove;
4425
4426     if (newGame) {
4427         forwardMostMove = backwardMostMove = currentMove = moveNum;
4428         if (gameMode == IcsExamining && moveNum == 0) {
4429           /* Workaround for ICS limitation: we are not told the wild
4430              type when starting to examine a game.  But if we ask for
4431              the move list, the move list header will tell us */
4432             ics_getting_history = H_REQUESTED;
4433             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4434             SendToICS(str);
4435         }
4436     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4437                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4438 #if ZIPPY
4439         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4440         /* [HGM] applied this also to an engine that is silently watching        */
4441         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4442             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4443             gameInfo.variant == currentlyInitializedVariant) {
4444           takeback = forwardMostMove - moveNum;
4445           for (i = 0; i < takeback; i++) {
4446             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4447             SendToProgram("undo\n", &first);
4448           }
4449         }
4450 #endif
4451
4452         forwardMostMove = moveNum;
4453         if (!pausing || currentMove > forwardMostMove)
4454           currentMove = forwardMostMove;
4455     } else {
4456         /* New part of history that is not contiguous with old part */
4457         if (pausing && gameMode == IcsExamining) {
4458             pauseExamInvalid = TRUE;
4459             forwardMostMove = pauseExamForwardMostMove;
4460             return;
4461         }
4462         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4463 #if ZIPPY
4464             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4465                 // [HGM] when we will receive the move list we now request, it will be
4466                 // fed to the engine from the first move on. So if the engine is not
4467                 // in the initial position now, bring it there.
4468                 InitChessProgram(&first, 0);
4469             }
4470 #endif
4471             ics_getting_history = H_REQUESTED;
4472             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4473             SendToICS(str);
4474         }
4475         forwardMostMove = backwardMostMove = currentMove = moveNum;
4476     }
4477
4478     /* Update the clocks */
4479     if (strchr(elapsed_time, '.')) {
4480       /* Time is in ms */
4481       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4482       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4483     } else {
4484       /* Time is in seconds */
4485       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4486       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4487     }
4488
4489
4490 #if ZIPPY
4491     if (appData.zippyPlay && newGame &&
4492         gameMode != IcsObserving && gameMode != IcsIdle &&
4493         gameMode != IcsExamining)
4494       ZippyFirstBoard(moveNum, basetime, increment);
4495 #endif
4496
4497     /* Put the move on the move list, first converting
4498        to canonical algebraic form. */
4499     if (moveNum > 0) {
4500   if (appData.debugMode) {
4501     if (appData.debugMode) { int f = forwardMostMove;
4502         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4503                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4504                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4505     }
4506     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4507     fprintf(debugFP, "moveNum = %d\n", moveNum);
4508     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4509     setbuf(debugFP, NULL);
4510   }
4511         if (moveNum <= backwardMostMove) {
4512             /* We don't know what the board looked like before
4513                this move.  Punt. */
4514           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4515             strcat(parseList[moveNum - 1], " ");
4516             strcat(parseList[moveNum - 1], elapsed_time);
4517             moveList[moveNum - 1][0] = NULLCHAR;
4518         } else if (strcmp(move_str, "none") == 0) {
4519             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4520             /* Again, we don't know what the board looked like;
4521                this is really the start of the game. */
4522             parseList[moveNum - 1][0] = NULLCHAR;
4523             moveList[moveNum - 1][0] = NULLCHAR;
4524             backwardMostMove = moveNum;
4525             startedFromSetupPosition = TRUE;
4526             fromX = fromY = toX = toY = -1;
4527         } else {
4528           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4529           //                 So we parse the long-algebraic move string in stead of the SAN move
4530           int valid; char buf[MSG_SIZ], *prom;
4531
4532           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4533                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4534           // str looks something like "Q/a1-a2"; kill the slash
4535           if(str[1] == '/')
4536             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4537           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4538           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4539                 strcat(buf, prom); // long move lacks promo specification!
4540           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4541                 if(appData.debugMode)
4542                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4543                 safeStrCpy(move_str, buf, MSG_SIZ);
4544           }
4545           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4546                                 &fromX, &fromY, &toX, &toY, &promoChar)
4547                || ParseOneMove(buf, moveNum - 1, &moveType,
4548                                 &fromX, &fromY, &toX, &toY, &promoChar);
4549           // end of long SAN patch
4550           if (valid) {
4551             (void) CoordsToAlgebraic(boards[moveNum - 1],
4552                                      PosFlags(moveNum - 1),
4553                                      fromY, fromX, toY, toX, promoChar,
4554                                      parseList[moveNum-1]);
4555             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4556               case MT_NONE:
4557               case MT_STALEMATE:
4558               default:
4559                 break;
4560               case MT_CHECK:
4561                 if(gameInfo.variant != VariantShogi)
4562                     strcat(parseList[moveNum - 1], "+");
4563                 break;
4564               case MT_CHECKMATE:
4565               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4566                 strcat(parseList[moveNum - 1], "#");
4567                 break;
4568             }
4569             strcat(parseList[moveNum - 1], " ");
4570             strcat(parseList[moveNum - 1], elapsed_time);
4571             /* currentMoveString is set as a side-effect of ParseOneMove */
4572             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4573             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4574             strcat(moveList[moveNum - 1], "\n");
4575
4576             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4577                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4578               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4579                 ChessSquare old, new = boards[moveNum][k][j];
4580                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4581                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4582                   if(old == new) continue;
4583                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4584                   else if(new == WhiteWazir || new == BlackWazir) {
4585                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4586                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4587                       else boards[moveNum][k][j] = old; // preserve type of Gold
4588                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4589                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4590               }
4591           } else {
4592             /* Move from ICS was illegal!?  Punt. */
4593             if (appData.debugMode) {
4594               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4595               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4596             }
4597             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4598             strcat(parseList[moveNum - 1], " ");
4599             strcat(parseList[moveNum - 1], elapsed_time);
4600             moveList[moveNum - 1][0] = NULLCHAR;
4601             fromX = fromY = toX = toY = -1;
4602           }
4603         }
4604   if (appData.debugMode) {
4605     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4606     setbuf(debugFP, NULL);
4607   }
4608
4609 #if ZIPPY
4610         /* Send move to chess program (BEFORE animating it). */
4611         if (appData.zippyPlay && !newGame && newMove &&
4612            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4613
4614             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4615                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4616                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4617                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4618                             move_str);
4619                     DisplayError(str, 0);
4620                 } else {
4621                     if (first.sendTime) {
4622                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4623                     }
4624                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4625                     if (firstMove && !bookHit) {
4626                         firstMove = FALSE;
4627                         if (first.useColors) {
4628                           SendToProgram(gameMode == IcsPlayingWhite ?
4629                                         "white\ngo\n" :
4630                                         "black\ngo\n", &first);
4631                         } else {
4632                           SendToProgram("go\n", &first);
4633                         }
4634                         first.maybeThinking = TRUE;
4635                     }
4636                 }
4637             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4638               if (moveList[moveNum - 1][0] == NULLCHAR) {
4639                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4640                 DisplayError(str, 0);
4641               } else {
4642                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4643                 SendMoveToProgram(moveNum - 1, &first);
4644               }
4645             }
4646         }
4647 #endif
4648     }
4649
4650     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4651         /* If move comes from a remote source, animate it.  If it
4652            isn't remote, it will have already been animated. */
4653         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4654             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4655         }
4656         if (!pausing && appData.highlightLastMove) {
4657             SetHighlights(fromX, fromY, toX, toY);
4658         }
4659     }
4660
4661     /* Start the clocks */
4662     whiteFlag = blackFlag = FALSE;
4663     appData.clockMode = !(basetime == 0 && increment == 0);
4664     if (ticking == 0) {
4665       ics_clock_paused = TRUE;
4666       StopClocks();
4667     } else if (ticking == 1) {
4668       ics_clock_paused = FALSE;
4669     }
4670     if (gameMode == IcsIdle ||
4671         relation == RELATION_OBSERVING_STATIC ||
4672         relation == RELATION_EXAMINING ||
4673         ics_clock_paused)
4674       DisplayBothClocks();
4675     else
4676       StartClocks();
4677
4678     /* Display opponents and material strengths */
4679     if (gameInfo.variant != VariantBughouse &&
4680         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4681         if (tinyLayout || smallLayout) {
4682             if(gameInfo.variant == VariantNormal)
4683               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4684                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4685                     basetime, increment);
4686             else
4687               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4688                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4689                     basetime, increment, (int) gameInfo.variant);
4690         } else {
4691             if(gameInfo.variant == VariantNormal)
4692               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4693                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4694                     basetime, increment);
4695             else
4696               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4697                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4698                     basetime, increment, VariantName(gameInfo.variant));
4699         }
4700         DisplayTitle(str);
4701   if (appData.debugMode) {
4702     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4703   }
4704     }
4705
4706
4707     /* Display the board */
4708     if (!pausing && !appData.noGUI) {
4709
4710       if (appData.premove)
4711           if (!gotPremove ||
4712              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4713              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4714               ClearPremoveHighlights();
4715
4716       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4717         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4718       DrawPosition(j, boards[currentMove]);
4719
4720       DisplayMove(moveNum - 1);
4721       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4722             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4723               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4724         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4725       }
4726     }
4727
4728     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4729 #if ZIPPY
4730     if(bookHit) { // [HGM] book: simulate book reply
4731         static char bookMove[MSG_SIZ]; // a bit generous?
4732
4733         programStats.nodes = programStats.depth = programStats.time =
4734         programStats.score = programStats.got_only_move = 0;
4735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4736
4737         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4738         strcat(bookMove, bookHit);
4739         HandleMachineMove(bookMove, &first);
4740     }
4741 #endif
4742 }
4743
4744 void
4745 GetMoveListEvent()
4746 {
4747     char buf[MSG_SIZ];
4748     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4749         ics_getting_history = H_REQUESTED;
4750         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4751         SendToICS(buf);
4752     }
4753 }
4754
4755 void
4756 AnalysisPeriodicEvent(force)
4757      int force;
4758 {
4759     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4760          && !force) || !appData.periodicUpdates)
4761       return;
4762
4763     /* Send . command to Crafty to collect stats */
4764     SendToProgram(".\n", &first);
4765
4766     /* Don't send another until we get a response (this makes
4767        us stop sending to old Crafty's which don't understand
4768        the "." command (sending illegal cmds resets node count & time,
4769        which looks bad)) */
4770     programStats.ok_to_send = 0;
4771 }
4772
4773 void ics_update_width(new_width)
4774         int new_width;
4775 {
4776         ics_printf("set width %d\n", new_width);
4777 }
4778
4779 void
4780 SendMoveToProgram(moveNum, cps)
4781      int moveNum;
4782      ChessProgramState *cps;
4783 {
4784     char buf[MSG_SIZ];
4785
4786     if (cps->useUsermove) {
4787       SendToProgram("usermove ", cps);
4788     }
4789     if (cps->useSAN) {
4790       char *space;
4791       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4792         int len = space - parseList[moveNum];
4793         memcpy(buf, parseList[moveNum], len);
4794         buf[len++] = '\n';
4795         buf[len] = NULLCHAR;
4796       } else {
4797         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4798       }
4799       SendToProgram(buf, cps);
4800     } else {
4801       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4802         AlphaRank(moveList[moveNum], 4);
4803         SendToProgram(moveList[moveNum], cps);
4804         AlphaRank(moveList[moveNum], 4); // and back
4805       } else
4806       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4807        * the engine. It would be nice to have a better way to identify castle
4808        * moves here. */
4809       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4810                                                                          && cps->useOOCastle) {
4811         int fromX = moveList[moveNum][0] - AAA;
4812         int fromY = moveList[moveNum][1] - ONE;
4813         int toX = moveList[moveNum][2] - AAA;
4814         int toY = moveList[moveNum][3] - ONE;
4815         if((boards[moveNum][fromY][fromX] == WhiteKing
4816             && boards[moveNum][toY][toX] == WhiteRook)
4817            || (boards[moveNum][fromY][fromX] == BlackKing
4818                && boards[moveNum][toY][toX] == BlackRook)) {
4819           if(toX > fromX) SendToProgram("O-O\n", cps);
4820           else SendToProgram("O-O-O\n", cps);
4821         }
4822         else SendToProgram(moveList[moveNum], cps);
4823       }
4824       else SendToProgram(moveList[moveNum], cps);
4825       /* End of additions by Tord */
4826     }
4827
4828     /* [HGM] setting up the opening has brought engine in force mode! */
4829     /*       Send 'go' if we are in a mode where machine should play. */
4830     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4831         (gameMode == TwoMachinesPlay   ||
4832 #if ZIPPY
4833          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4834 #endif
4835          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4836         SendToProgram("go\n", cps);
4837   if (appData.debugMode) {
4838     fprintf(debugFP, "(extra)\n");
4839   }
4840     }
4841     setboardSpoiledMachineBlack = 0;
4842 }
4843
4844 void
4845 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4846      ChessMove moveType;
4847      int fromX, fromY, toX, toY;
4848      char promoChar;
4849 {
4850     char user_move[MSG_SIZ];
4851
4852     switch (moveType) {
4853       default:
4854         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4855                 (int)moveType, fromX, fromY, toX, toY);
4856         DisplayError(user_move + strlen("say "), 0);
4857         break;
4858       case WhiteKingSideCastle:
4859       case BlackKingSideCastle:
4860       case WhiteQueenSideCastleWild:
4861       case BlackQueenSideCastleWild:
4862       /* PUSH Fabien */
4863       case WhiteHSideCastleFR:
4864       case BlackHSideCastleFR:
4865       /* POP Fabien */
4866         snprintf(user_move, MSG_SIZ, "o-o\n");
4867         break;
4868       case WhiteQueenSideCastle:
4869       case BlackQueenSideCastle:
4870       case WhiteKingSideCastleWild:
4871       case BlackKingSideCastleWild:
4872       /* PUSH Fabien */
4873       case WhiteASideCastleFR:
4874       case BlackASideCastleFR:
4875       /* POP Fabien */
4876         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4877         break;
4878       case WhiteNonPromotion:
4879       case BlackNonPromotion:
4880         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4881         break;
4882       case WhitePromotion:
4883       case BlackPromotion:
4884         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4885           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4886                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4887                 PieceToChar(WhiteFerz));
4888         else if(gameInfo.variant == VariantGreat)
4889           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4890                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4891                 PieceToChar(WhiteMan));
4892         else
4893           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4894                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4895                 promoChar);
4896         break;
4897       case WhiteDrop:
4898       case BlackDrop:
4899       drop:
4900         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4901                  ToUpper(PieceToChar((ChessSquare) fromX)),
4902                  AAA + toX, ONE + toY);
4903         break;
4904       case IllegalMove:  /* could be a variant we don't quite understand */
4905         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4906       case NormalMove:
4907       case WhiteCapturesEnPassant:
4908       case BlackCapturesEnPassant:
4909         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4910                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4911         break;
4912     }
4913     SendToICS(user_move);
4914     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4915         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4916 }
4917
4918 void
4919 UploadGameEvent()
4920 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4921     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4922     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4923     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4924         DisplayError("You cannot do this while you are playing or observing", 0);
4925         return;
4926     }
4927     if(gameMode != IcsExamining) { // is this ever not the case?
4928         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4929
4930         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4931           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4932         } else { // on FICS we must first go to general examine mode
4933           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4934         }
4935         if(gameInfo.variant != VariantNormal) {
4936             // try figure out wild number, as xboard names are not always valid on ICS
4937             for(i=1; i<=36; i++) {
4938               snprintf(buf, MSG_SIZ, "wild/%d", i);
4939                 if(StringToVariant(buf) == gameInfo.variant) break;
4940             }
4941             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4942             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4943             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4944         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4945         SendToICS(ics_prefix);
4946         SendToICS(buf);
4947         if(startedFromSetupPosition || backwardMostMove != 0) {
4948           fen = PositionToFEN(backwardMostMove, NULL);
4949           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4950             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4951             SendToICS(buf);
4952           } else { // FICS: everything has to set by separate bsetup commands
4953             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4954             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4955             SendToICS(buf);
4956             if(!WhiteOnMove(backwardMostMove)) {
4957                 SendToICS("bsetup tomove black\n");
4958             }
4959             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4960             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4961             SendToICS(buf);
4962             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4963             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4964             SendToICS(buf);
4965             i = boards[backwardMostMove][EP_STATUS];
4966             if(i >= 0) { // set e.p.
4967               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4968                 SendToICS(buf);
4969             }
4970             bsetup++;
4971           }
4972         }
4973       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4974             SendToICS("bsetup done\n"); // switch to normal examining.
4975     }
4976     for(i = backwardMostMove; i<last; i++) {
4977         char buf[20];
4978         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4979         SendToICS(buf);
4980     }
4981     SendToICS(ics_prefix);
4982     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4983 }
4984
4985 void
4986 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4987      int rf, ff, rt, ft;
4988      char promoChar;
4989      char move[7];
4990 {
4991     if (rf == DROP_RANK) {
4992       sprintf(move, "%c@%c%c\n",
4993                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4994     } else {
4995         if (promoChar == 'x' || promoChar == NULLCHAR) {
4996           sprintf(move, "%c%c%c%c\n",
4997                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4998         } else {
4999             sprintf(move, "%c%c%c%c%c\n",
5000                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5001         }
5002     }
5003 }
5004
5005 void
5006 ProcessICSInitScript(f)
5007      FILE *f;
5008 {
5009     char buf[MSG_SIZ];
5010
5011     while (fgets(buf, MSG_SIZ, f)) {
5012         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5013     }
5014
5015     fclose(f);
5016 }
5017
5018
5019 static int lastX, lastY, selectFlag, dragging;
5020
5021 void
5022 Sweep(int step)
5023 {
5024     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5025     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5026     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5027     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5028     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5029     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5030     do {
5031         promoSweep -= step;
5032         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5033         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5034         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5035         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5036         if(!step) step = 1;
5037     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5038             appData.testLegality && (promoSweep == king ||
5039             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5040     ChangeDragPiece(promoSweep);
5041 }
5042
5043 int PromoScroll(int x, int y)
5044 {
5045   int step = 0;
5046
5047   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5048   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5049   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5050   if(!step) return FALSE;
5051   lastX = x; lastY = y;
5052   if((promoSweep < BlackPawn) == flipView) step = -step;
5053   if(step > 0) selectFlag = 1;
5054   if(!selectFlag) Sweep(step);
5055   return FALSE;
5056 }
5057
5058 void
5059 NextPiece(int step)
5060 {
5061     ChessSquare piece = boards[currentMove][toY][toX];
5062     do {
5063         pieceSweep -= step;
5064         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5065         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5066         if(!step) step = -1;
5067     } while(PieceToChar(pieceSweep) == '.');
5068     boards[currentMove][toY][toX] = pieceSweep;
5069     DrawPosition(FALSE, boards[currentMove]);
5070     boards[currentMove][toY][toX] = piece;
5071 }
5072 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5073 void
5074 AlphaRank(char *move, int n)
5075 {
5076 //    char *p = move, c; int x, y;
5077
5078     if (appData.debugMode) {
5079         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5080     }
5081
5082     if(move[1]=='*' &&
5083        move[2]>='0' && move[2]<='9' &&
5084        move[3]>='a' && move[3]<='x'    ) {
5085         move[1] = '@';
5086         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5087         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5088     } else
5089     if(move[0]>='0' && move[0]<='9' &&
5090        move[1]>='a' && move[1]<='x' &&
5091        move[2]>='0' && move[2]<='9' &&
5092        move[3]>='a' && move[3]<='x'    ) {
5093         /* input move, Shogi -> normal */
5094         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5095         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5096         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5097         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5098     } else
5099     if(move[1]=='@' &&
5100        move[3]>='0' && move[3]<='9' &&
5101        move[2]>='a' && move[2]<='x'    ) {
5102         move[1] = '*';
5103         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5104         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5105     } else
5106     if(
5107        move[0]>='a' && move[0]<='x' &&
5108        move[3]>='0' && move[3]<='9' &&
5109        move[2]>='a' && move[2]<='x'    ) {
5110          /* output move, normal -> Shogi */
5111         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5112         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5113         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5114         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5115         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5116     }
5117     if (appData.debugMode) {
5118         fprintf(debugFP, "   out = '%s'\n", move);
5119     }
5120 }
5121
5122 char yy_textstr[8000];
5123
5124 /* Parser for moves from gnuchess, ICS, or user typein box */
5125 Boolean
5126 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5127      char *move;
5128      int moveNum;
5129      ChessMove *moveType;
5130      int *fromX, *fromY, *toX, *toY;
5131      char *promoChar;
5132 {
5133     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5134
5135     switch (*moveType) {
5136       case WhitePromotion:
5137       case BlackPromotion:
5138       case WhiteNonPromotion:
5139       case BlackNonPromotion:
5140       case NormalMove:
5141       case WhiteCapturesEnPassant:
5142       case BlackCapturesEnPassant:
5143       case WhiteKingSideCastle:
5144       case WhiteQueenSideCastle:
5145       case BlackKingSideCastle:
5146       case BlackQueenSideCastle:
5147       case WhiteKingSideCastleWild:
5148       case WhiteQueenSideCastleWild:
5149       case BlackKingSideCastleWild:
5150       case BlackQueenSideCastleWild:
5151       /* Code added by Tord: */
5152       case WhiteHSideCastleFR:
5153       case WhiteASideCastleFR:
5154       case BlackHSideCastleFR:
5155       case BlackASideCastleFR:
5156       /* End of code added by Tord */
5157       case IllegalMove:         /* bug or odd chess variant */
5158         *fromX = currentMoveString[0] - AAA;
5159         *fromY = currentMoveString[1] - ONE;
5160         *toX = currentMoveString[2] - AAA;
5161         *toY = currentMoveString[3] - ONE;
5162         *promoChar = currentMoveString[4];
5163         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5164             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5165     if (appData.debugMode) {
5166         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5167     }
5168             *fromX = *fromY = *toX = *toY = 0;
5169             return FALSE;
5170         }
5171         if (appData.testLegality) {
5172           return (*moveType != IllegalMove);
5173         } else {
5174           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5175                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5176         }
5177
5178       case WhiteDrop:
5179       case BlackDrop:
5180         *fromX = *moveType == WhiteDrop ?
5181           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5182           (int) CharToPiece(ToLower(currentMoveString[0]));
5183         *fromY = DROP_RANK;
5184         *toX = currentMoveString[2] - AAA;
5185         *toY = currentMoveString[3] - ONE;
5186         *promoChar = NULLCHAR;
5187         return TRUE;
5188
5189       case AmbiguousMove:
5190       case ImpossibleMove:
5191       case EndOfFile:
5192       case ElapsedTime:
5193       case Comment:
5194       case PGNTag:
5195       case NAG:
5196       case WhiteWins:
5197       case BlackWins:
5198       case GameIsDrawn:
5199       default:
5200     if (appData.debugMode) {
5201         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5202     }
5203         /* bug? */
5204         *fromX = *fromY = *toX = *toY = 0;
5205         *promoChar = NULLCHAR;
5206         return FALSE;
5207     }
5208 }
5209
5210
5211 void
5212 ParsePV(char *pv, Boolean storeComments)
5213 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5214   int fromX, fromY, toX, toY; char promoChar;
5215   ChessMove moveType;
5216   Boolean valid;
5217   int nr = 0;
5218
5219   endPV = forwardMostMove;
5220   do {
5221     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5222     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5223     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5224 if(appData.debugMode){
5225 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);
5226 }
5227     if(!valid && nr == 0 &&
5228        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5229         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5230         // Hande case where played move is different from leading PV move
5231         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5232         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5233         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5234         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5235           endPV += 2; // if position different, keep this
5236           moveList[endPV-1][0] = fromX + AAA;
5237           moveList[endPV-1][1] = fromY + ONE;
5238           moveList[endPV-1][2] = toX + AAA;
5239           moveList[endPV-1][3] = toY + ONE;
5240           parseList[endPV-1][0] = NULLCHAR;
5241           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5242         }
5243       }
5244     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5245     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5246     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5247     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5248         valid++; // allow comments in PV
5249         continue;
5250     }
5251     nr++;
5252     if(endPV+1 > framePtr) break; // no space, truncate
5253     if(!valid) break;
5254     endPV++;
5255     CopyBoard(boards[endPV], boards[endPV-1]);
5256     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
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     moveList[endPV-1][4] = promoChar;
5262     moveList[endPV-1][5] = NULLCHAR;
5263     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5264     if(storeComments)
5265         CoordsToAlgebraic(boards[endPV - 1],
5266                              PosFlags(endPV - 1),
5267                              fromY, fromX, toY, toX, promoChar,
5268                              parseList[endPV - 1]);
5269     else
5270         parseList[endPV-1][0] = NULLCHAR;
5271   } while(valid);
5272   currentMove = endPV;
5273   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5274   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5275                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5276   DrawPosition(TRUE, boards[currentMove]);
5277 }
5278
5279 Boolean
5280 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5281 {
5282         int startPV;
5283         char *p;
5284
5285         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5286         lastX = x; lastY = y;
5287         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5288         startPV = index;
5289         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5290         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5291         index = startPV;
5292         do{ while(buf[index] && buf[index] != '\n') index++;
5293         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5294         buf[index] = 0;
5295         ParsePV(buf+startPV, FALSE);
5296         *start = startPV; *end = index-1;
5297         return TRUE;
5298 }
5299
5300 Boolean
5301 LoadPV(int x, int y)
5302 { // called on right mouse click to load PV
5303   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5304   lastX = x; lastY = y;
5305   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5306   return TRUE;
5307 }
5308
5309 void
5310 UnLoadPV()
5311 {
5312   if(endPV < 0) return;
5313   endPV = -1;
5314   currentMove = forwardMostMove;
5315   ClearPremoveHighlights();
5316   DrawPosition(TRUE, boards[currentMove]);
5317 }
5318
5319 void
5320 MovePV(int x, int y, int h)
5321 { // step through PV based on mouse coordinates (called on mouse move)
5322   int margin = h>>3, step = 0;
5323
5324   // we must somehow check if right button is still down (might be released off board!)
5325   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5326   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5327   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5328   if(!step) return;
5329   lastX = x; lastY = y;
5330
5331   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5332   if(endPV < 0) return;
5333   if(y < margin) step = 1; else
5334   if(y > h - margin) step = -1;
5335   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5336   currentMove += step;
5337   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5338   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5339                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5340   DrawPosition(FALSE, boards[currentMove]);
5341 }
5342
5343
5344 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5345 // All positions will have equal probability, but the current method will not provide a unique
5346 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5347 #define DARK 1
5348 #define LITE 2
5349 #define ANY 3
5350
5351 int squaresLeft[4];
5352 int piecesLeft[(int)BlackPawn];
5353 int seed, nrOfShuffles;
5354
5355 void GetPositionNumber()
5356 {       // sets global variable seed
5357         int i;
5358
5359         seed = appData.defaultFrcPosition;
5360         if(seed < 0) { // randomize based on time for negative FRC position numbers
5361                 for(i=0; i<50; i++) seed += random();
5362                 seed = random() ^ random() >> 8 ^ random() << 8;
5363                 if(seed<0) seed = -seed;
5364         }
5365 }
5366
5367 int put(Board board, int pieceType, int rank, int n, int shade)
5368 // put the piece on the (n-1)-th empty squares of the given shade
5369 {
5370         int i;
5371
5372         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5373                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5374                         board[rank][i] = (ChessSquare) pieceType;
5375                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5376                         squaresLeft[ANY]--;
5377                         piecesLeft[pieceType]--;
5378                         return i;
5379                 }
5380         }
5381         return -1;
5382 }
5383
5384
5385 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5386 // calculate where the next piece goes, (any empty square), and put it there
5387 {
5388         int i;
5389
5390         i = seed % squaresLeft[shade];
5391         nrOfShuffles *= squaresLeft[shade];
5392         seed /= squaresLeft[shade];
5393         put(board, pieceType, rank, i, shade);
5394 }
5395
5396 void AddTwoPieces(Board board, int pieceType, int rank)
5397 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5398 {
5399         int i, n=squaresLeft[ANY], j=n-1, k;
5400
5401         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5402         i = seed % k;  // pick one
5403         nrOfShuffles *= k;
5404         seed /= k;
5405         while(i >= j) i -= j--;
5406         j = n - 1 - j; i += j;
5407         put(board, pieceType, rank, j, ANY);
5408         put(board, pieceType, rank, i, ANY);
5409 }
5410
5411 void SetUpShuffle(Board board, int number)
5412 {
5413         int i, p, first=1;
5414
5415         GetPositionNumber(); nrOfShuffles = 1;
5416
5417         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5418         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5419         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5420
5421         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5422
5423         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5424             p = (int) board[0][i];
5425             if(p < (int) BlackPawn) piecesLeft[p] ++;
5426             board[0][i] = EmptySquare;
5427         }
5428
5429         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5430             // shuffles restricted to allow normal castling put KRR first
5431             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5432                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5433             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5434                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5435             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5436                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5437             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5438                 put(board, WhiteRook, 0, 0, ANY);
5439             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5440         }
5441
5442         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5443             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5444             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5445                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5446                 while(piecesLeft[p] >= 2) {
5447                     AddOnePiece(board, p, 0, LITE);
5448                     AddOnePiece(board, p, 0, DARK);
5449                 }
5450                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5451             }
5452
5453         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5454             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5455             // but we leave King and Rooks for last, to possibly obey FRC restriction
5456             if(p == (int)WhiteRook) continue;
5457             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5458             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5459         }
5460
5461         // now everything is placed, except perhaps King (Unicorn) and Rooks
5462
5463         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5464             // Last King gets castling rights
5465             while(piecesLeft[(int)WhiteUnicorn]) {
5466                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5467                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5468             }
5469
5470             while(piecesLeft[(int)WhiteKing]) {
5471                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5472                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5473             }
5474
5475
5476         } else {
5477             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5478             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5479         }
5480
5481         // Only Rooks can be left; simply place them all
5482         while(piecesLeft[(int)WhiteRook]) {
5483                 i = put(board, WhiteRook, 0, 0, ANY);
5484                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5485                         if(first) {
5486                                 first=0;
5487                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5488                         }
5489                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5490                 }
5491         }
5492         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5493             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5494         }
5495
5496         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5497 }
5498
5499 int SetCharTable( char *table, const char * map )
5500 /* [HGM] moved here from winboard.c because of its general usefulness */
5501 /*       Basically a safe strcpy that uses the last character as King */
5502 {
5503     int result = FALSE; int NrPieces;
5504
5505     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5506                     && NrPieces >= 12 && !(NrPieces&1)) {
5507         int i; /* [HGM] Accept even length from 12 to 34 */
5508
5509         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5510         for( i=0; i<NrPieces/2-1; i++ ) {
5511             table[i] = map[i];
5512             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5513         }
5514         table[(int) WhiteKing]  = map[NrPieces/2-1];
5515         table[(int) BlackKing]  = map[NrPieces-1];
5516
5517         result = TRUE;
5518     }
5519
5520     return result;
5521 }
5522
5523 void Prelude(Board board)
5524 {       // [HGM] superchess: random selection of exo-pieces
5525         int i, j, k; ChessSquare p;
5526         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5527
5528         GetPositionNumber(); // use FRC position number
5529
5530         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5531             SetCharTable(pieceToChar, appData.pieceToCharTable);
5532             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5533                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5534         }
5535
5536         j = seed%4;                 seed /= 4;
5537         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5538         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5539         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5540         j = seed%3 + (seed%3 >= j); seed /= 3;
5541         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5542         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5543         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5544         j = seed%3;                 seed /= 3;
5545         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5546         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5547         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5548         j = seed%2 + (seed%2 >= j); seed /= 2;
5549         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5550         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5551         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5552         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5553         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5554         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5555         put(board, exoPieces[0],    0, 0, ANY);
5556         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5557 }
5558
5559 void
5560 InitPosition(redraw)
5561      int redraw;
5562 {
5563     ChessSquare (* pieces)[BOARD_FILES];
5564     int i, j, pawnRow, overrule,
5565     oldx = gameInfo.boardWidth,
5566     oldy = gameInfo.boardHeight,
5567     oldh = gameInfo.holdingsWidth;
5568     static int oldv;
5569
5570     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5571
5572     /* [AS] Initialize pv info list [HGM] and game status */
5573     {
5574         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5575             pvInfoList[i].depth = 0;
5576             boards[i][EP_STATUS] = EP_NONE;
5577             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5578         }
5579
5580         initialRulePlies = 0; /* 50-move counter start */
5581
5582         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5583         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5584     }
5585
5586
5587     /* [HGM] logic here is completely changed. In stead of full positions */
5588     /* the initialized data only consist of the two backranks. The switch */
5589     /* selects which one we will use, which is than copied to the Board   */
5590     /* initialPosition, which for the rest is initialized by Pawns and    */
5591     /* empty squares. This initial position is then copied to boards[0],  */
5592     /* possibly after shuffling, so that it remains available.            */
5593
5594     gameInfo.holdingsWidth = 0; /* default board sizes */
5595     gameInfo.boardWidth    = 8;
5596     gameInfo.boardHeight   = 8;
5597     gameInfo.holdingsSize  = 0;
5598     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5599     for(i=0; i<BOARD_FILES-2; i++)
5600       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5601     initialPosition[EP_STATUS] = EP_NONE;
5602     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5603     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5604          SetCharTable(pieceNickName, appData.pieceNickNames);
5605     else SetCharTable(pieceNickName, "............");
5606     pieces = FIDEArray;
5607
5608     switch (gameInfo.variant) {
5609     case VariantFischeRandom:
5610       shuffleOpenings = TRUE;
5611     default:
5612       break;
5613     case VariantShatranj:
5614       pieces = ShatranjArray;
5615       nrCastlingRights = 0;
5616       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5617       break;
5618     case VariantMakruk:
5619       pieces = makrukArray;
5620       nrCastlingRights = 0;
5621       startedFromSetupPosition = TRUE;
5622       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5623       break;
5624     case VariantTwoKings:
5625       pieces = twoKingsArray;
5626       break;
5627     case VariantCapaRandom:
5628       shuffleOpenings = TRUE;
5629     case VariantCapablanca:
5630       pieces = CapablancaArray;
5631       gameInfo.boardWidth = 10;
5632       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5633       break;
5634     case VariantGothic:
5635       pieces = GothicArray;
5636       gameInfo.boardWidth = 10;
5637       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5638       break;
5639     case VariantSChess:
5640       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5641       gameInfo.holdingsSize = 7;
5642       break;
5643     case VariantJanus:
5644       pieces = JanusArray;
5645       gameInfo.boardWidth = 10;
5646       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5647       nrCastlingRights = 6;
5648         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5649         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5650         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5651         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5652         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5653         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5654       break;
5655     case VariantFalcon:
5656       pieces = FalconArray;
5657       gameInfo.boardWidth = 10;
5658       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5659       break;
5660     case VariantXiangqi:
5661       pieces = XiangqiArray;
5662       gameInfo.boardWidth  = 9;
5663       gameInfo.boardHeight = 10;
5664       nrCastlingRights = 0;
5665       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5666       break;
5667     case VariantShogi:
5668       pieces = ShogiArray;
5669       gameInfo.boardWidth  = 9;
5670       gameInfo.boardHeight = 9;
5671       gameInfo.holdingsSize = 7;
5672       nrCastlingRights = 0;
5673       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5674       break;
5675     case VariantCourier:
5676       pieces = CourierArray;
5677       gameInfo.boardWidth  = 12;
5678       nrCastlingRights = 0;
5679       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5680       break;
5681     case VariantKnightmate:
5682       pieces = KnightmateArray;
5683       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5684       break;
5685     case VariantSpartan:
5686       pieces = SpartanArray;
5687       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5688       break;
5689     case VariantFairy:
5690       pieces = fairyArray;
5691       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5692       break;
5693     case VariantGreat:
5694       pieces = GreatArray;
5695       gameInfo.boardWidth = 10;
5696       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5697       gameInfo.holdingsSize = 8;
5698       break;
5699     case VariantSuper:
5700       pieces = FIDEArray;
5701       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5702       gameInfo.holdingsSize = 8;
5703       startedFromSetupPosition = TRUE;
5704       break;
5705     case VariantCrazyhouse:
5706     case VariantBughouse:
5707       pieces = FIDEArray;
5708       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5709       gameInfo.holdingsSize = 5;
5710       break;
5711     case VariantWildCastle:
5712       pieces = FIDEArray;
5713       /* !!?shuffle with kings guaranteed to be on d or e file */
5714       shuffleOpenings = 1;
5715       break;
5716     case VariantNoCastle:
5717       pieces = FIDEArray;
5718       nrCastlingRights = 0;
5719       /* !!?unconstrained back-rank shuffle */
5720       shuffleOpenings = 1;
5721       break;
5722     }
5723
5724     overrule = 0;
5725     if(appData.NrFiles >= 0) {
5726         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5727         gameInfo.boardWidth = appData.NrFiles;
5728     }
5729     if(appData.NrRanks >= 0) {
5730         gameInfo.boardHeight = appData.NrRanks;
5731     }
5732     if(appData.holdingsSize >= 0) {
5733         i = appData.holdingsSize;
5734         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5735         gameInfo.holdingsSize = i;
5736     }
5737     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5738     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5739         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5740
5741     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5742     if(pawnRow < 1) pawnRow = 1;
5743     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5744
5745     /* User pieceToChar list overrules defaults */
5746     if(appData.pieceToCharTable != NULL)
5747         SetCharTable(pieceToChar, appData.pieceToCharTable);
5748
5749     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5750
5751         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5752             s = (ChessSquare) 0; /* account holding counts in guard band */
5753         for( i=0; i<BOARD_HEIGHT; i++ )
5754             initialPosition[i][j] = s;
5755
5756         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5757         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5758         initialPosition[pawnRow][j] = WhitePawn;
5759         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5760         if(gameInfo.variant == VariantXiangqi) {
5761             if(j&1) {
5762                 initialPosition[pawnRow][j] =
5763                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5764                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5765                    initialPosition[2][j] = WhiteCannon;
5766                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5767                 }
5768             }
5769         }
5770         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5771     }
5772     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5773
5774             j=BOARD_LEFT+1;
5775             initialPosition[1][j] = WhiteBishop;
5776             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5777             j=BOARD_RGHT-2;
5778             initialPosition[1][j] = WhiteRook;
5779             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5780     }
5781
5782     if( nrCastlingRights == -1) {
5783         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5784         /*       This sets default castling rights from none to normal corners   */
5785         /* Variants with other castling rights must set them themselves above    */
5786         nrCastlingRights = 6;
5787
5788         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5789         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5790         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5791         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5792         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5793         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5794      }
5795
5796      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5797      if(gameInfo.variant == VariantGreat) { // promotion commoners
5798         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5799         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5800         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5801         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5802      }
5803      if( gameInfo.variant == VariantSChess ) {
5804       initialPosition[1][0] = BlackMarshall;
5805       initialPosition[2][0] = BlackAngel;
5806       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5807       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5808       initialPosition[1][1] = initialPosition[2][1] = 
5809       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5810      }
5811   if (appData.debugMode) {
5812     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5813   }
5814     if(shuffleOpenings) {
5815         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5816         startedFromSetupPosition = TRUE;
5817     }
5818     if(startedFromPositionFile) {
5819       /* [HGM] loadPos: use PositionFile for every new game */
5820       CopyBoard(initialPosition, filePosition);
5821       for(i=0; i<nrCastlingRights; i++)
5822           initialRights[i] = filePosition[CASTLING][i];
5823       startedFromSetupPosition = TRUE;
5824     }
5825
5826     CopyBoard(boards[0], initialPosition);
5827
5828     if(oldx != gameInfo.boardWidth ||
5829        oldy != gameInfo.boardHeight ||
5830        oldv != gameInfo.variant ||
5831        oldh != gameInfo.holdingsWidth
5832                                          )
5833             InitDrawingSizes(-2 ,0);
5834
5835     oldv = gameInfo.variant;
5836     if (redraw)
5837       DrawPosition(TRUE, boards[currentMove]);
5838 }
5839
5840 void
5841 SendBoard(cps, moveNum)
5842      ChessProgramState *cps;
5843      int moveNum;
5844 {
5845     char message[MSG_SIZ];
5846
5847     if (cps->useSetboard) {
5848       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5849       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5850       SendToProgram(message, cps);
5851       free(fen);
5852
5853     } else {
5854       ChessSquare *bp;
5855       int i, j;
5856       /* Kludge to set black to move, avoiding the troublesome and now
5857        * deprecated "black" command.
5858        */
5859       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5860         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5861
5862       SendToProgram("edit\n", cps);
5863       SendToProgram("#\n", cps);
5864       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5865         bp = &boards[moveNum][i][BOARD_LEFT];
5866         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5867           if ((int) *bp < (int) BlackPawn) {
5868             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5869                     AAA + j, ONE + i);
5870             if(message[0] == '+' || message[0] == '~') {
5871               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5872                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5873                         AAA + j, ONE + i);
5874             }
5875             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5876                 message[1] = BOARD_RGHT   - 1 - j + '1';
5877                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5878             }
5879             SendToProgram(message, cps);
5880           }
5881         }
5882       }
5883
5884       SendToProgram("c\n", cps);
5885       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5886         bp = &boards[moveNum][i][BOARD_LEFT];
5887         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5888           if (((int) *bp != (int) EmptySquare)
5889               && ((int) *bp >= (int) BlackPawn)) {
5890             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(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(".\n", cps);
5907     }
5908     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5909 }
5910
5911 ChessSquare
5912 DefaultPromoChoice(int white)
5913 {
5914     ChessSquare result;
5915     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5916         result = WhiteFerz; // no choice
5917     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5918         result= WhiteKing; // in Suicide Q is the last thing we want
5919     else if(gameInfo.variant == VariantSpartan)
5920         result = white ? WhiteQueen : WhiteAngel;
5921     else result = WhiteQueen;
5922     if(!white) result = WHITE_TO_BLACK result;
5923     return result;
5924 }
5925
5926 static int autoQueen; // [HGM] oneclick
5927
5928 int
5929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5930 {
5931     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5932     /* [HGM] add Shogi promotions */
5933     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5934     ChessSquare piece;
5935     ChessMove moveType;
5936     Boolean premove;
5937
5938     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5939     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5940
5941     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5942       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5943         return FALSE;
5944
5945     piece = boards[currentMove][fromY][fromX];
5946     if(gameInfo.variant == VariantShogi) {
5947         promotionZoneSize = BOARD_HEIGHT/3;
5948         highestPromotingPiece = (int)WhiteFerz;
5949     } else if(gameInfo.variant == VariantMakruk) {
5950         promotionZoneSize = 3;
5951     }
5952
5953     // Treat Lance as Pawn when it is not representing Amazon
5954     if(gameInfo.variant != VariantSuper) {
5955         if(piece == WhiteLance) piece = WhitePawn; else
5956         if(piece == BlackLance) piece = BlackPawn;
5957     }
5958
5959     // next weed out all moves that do not touch the promotion zone at all
5960     if((int)piece >= BlackPawn) {
5961         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5962              return FALSE;
5963         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5964     } else {
5965         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5966            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5967     }
5968
5969     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5970
5971     // weed out mandatory Shogi promotions
5972     if(gameInfo.variant == VariantShogi) {
5973         if(piece >= BlackPawn) {
5974             if(toY == 0 && piece == BlackPawn ||
5975                toY == 0 && piece == BlackQueen ||
5976                toY <= 1 && piece == BlackKnight) {
5977                 *promoChoice = '+';
5978                 return FALSE;
5979             }
5980         } else {
5981             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5982                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5983                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5984                 *promoChoice = '+';
5985                 return FALSE;
5986             }
5987         }
5988     }
5989
5990     // weed out obviously illegal Pawn moves
5991     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5992         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5993         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5994         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5995         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5996         // note we are not allowed to test for valid (non-)capture, due to premove
5997     }
5998
5999     // we either have a choice what to promote to, or (in Shogi) whether to promote
6000     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6001         *promoChoice = PieceToChar(BlackFerz);  // no choice
6002         return FALSE;
6003     }
6004     // no sense asking what we must promote to if it is going to explode...
6005     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6006         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6007         return FALSE;
6008     }
6009     // give caller the default choice even if we will not make it
6010     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6011     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6012     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6013                            && gameInfo.variant != VariantShogi
6014                            && gameInfo.variant != VariantSuper) return FALSE;
6015     if(autoQueen) return FALSE; // predetermined
6016
6017     // suppress promotion popup on illegal moves that are not premoves
6018     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6019               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6020     if(appData.testLegality && !premove) {
6021         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6022                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6023         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6024             return FALSE;
6025     }
6026
6027     return TRUE;
6028 }
6029
6030 int
6031 InPalace(row, column)
6032      int row, column;
6033 {   /* [HGM] for Xiangqi */
6034     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6035          column < (BOARD_WIDTH + 4)/2 &&
6036          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6037     return FALSE;
6038 }
6039
6040 int
6041 PieceForSquare (x, y)
6042      int x;
6043      int y;
6044 {
6045   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6046      return -1;
6047   else
6048      return boards[currentMove][y][x];
6049 }
6050
6051 int
6052 OKToStartUserMove(x, y)
6053      int x, y;
6054 {
6055     ChessSquare from_piece;
6056     int white_piece;
6057
6058     if (matchMode) return FALSE;
6059     if (gameMode == EditPosition) return TRUE;
6060
6061     if (x >= 0 && y >= 0)
6062       from_piece = boards[currentMove][y][x];
6063     else
6064       from_piece = EmptySquare;
6065
6066     if (from_piece == EmptySquare) return FALSE;
6067
6068     white_piece = (int)from_piece >= (int)WhitePawn &&
6069       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6070
6071     switch (gameMode) {
6072       case PlayFromGameFile:
6073       case AnalyzeFile:
6074       case TwoMachinesPlay:
6075       case EndOfGame:
6076         return FALSE;
6077
6078       case IcsObserving:
6079       case IcsIdle:
6080         return FALSE;
6081
6082       case MachinePlaysWhite:
6083       case IcsPlayingBlack:
6084         if (appData.zippyPlay) return FALSE;
6085         if (white_piece) {
6086             DisplayMoveError(_("You are playing Black"));
6087             return FALSE;
6088         }
6089         break;
6090
6091       case MachinePlaysBlack:
6092       case IcsPlayingWhite:
6093         if (appData.zippyPlay) return FALSE;
6094         if (!white_piece) {
6095             DisplayMoveError(_("You are playing White"));
6096             return FALSE;
6097         }
6098         break;
6099
6100       case EditGame:
6101         if (!white_piece && WhiteOnMove(currentMove)) {
6102             DisplayMoveError(_("It is White's turn"));
6103             return FALSE;
6104         }
6105         if (white_piece && !WhiteOnMove(currentMove)) {
6106             DisplayMoveError(_("It is Black's turn"));
6107             return FALSE;
6108         }
6109         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6110             /* Editing correspondence game history */
6111             /* Could disallow this or prompt for confirmation */
6112             cmailOldMove = -1;
6113         }
6114         break;
6115
6116       case BeginningOfGame:
6117         if (appData.icsActive) return FALSE;
6118         if (!appData.noChessProgram) {
6119             if (!white_piece) {
6120                 DisplayMoveError(_("You are playing White"));
6121                 return FALSE;
6122             }
6123         }
6124         break;
6125
6126       case Training:
6127         if (!white_piece && WhiteOnMove(currentMove)) {
6128             DisplayMoveError(_("It is White's turn"));
6129             return FALSE;
6130         }
6131         if (white_piece && !WhiteOnMove(currentMove)) {
6132             DisplayMoveError(_("It is Black's turn"));
6133             return FALSE;
6134         }
6135         break;
6136
6137       default:
6138       case IcsExamining:
6139         break;
6140     }
6141     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6142         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6143         && gameMode != AnalyzeFile && gameMode != Training) {
6144         DisplayMoveError(_("Displayed position is not current"));
6145         return FALSE;
6146     }
6147     return TRUE;
6148 }
6149
6150 Boolean
6151 OnlyMove(int *x, int *y, Boolean captures) {
6152     DisambiguateClosure cl;
6153     if (appData.zippyPlay) return FALSE;
6154     switch(gameMode) {
6155       case MachinePlaysBlack:
6156       case IcsPlayingWhite:
6157       case BeginningOfGame:
6158         if(!WhiteOnMove(currentMove)) return FALSE;
6159         break;
6160       case MachinePlaysWhite:
6161       case IcsPlayingBlack:
6162         if(WhiteOnMove(currentMove)) return FALSE;
6163         break;
6164       case EditGame:
6165         break;
6166       default:
6167         return FALSE;
6168     }
6169     cl.pieceIn = EmptySquare;
6170     cl.rfIn = *y;
6171     cl.ffIn = *x;
6172     cl.rtIn = -1;
6173     cl.ftIn = -1;
6174     cl.promoCharIn = NULLCHAR;
6175     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6176     if( cl.kind == NormalMove ||
6177         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6178         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6179         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6180       fromX = cl.ff;
6181       fromY = cl.rf;
6182       *x = cl.ft;
6183       *y = cl.rt;
6184       return TRUE;
6185     }
6186     if(cl.kind != ImpossibleMove) return FALSE;
6187     cl.pieceIn = EmptySquare;
6188     cl.rfIn = -1;
6189     cl.ffIn = -1;
6190     cl.rtIn = *y;
6191     cl.ftIn = *x;
6192     cl.promoCharIn = NULLCHAR;
6193     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6194     if( cl.kind == NormalMove ||
6195         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6196         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6197         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6198       fromX = cl.ff;
6199       fromY = cl.rf;
6200       *x = cl.ft;
6201       *y = cl.rt;
6202       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6203       return TRUE;
6204     }
6205     return FALSE;
6206 }
6207
6208 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6209 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6210 int lastLoadGameUseList = FALSE;
6211 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6212 ChessMove lastLoadGameStart = EndOfFile;
6213
6214 void
6215 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6216      int fromX, fromY, toX, toY;
6217      int promoChar;
6218 {
6219     ChessMove moveType;
6220     ChessSquare pdown, pup;
6221
6222     /* Check if the user is playing in turn.  This is complicated because we
6223        let the user "pick up" a piece before it is his turn.  So the piece he
6224        tried to pick up may have been captured by the time he puts it down!
6225        Therefore we use the color the user is supposed to be playing in this
6226        test, not the color of the piece that is currently on the starting
6227        square---except in EditGame mode, where the user is playing both
6228        sides; fortunately there the capture race can't happen.  (It can
6229        now happen in IcsExamining mode, but that's just too bad.  The user
6230        will get a somewhat confusing message in that case.)
6231        */
6232
6233     switch (gameMode) {
6234       case PlayFromGameFile:
6235       case AnalyzeFile:
6236       case TwoMachinesPlay:
6237       case EndOfGame:
6238       case IcsObserving:
6239       case IcsIdle:
6240         /* We switched into a game mode where moves are not accepted,
6241            perhaps while the mouse button was down. */
6242         return;
6243
6244       case MachinePlaysWhite:
6245         /* User is moving for Black */
6246         if (WhiteOnMove(currentMove)) {
6247             DisplayMoveError(_("It is White's turn"));
6248             return;
6249         }
6250         break;
6251
6252       case MachinePlaysBlack:
6253         /* User is moving for White */
6254         if (!WhiteOnMove(currentMove)) {
6255             DisplayMoveError(_("It is Black's turn"));
6256             return;
6257         }
6258         break;
6259
6260       case EditGame:
6261       case IcsExamining:
6262       case BeginningOfGame:
6263       case AnalyzeMode:
6264       case Training:
6265         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6266         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6267             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6268             /* User is moving for Black */
6269             if (WhiteOnMove(currentMove)) {
6270                 DisplayMoveError(_("It is White's turn"));
6271                 return;
6272             }
6273         } else {
6274             /* User is moving for White */
6275             if (!WhiteOnMove(currentMove)) {
6276                 DisplayMoveError(_("It is Black's turn"));
6277                 return;
6278             }
6279         }
6280         break;
6281
6282       case IcsPlayingBlack:
6283         /* User is moving for Black */
6284         if (WhiteOnMove(currentMove)) {
6285             if (!appData.premove) {
6286                 DisplayMoveError(_("It is White's turn"));
6287             } else if (toX >= 0 && toY >= 0) {
6288                 premoveToX = toX;
6289                 premoveToY = toY;
6290                 premoveFromX = fromX;
6291                 premoveFromY = fromY;
6292                 premovePromoChar = promoChar;
6293                 gotPremove = 1;
6294                 if (appData.debugMode)
6295                     fprintf(debugFP, "Got premove: fromX %d,"
6296                             "fromY %d, toX %d, toY %d\n",
6297                             fromX, fromY, toX, toY);
6298             }
6299             return;
6300         }
6301         break;
6302
6303       case IcsPlayingWhite:
6304         /* User is moving for White */
6305         if (!WhiteOnMove(currentMove)) {
6306             if (!appData.premove) {
6307                 DisplayMoveError(_("It is Black's turn"));
6308             } else if (toX >= 0 && toY >= 0) {
6309                 premoveToX = toX;
6310                 premoveToY = toY;
6311                 premoveFromX = fromX;
6312                 premoveFromY = fromY;
6313                 premovePromoChar = promoChar;
6314                 gotPremove = 1;
6315                 if (appData.debugMode)
6316                     fprintf(debugFP, "Got premove: fromX %d,"
6317                             "fromY %d, toX %d, toY %d\n",
6318                             fromX, fromY, toX, toY);
6319             }
6320             return;
6321         }
6322         break;
6323
6324       default:
6325         break;
6326
6327       case EditPosition:
6328         /* EditPosition, empty square, or different color piece;
6329            click-click move is possible */
6330         if (toX == -2 || toY == -2) {
6331             boards[0][fromY][fromX] = EmptySquare;
6332             DrawPosition(FALSE, boards[currentMove]);
6333             return;
6334         } else if (toX >= 0 && toY >= 0) {
6335             boards[0][toY][toX] = boards[0][fromY][fromX];
6336             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6337                 if(boards[0][fromY][0] != EmptySquare) {
6338                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6339                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6340                 }
6341             } else
6342             if(fromX == BOARD_RGHT+1) {
6343                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6344                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6345                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6346                 }
6347             } else
6348             boards[0][fromY][fromX] = EmptySquare;
6349             DrawPosition(FALSE, boards[currentMove]);
6350             return;
6351         }
6352         return;
6353     }
6354
6355     if(toX < 0 || toY < 0) return;
6356     pdown = boards[currentMove][fromY][fromX];
6357     pup = boards[currentMove][toY][toX];
6358
6359     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6360     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6361          if( pup != EmptySquare ) return;
6362          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6363            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6364                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6365            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6366            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6367            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6368            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6369          fromY = DROP_RANK;
6370     }
6371
6372     /* [HGM] always test for legality, to get promotion info */
6373     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6374                                          fromY, fromX, toY, toX, promoChar);
6375     /* [HGM] but possibly ignore an IllegalMove result */
6376     if (appData.testLegality) {
6377         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6378             DisplayMoveError(_("Illegal move"));
6379             return;
6380         }
6381     }
6382
6383     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6384 }
6385
6386 /* Common tail of UserMoveEvent and DropMenuEvent */
6387 int
6388 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6389      ChessMove moveType;
6390      int fromX, fromY, toX, toY;
6391      /*char*/int promoChar;
6392 {
6393     char *bookHit = 0;
6394
6395     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6396         // [HGM] superchess: suppress promotions to non-available piece
6397         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6398         if(WhiteOnMove(currentMove)) {
6399             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6400         } else {
6401             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6402         }
6403     }
6404
6405     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6406        move type in caller when we know the move is a legal promotion */
6407     if(moveType == NormalMove && promoChar)
6408         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6409
6410     /* [HGM] <popupFix> The following if has been moved here from
6411        UserMoveEvent(). Because it seemed to belong here (why not allow
6412        piece drops in training games?), and because it can only be
6413        performed after it is known to what we promote. */
6414     if (gameMode == Training) {
6415       /* compare the move played on the board to the next move in the
6416        * game. If they match, display the move and the opponent's response.
6417        * If they don't match, display an error message.
6418        */
6419       int saveAnimate;
6420       Board testBoard;
6421       CopyBoard(testBoard, boards[currentMove]);
6422       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6423
6424       if (CompareBoards(testBoard, boards[currentMove+1])) {
6425         ForwardInner(currentMove+1);
6426
6427         /* Autoplay the opponent's response.
6428          * if appData.animate was TRUE when Training mode was entered,
6429          * the response will be animated.
6430          */
6431         saveAnimate = appData.animate;
6432         appData.animate = animateTraining;
6433         ForwardInner(currentMove+1);
6434         appData.animate = saveAnimate;
6435
6436         /* check for the end of the game */
6437         if (currentMove >= forwardMostMove) {
6438           gameMode = PlayFromGameFile;
6439           ModeHighlight();
6440           SetTrainingModeOff();
6441           DisplayInformation(_("End of game"));
6442         }
6443       } else {
6444         DisplayError(_("Incorrect move"), 0);
6445       }
6446       return 1;
6447     }
6448
6449   /* Ok, now we know that the move is good, so we can kill
6450      the previous line in Analysis Mode */
6451   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6452                                 && currentMove < forwardMostMove) {
6453     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6454     else forwardMostMove = currentMove;
6455   }
6456
6457   /* If we need the chess program but it's dead, restart it */
6458   ResurrectChessProgram();
6459
6460   /* A user move restarts a paused game*/
6461   if (pausing)
6462     PauseEvent();
6463
6464   thinkOutput[0] = NULLCHAR;
6465
6466   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6467
6468   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6469     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6470     return 1;
6471   }
6472
6473   if (gameMode == BeginningOfGame) {
6474     if (appData.noChessProgram) {
6475       gameMode = EditGame;
6476       SetGameInfo();
6477     } else {
6478       char buf[MSG_SIZ];
6479       gameMode = MachinePlaysBlack;
6480       StartClocks();
6481       SetGameInfo();
6482       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6483       DisplayTitle(buf);
6484       if (first.sendName) {
6485         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6486         SendToProgram(buf, &first);
6487       }
6488       StartClocks();
6489     }
6490     ModeHighlight();
6491   }
6492
6493   /* Relay move to ICS or chess engine */
6494   if (appData.icsActive) {
6495     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6496         gameMode == IcsExamining) {
6497       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6498         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6499         SendToICS("draw ");
6500         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6501       }
6502       // also send plain move, in case ICS does not understand atomic claims
6503       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6504       ics_user_moved = 1;
6505     }
6506   } else {
6507     if (first.sendTime && (gameMode == BeginningOfGame ||
6508                            gameMode == MachinePlaysWhite ||
6509                            gameMode == MachinePlaysBlack)) {
6510       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6511     }
6512     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6513          // [HGM] book: if program might be playing, let it use book
6514         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6515         first.maybeThinking = TRUE;
6516     } else SendMoveToProgram(forwardMostMove-1, &first);
6517     if (currentMove == cmailOldMove + 1) {
6518       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6519     }
6520   }
6521
6522   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6523
6524   switch (gameMode) {
6525   case EditGame:
6526     if(appData.testLegality)
6527     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6528     case MT_NONE:
6529     case MT_CHECK:
6530       break;
6531     case MT_CHECKMATE:
6532     case MT_STAINMATE:
6533       if (WhiteOnMove(currentMove)) {
6534         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6535       } else {
6536         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6537       }
6538       break;
6539     case MT_STALEMATE:
6540       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6541       break;
6542     }
6543     break;
6544
6545   case MachinePlaysBlack:
6546   case MachinePlaysWhite:
6547     /* disable certain menu options while machine is thinking */
6548     SetMachineThinkingEnables();
6549     break;
6550
6551   default:
6552     break;
6553   }
6554
6555   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6556   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6557
6558   if(bookHit) { // [HGM] book: simulate book reply
6559         static char bookMove[MSG_SIZ]; // a bit generous?
6560
6561         programStats.nodes = programStats.depth = programStats.time =
6562         programStats.score = programStats.got_only_move = 0;
6563         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6564
6565         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6566         strcat(bookMove, bookHit);
6567         HandleMachineMove(bookMove, &first);
6568   }
6569   return 1;
6570 }
6571
6572 void
6573 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6574      Board board;
6575      int flags;
6576      ChessMove kind;
6577      int rf, ff, rt, ft;
6578      VOIDSTAR closure;
6579 {
6580     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6581     Markers *m = (Markers *) closure;
6582     if(rf == fromY && ff == fromX)
6583         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6584                          || kind == WhiteCapturesEnPassant
6585                          || kind == BlackCapturesEnPassant);
6586     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6587 }
6588
6589 void
6590 MarkTargetSquares(int clear)
6591 {
6592   int x, y;
6593   if(!appData.markers || !appData.highlightDragging ||
6594      !appData.testLegality || gameMode == EditPosition) return;
6595   if(clear) {
6596     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6597   } else {
6598     int capt = 0;
6599     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6600     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6601       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6602       if(capt)
6603       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6604     }
6605   }
6606   DrawPosition(TRUE, NULL);
6607 }
6608
6609 int
6610 Explode(Board board, int fromX, int fromY, int toX, int toY)
6611 {
6612     if(gameInfo.variant == VariantAtomic &&
6613        (board[toY][toX] != EmptySquare ||                     // capture?
6614         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6615                          board[fromY][fromX] == BlackPawn   )
6616       )) {
6617         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6618         return TRUE;
6619     }
6620     return FALSE;
6621 }
6622
6623 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6624
6625 int CanPromote(ChessSquare piece, int y)
6626 {
6627         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6628         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6629         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6630            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6631            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6632                                                   gameInfo.variant == VariantMakruk) return FALSE;
6633         return (piece == BlackPawn && y == 1 ||
6634                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6635                 piece == BlackLance && y == 1 ||
6636                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6637 }
6638
6639 void LeftClick(ClickType clickType, int xPix, int yPix)
6640 {
6641     int x, y;
6642     Boolean saveAnimate;
6643     static int second = 0, promotionChoice = 0, clearFlag = 0;
6644     char promoChoice = NULLCHAR;
6645     ChessSquare piece;
6646
6647     if(appData.seekGraph && appData.icsActive && loggedOn &&
6648         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6649         SeekGraphClick(clickType, xPix, yPix, 0);
6650         return;
6651     }
6652
6653     if (clickType == Press) ErrorPopDown();
6654     MarkTargetSquares(1);
6655
6656     x = EventToSquare(xPix, BOARD_WIDTH);
6657     y = EventToSquare(yPix, BOARD_HEIGHT);
6658     if (!flipView && y >= 0) {
6659         y = BOARD_HEIGHT - 1 - y;
6660     }
6661     if (flipView && x >= 0) {
6662         x = BOARD_WIDTH - 1 - x;
6663     }
6664
6665     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6666         defaultPromoChoice = promoSweep;
6667         promoSweep = EmptySquare;   // terminate sweep
6668         promoDefaultAltered = TRUE;
6669         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6670     }
6671
6672     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6673         if(clickType == Release) return; // ignore upclick of click-click destination
6674         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6675         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6676         if(gameInfo.holdingsWidth &&
6677                 (WhiteOnMove(currentMove)
6678                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6679                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6680             // click in right holdings, for determining promotion piece
6681             ChessSquare p = boards[currentMove][y][x];
6682             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6683             if(p != EmptySquare) {
6684                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6685                 fromX = fromY = -1;
6686                 return;
6687             }
6688         }
6689         DrawPosition(FALSE, boards[currentMove]);
6690         return;
6691     }
6692
6693     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6694     if(clickType == Press
6695             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6696               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6697               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6698         return;
6699
6700     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6701         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6702
6703     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6704         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6705                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6706         defaultPromoChoice = DefaultPromoChoice(side);
6707     }
6708
6709     autoQueen = appData.alwaysPromoteToQueen;
6710
6711     if (fromX == -1) {
6712       int originalY = y;
6713       gatingPiece = EmptySquare;
6714       if (clickType != Press) {
6715         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6716             DragPieceEnd(xPix, yPix); dragging = 0;
6717             DrawPosition(FALSE, NULL);
6718         }
6719         return;
6720       }
6721       fromX = x; fromY = y;
6722       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6723          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6724          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6725             /* First square */
6726             if (OKToStartUserMove(fromX, fromY)) {
6727                 second = 0;
6728                 MarkTargetSquares(0);
6729                 DragPieceBegin(xPix, yPix); dragging = 1;
6730                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6731                     promoSweep = defaultPromoChoice;
6732                     selectFlag = 0; lastX = xPix; lastY = yPix;
6733                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6734                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6735                 }
6736                 if (appData.highlightDragging) {
6737                     SetHighlights(fromX, fromY, -1, -1);
6738                 }
6739             } else fromX = fromY = -1;
6740             return;
6741         }
6742     }
6743
6744     /* fromX != -1 */
6745     if (clickType == Press && gameMode != EditPosition) {
6746         ChessSquare fromP;
6747         ChessSquare toP;
6748         int frc;
6749
6750         // ignore off-board to clicks
6751         if(y < 0 || x < 0) return;
6752
6753         /* Check if clicking again on the same color piece */
6754         fromP = boards[currentMove][fromY][fromX];
6755         toP = boards[currentMove][y][x];
6756         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6757         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6758              WhitePawn <= toP && toP <= WhiteKing &&
6759              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6760              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6761             (BlackPawn <= fromP && fromP <= BlackKing &&
6762              BlackPawn <= toP && toP <= BlackKing &&
6763              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6764              !(fromP == BlackKing && toP == BlackRook && frc))) {
6765             /* Clicked again on same color piece -- changed his mind */
6766             second = (x == fromX && y == fromY);
6767             promoDefaultAltered = FALSE;
6768            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6769             if (appData.highlightDragging) {
6770                 SetHighlights(x, y, -1, -1);
6771             } else {
6772                 ClearHighlights();
6773             }
6774             if (OKToStartUserMove(x, y)) {
6775                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6776                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6777                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6778                  gatingPiece = boards[currentMove][fromY][fromX];
6779                 else gatingPiece = EmptySquare;
6780                 fromX = x;
6781                 fromY = y; dragging = 1;
6782                 MarkTargetSquares(0);
6783                 DragPieceBegin(xPix, yPix);
6784                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6785                     promoSweep = defaultPromoChoice;
6786                     selectFlag = 0; lastX = xPix; lastY = yPix;
6787                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6788                 }
6789             }
6790            }
6791            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6792            second = FALSE; 
6793         }
6794         // ignore clicks on holdings
6795         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6796     }
6797
6798     if (clickType == Release && x == fromX && y == fromY) {
6799         DragPieceEnd(xPix, yPix); dragging = 0;
6800         if(clearFlag) {
6801             // a deferred attempt to click-click move an empty square on top of a piece
6802             boards[currentMove][y][x] = EmptySquare;
6803             ClearHighlights();
6804             DrawPosition(FALSE, boards[currentMove]);
6805             fromX = fromY = -1; clearFlag = 0;
6806             return;
6807         }
6808         if (appData.animateDragging) {
6809             /* Undo animation damage if any */
6810             DrawPosition(FALSE, NULL);
6811         }
6812         if (second) {
6813             /* Second up/down in same square; just abort move */
6814             second = 0;
6815             fromX = fromY = -1;
6816             gatingPiece = EmptySquare;
6817             ClearHighlights();
6818             gotPremove = 0;
6819             ClearPremoveHighlights();
6820         } else {
6821             /* First upclick in same square; start click-click mode */
6822             SetHighlights(x, y, -1, -1);
6823         }
6824         return;
6825     }
6826
6827     clearFlag = 0;
6828
6829     /* we now have a different from- and (possibly off-board) to-square */
6830     /* Completed move */
6831     toX = x;
6832     toY = y;
6833     saveAnimate = appData.animate;
6834     if (clickType == Press) {
6835         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6836             // must be Edit Position mode with empty-square selected
6837             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6838             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6839             return;
6840         }
6841         /* Finish clickclick move */
6842         if (appData.animate || appData.highlightLastMove) {
6843             SetHighlights(fromX, fromY, toX, toY);
6844         } else {
6845             ClearHighlights();
6846         }
6847     } else {
6848         /* Finish drag move */
6849         if (appData.highlightLastMove) {
6850             SetHighlights(fromX, fromY, toX, toY);
6851         } else {
6852             ClearHighlights();
6853         }
6854         DragPieceEnd(xPix, yPix); dragging = 0;
6855         /* Don't animate move and drag both */
6856         appData.animate = FALSE;
6857     }
6858
6859     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6860     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6861         ChessSquare piece = boards[currentMove][fromY][fromX];
6862         if(gameMode == EditPosition && piece != EmptySquare &&
6863            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6864             int n;
6865
6866             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6867                 n = PieceToNumber(piece - (int)BlackPawn);
6868                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6869                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6870                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6871             } else
6872             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6873                 n = PieceToNumber(piece);
6874                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6875                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6876                 boards[currentMove][n][BOARD_WIDTH-2]++;
6877             }
6878             boards[currentMove][fromY][fromX] = EmptySquare;
6879         }
6880         ClearHighlights();
6881         fromX = fromY = -1;
6882         DrawPosition(TRUE, boards[currentMove]);
6883         return;
6884     }
6885
6886     // off-board moves should not be highlighted
6887     if(x < 0 || y < 0) ClearHighlights();
6888
6889     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6890
6891     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6892         SetHighlights(fromX, fromY, toX, toY);
6893         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6894             // [HGM] super: promotion to captured piece selected from holdings
6895             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6896             promotionChoice = TRUE;
6897             // kludge follows to temporarily execute move on display, without promoting yet
6898             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6899             boards[currentMove][toY][toX] = p;
6900             DrawPosition(FALSE, boards[currentMove]);
6901             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6902             boards[currentMove][toY][toX] = q;
6903             DisplayMessage("Click in holdings to choose piece", "");
6904             return;
6905         }
6906         PromotionPopUp();
6907     } else {
6908         int oldMove = currentMove;
6909         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6910         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6911         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6912         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6913            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6914             DrawPosition(TRUE, boards[currentMove]);
6915         fromX = fromY = -1;
6916     }
6917     appData.animate = saveAnimate;
6918     if (appData.animate || appData.animateDragging) {
6919         /* Undo animation damage if needed */
6920         DrawPosition(FALSE, NULL);
6921     }
6922 }
6923
6924 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6925 {   // front-end-free part taken out of PieceMenuPopup
6926     int whichMenu; int xSqr, ySqr;
6927
6928     if(seekGraphUp) { // [HGM] seekgraph
6929         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6930         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6931         return -2;
6932     }
6933
6934     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6935          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6936         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6937         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6938         if(action == Press)   {
6939             originalFlip = flipView;
6940             flipView = !flipView; // temporarily flip board to see game from partners perspective
6941             DrawPosition(TRUE, partnerBoard);
6942             DisplayMessage(partnerStatus, "");
6943             partnerUp = TRUE;
6944         } else if(action == Release) {
6945             flipView = originalFlip;
6946             DrawPosition(TRUE, boards[currentMove]);
6947             partnerUp = FALSE;
6948         }
6949         return -2;
6950     }
6951
6952     xSqr = EventToSquare(x, BOARD_WIDTH);
6953     ySqr = EventToSquare(y, BOARD_HEIGHT);
6954     if (action == Release) {
6955         if(pieceSweep != EmptySquare) {
6956             EditPositionMenuEvent(pieceSweep, toX, toY);
6957             pieceSweep = EmptySquare;
6958         } else UnLoadPV(); // [HGM] pv
6959     }
6960     if (action != Press) return -2; // return code to be ignored
6961     switch (gameMode) {
6962       case IcsExamining:
6963         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6964       case EditPosition:
6965         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6966         if (xSqr < 0 || ySqr < 0) return -1;
6967         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6968         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6969         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6970         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6971         NextPiece(0);
6972         return -2;\r
6973       case IcsObserving:
6974         if(!appData.icsEngineAnalyze) return -1;
6975       case IcsPlayingWhite:
6976       case IcsPlayingBlack:
6977         if(!appData.zippyPlay) goto noZip;
6978       case AnalyzeMode:
6979       case AnalyzeFile:
6980       case MachinePlaysWhite:
6981       case MachinePlaysBlack:
6982       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6983         if (!appData.dropMenu) {
6984           LoadPV(x, y);
6985           return 2; // flag front-end to grab mouse events
6986         }
6987         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6988            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6989       case EditGame:
6990       noZip:
6991         if (xSqr < 0 || ySqr < 0) return -1;
6992         if (!appData.dropMenu || appData.testLegality &&
6993             gameInfo.variant != VariantBughouse &&
6994             gameInfo.variant != VariantCrazyhouse) return -1;
6995         whichMenu = 1; // drop menu
6996         break;
6997       default:
6998         return -1;
6999     }
7000
7001     if (((*fromX = xSqr) < 0) ||
7002         ((*fromY = ySqr) < 0)) {
7003         *fromX = *fromY = -1;
7004         return -1;
7005     }
7006     if (flipView)
7007       *fromX = BOARD_WIDTH - 1 - *fromX;
7008     else
7009       *fromY = BOARD_HEIGHT - 1 - *fromY;
7010
7011     return whichMenu;
7012 }
7013
7014 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7015 {
7016 //    char * hint = lastHint;
7017     FrontEndProgramStats stats;
7018
7019     stats.which = cps == &first ? 0 : 1;
7020     stats.depth = cpstats->depth;
7021     stats.nodes = cpstats->nodes;
7022     stats.score = cpstats->score;
7023     stats.time = cpstats->time;
7024     stats.pv = cpstats->movelist;
7025     stats.hint = lastHint;
7026     stats.an_move_index = 0;
7027     stats.an_move_count = 0;
7028
7029     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7030         stats.hint = cpstats->move_name;
7031         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7032         stats.an_move_count = cpstats->nr_moves;
7033     }
7034
7035     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
7036
7037     SetProgramStats( &stats );
7038 }
7039
7040 #define MAXPLAYERS 500
7041
7042 char *
7043 TourneyStandings(int display)
7044 {
7045     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7046     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7047     char result, *p, *names[MAXPLAYERS];
7048
7049     names[0] = p = strdup(appData.participants);
7050     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7051
7052     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7053
7054     while(result = appData.results[nr]) {
7055         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7056         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7057         wScore = bScore = 0;
7058         switch(result) {
7059           case '+': wScore = 2; break;
7060           case '-': bScore = 2; break;
7061           case '=': wScore = bScore = 1; break;
7062           case ' ':
7063           case '*': return NULL; // tourney not finished
7064         }
7065         score[w] += wScore;
7066         score[b] += bScore;
7067         games[w]++;
7068         games[b]++;
7069         nr++;
7070     }
7071     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7072     for(w=0; w<nPlayers; w++) {
7073         bScore = -1;
7074         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7075         ranking[w] = b; points[w] = bScore; score[b] = -2;
7076     }
7077     p = malloc(nPlayers*34+1);
7078     for(w=0; w<nPlayers && w<display; w++)
7079         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7080     free(names[0]);
7081     return p;
7082 }
7083
7084 void
7085 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7086 {       // count all piece types
7087         int p, f, r;
7088         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7089         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7090         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7091                 p = board[r][f];
7092                 pCnt[p]++;
7093                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7094                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7095                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7096                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7097                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7098                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7099         }
7100 }
7101
7102 int
7103 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7104 {
7105         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7106         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7107
7108         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7109         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7110         if(myPawns == 2 && nMine == 3) // KPP
7111             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7112         if(myPawns == 1 && nMine == 2) // KP
7113             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7114         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7115             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7116         if(myPawns) return FALSE;
7117         if(pCnt[WhiteRook+side])
7118             return pCnt[BlackRook-side] ||
7119                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7120                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7121                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7122         if(pCnt[WhiteCannon+side]) {
7123             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7124             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7125         }
7126         if(pCnt[WhiteKnight+side])
7127             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7128         return FALSE;
7129 }
7130
7131 int
7132 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7133 {
7134         VariantClass v = gameInfo.variant;
7135
7136         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7137         if(v == VariantShatranj) return TRUE; // always winnable through baring
7138         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7139         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7140
7141         if(v == VariantXiangqi) {
7142                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7143
7144                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7145                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7146                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7147                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7148                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7149                 if(stale) // we have at least one last-rank P plus perhaps C
7150                     return majors // KPKX
7151                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7152                 else // KCA*E*
7153                     return pCnt[WhiteFerz+side] // KCAK
7154                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7155                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7156                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7157
7158         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7159                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7160
7161                 if(nMine == 1) return FALSE; // bare King
7162                 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
7163                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7164                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7165                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7166                 if(pCnt[WhiteKnight+side])
7167                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7168                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7169                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7170                 if(nBishops)
7171                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7172                 if(pCnt[WhiteAlfil+side])
7173                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7174                 if(pCnt[WhiteWazir+side])
7175                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7176         }
7177
7178         return TRUE;
7179 }
7180
7181 int
7182 Adjudicate(ChessProgramState *cps)
7183 {       // [HGM] some adjudications useful with buggy engines
7184         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7185         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7186         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7187         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7188         int k, count = 0; static int bare = 1;
7189         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7190         Boolean canAdjudicate = !appData.icsActive;
7191
7192         // most tests only when we understand the game, i.e. legality-checking on
7193             if( appData.testLegality )
7194             {   /* [HGM] Some more adjudications for obstinate engines */
7195                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7196                 static int moveCount = 6;
7197                 ChessMove result;
7198                 char *reason = NULL;
7199
7200                 /* Count what is on board. */
7201                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7202
7203                 /* Some material-based adjudications that have to be made before stalemate test */
7204                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7205                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7206                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7207                      if(canAdjudicate && appData.checkMates) {
7208                          if(engineOpponent)
7209                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7210                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7211                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7212                          return 1;
7213                      }
7214                 }
7215
7216                 /* Bare King in Shatranj (loses) or Losers (wins) */
7217                 if( nrW == 1 || nrB == 1) {
7218                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7219                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7220                      if(canAdjudicate && appData.checkMates) {
7221                          if(engineOpponent)
7222                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7223                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7224                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7225                          return 1;
7226                      }
7227                   } else
7228                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7229                   {    /* bare King */
7230                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7231                         if(canAdjudicate && appData.checkMates) {
7232                             /* but only adjudicate if adjudication enabled */
7233                             if(engineOpponent)
7234                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7235                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7236                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7237                             return 1;
7238                         }
7239                   }
7240                 } else bare = 1;
7241
7242
7243             // don't wait for engine to announce game end if we can judge ourselves
7244             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7245               case MT_CHECK:
7246                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7247                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7248                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7249                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7250                             checkCnt++;
7251                         if(checkCnt >= 2) {
7252                             reason = "Xboard adjudication: 3rd check";
7253                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7254                             break;
7255                         }
7256                     }
7257                 }
7258               case MT_NONE:
7259               default:
7260                 break;
7261               case MT_STALEMATE:
7262               case MT_STAINMATE:
7263                 reason = "Xboard adjudication: Stalemate";
7264                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7265                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7266                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7267                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7268                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7269                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7270                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7271                                                                         EP_CHECKMATE : EP_WINS);
7272                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7273                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7274                 }
7275                 break;
7276               case MT_CHECKMATE:
7277                 reason = "Xboard adjudication: Checkmate";
7278                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7279                 break;
7280             }
7281
7282                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7283                     case EP_STALEMATE:
7284                         result = GameIsDrawn; break;
7285                     case EP_CHECKMATE:
7286                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7287                     case EP_WINS:
7288                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7289                     default:
7290                         result = EndOfFile;
7291                 }
7292                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7293                     if(engineOpponent)
7294                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7295                     GameEnds( result, reason, GE_XBOARD );
7296                     return 1;
7297                 }
7298
7299                 /* Next absolutely insufficient mating material. */
7300                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7301                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7302                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7303
7304                      /* always flag draws, for judging claims */
7305                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7306
7307                      if(canAdjudicate && appData.materialDraws) {
7308                          /* but only adjudicate them if adjudication enabled */
7309                          if(engineOpponent) {
7310                            SendToProgram("force\n", engineOpponent); // suppress reply
7311                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7312                          }
7313                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7314                          return 1;
7315                      }
7316                 }
7317
7318                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7319                 if(gameInfo.variant == VariantXiangqi ?
7320                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7321                  : nrW + nrB == 4 &&
7322                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7323                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7324                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7325                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7326                    ) ) {
7327                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7328                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7329                           if(engineOpponent) {
7330                             SendToProgram("force\n", engineOpponent); // suppress reply
7331                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7332                           }
7333                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7334                           return 1;
7335                      }
7336                 } else moveCount = 6;
7337             }
7338         if (appData.debugMode) { int i;
7339             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7340                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7341                     appData.drawRepeats);
7342             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7343               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7344
7345         }
7346
7347         // Repetition draws and 50-move rule can be applied independently of legality testing
7348
7349                 /* Check for rep-draws */
7350                 count = 0;
7351                 for(k = forwardMostMove-2;
7352                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7353                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7354                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7355                     k-=2)
7356                 {   int rights=0;
7357                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7358                         /* compare castling rights */
7359                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7360                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7361                                 rights++; /* King lost rights, while rook still had them */
7362                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7363                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7364                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7365                                    rights++; /* but at least one rook lost them */
7366                         }
7367                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7368                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7369                                 rights++;
7370                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7371                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7372                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7373                                    rights++;
7374                         }
7375                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7376                             && appData.drawRepeats > 1) {
7377                              /* adjudicate after user-specified nr of repeats */
7378                              int result = GameIsDrawn;
7379                              char *details = "XBoard adjudication: repetition draw";
7380                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7381                                 // [HGM] xiangqi: check for forbidden perpetuals
7382                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7383                                 for(m=forwardMostMove; m>k; m-=2) {
7384                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7385                                         ourPerpetual = 0; // the current mover did not always check
7386                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7387                                         hisPerpetual = 0; // the opponent did not always check
7388                                 }
7389                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7390                                                                         ourPerpetual, hisPerpetual);
7391                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7392                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7393                                     details = "Xboard adjudication: perpetual checking";
7394                                 } else
7395                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7396                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7397                                 } else
7398                                 // Now check for perpetual chases
7399                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7400                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7401                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7402                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7403                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7404                                         details = "Xboard adjudication: perpetual chasing";
7405                                     } else
7406                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7407                                         break; // Abort repetition-checking loop.
7408                                 }
7409                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7410                              }
7411                              if(engineOpponent) {
7412                                SendToProgram("force\n", engineOpponent); // suppress reply
7413                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7414                              }
7415                              GameEnds( result, details, GE_XBOARD );
7416                              return 1;
7417                         }
7418                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7419                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7420                     }
7421                 }
7422
7423                 /* Now we test for 50-move draws. Determine ply count */
7424                 count = forwardMostMove;
7425                 /* look for last irreversble move */
7426                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7427                     count--;
7428                 /* if we hit starting position, add initial plies */
7429                 if( count == backwardMostMove )
7430                     count -= initialRulePlies;
7431                 count = forwardMostMove - count;
7432                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7433                         // adjust reversible move counter for checks in Xiangqi
7434                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7435                         if(i < backwardMostMove) i = backwardMostMove;
7436                         while(i <= forwardMostMove) {
7437                                 lastCheck = inCheck; // check evasion does not count
7438                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7439                                 if(inCheck || lastCheck) count--; // check does not count
7440                                 i++;
7441                         }
7442                 }
7443                 if( count >= 100)
7444                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7445                          /* this is used to judge if draw claims are legal */
7446                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7447                          if(engineOpponent) {
7448                            SendToProgram("force\n", engineOpponent); // suppress reply
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7450                          }
7451                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7452                          return 1;
7453                 }
7454
7455                 /* if draw offer is pending, treat it as a draw claim
7456                  * when draw condition present, to allow engines a way to
7457                  * claim draws before making their move to avoid a race
7458                  * condition occurring after their move
7459                  */
7460                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7461                          char *p = NULL;
7462                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7463                              p = "Draw claim: 50-move rule";
7464                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7465                              p = "Draw claim: 3-fold repetition";
7466                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7467                              p = "Draw claim: insufficient mating material";
7468                          if( p != NULL && canAdjudicate) {
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, p, GE_XBOARD );
7474                              return 1;
7475                          }
7476                 }
7477
7478                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7479                     if(engineOpponent) {
7480                       SendToProgram("force\n", engineOpponent); // suppress reply
7481                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7482                     }
7483                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7484                     return 1;
7485                 }
7486         return 0;
7487 }
7488
7489 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7490 {   // [HGM] book: this routine intercepts moves to simulate book replies
7491     char *bookHit = NULL;
7492
7493     //first determine if the incoming move brings opponent into his book
7494     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7495         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7496     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7497     if(bookHit != NULL && !cps->bookSuspend) {
7498         // make sure opponent is not going to reply after receiving move to book position
7499         SendToProgram("force\n", cps);
7500         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7501     }
7502     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7503     // now arrange restart after book miss
7504     if(bookHit) {
7505         // after a book hit we never send 'go', and the code after the call to this routine
7506         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7507         char buf[MSG_SIZ];
7508         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7509         SendToProgram(buf, cps);
7510         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7511     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7512         SendToProgram("go\n", cps);
7513         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7514     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7515         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7516             SendToProgram("go\n", cps);
7517         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7518     }
7519     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7520 }
7521
7522 char *savedMessage;
7523 ChessProgramState *savedState;
7524 void DeferredBookMove(void)
7525 {
7526         if(savedState->lastPing != savedState->lastPong)
7527                     ScheduleDelayedEvent(DeferredBookMove, 10);
7528         else
7529         HandleMachineMove(savedMessage, savedState);
7530 }
7531
7532 void
7533 HandleMachineMove(message, cps)
7534      char *message;
7535      ChessProgramState *cps;
7536 {
7537     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7538     char realname[MSG_SIZ];
7539     int fromX, fromY, toX, toY;
7540     ChessMove moveType;
7541     char promoChar;
7542     char *p;
7543     int machineWhite;
7544     char *bookHit;
7545
7546     cps->userError = 0;
7547
7548 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7549     /*
7550      * Kludge to ignore BEL characters
7551      */
7552     while (*message == '\007') message++;
7553
7554     /*
7555      * [HGM] engine debug message: ignore lines starting with '#' character
7556      */
7557     if(cps->debug && *message == '#') return;
7558
7559     /*
7560      * Look for book output
7561      */
7562     if (cps == &first && bookRequested) {
7563         if (message[0] == '\t' || message[0] == ' ') {
7564             /* Part of the book output is here; append it */
7565             strcat(bookOutput, message);
7566             strcat(bookOutput, "  \n");
7567             return;
7568         } else if (bookOutput[0] != NULLCHAR) {
7569             /* All of book output has arrived; display it */
7570             char *p = bookOutput;
7571             while (*p != NULLCHAR) {
7572                 if (*p == '\t') *p = ' ';
7573                 p++;
7574             }
7575             DisplayInformation(bookOutput);
7576             bookRequested = FALSE;
7577             /* Fall through to parse the current output */
7578         }
7579     }
7580
7581     /*
7582      * Look for machine move.
7583      */
7584     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7585         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7586     {
7587         /* This method is only useful on engines that support ping */
7588         if (cps->lastPing != cps->lastPong) {
7589           if (gameMode == BeginningOfGame) {
7590             /* Extra move from before last new; ignore */
7591             if (appData.debugMode) {
7592                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7593             }
7594           } else {
7595             if (appData.debugMode) {
7596                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7597                         cps->which, gameMode);
7598             }
7599
7600             SendToProgram("undo\n", cps);
7601           }
7602           return;
7603         }
7604
7605         switch (gameMode) {
7606           case BeginningOfGame:
7607             /* Extra move from before last reset; ignore */
7608             if (appData.debugMode) {
7609                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7610             }
7611             return;
7612
7613           case EndOfGame:
7614           case IcsIdle:
7615           default:
7616             /* Extra move after we tried to stop.  The mode test is
7617                not a reliable way of detecting this problem, but it's
7618                the best we can do on engines that don't support ping.
7619             */
7620             if (appData.debugMode) {
7621                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7622                         cps->which, gameMode);
7623             }
7624             SendToProgram("undo\n", cps);
7625             return;
7626
7627           case MachinePlaysWhite:
7628           case IcsPlayingWhite:
7629             machineWhite = TRUE;
7630             break;
7631
7632           case MachinePlaysBlack:
7633           case IcsPlayingBlack:
7634             machineWhite = FALSE;
7635             break;
7636
7637           case TwoMachinesPlay:
7638             machineWhite = (cps->twoMachinesColor[0] == 'w');
7639             break;
7640         }
7641         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7642             if (appData.debugMode) {
7643                 fprintf(debugFP,
7644                         "Ignoring move out of turn by %s, gameMode %d"
7645                         ", forwardMost %d\n",
7646                         cps->which, gameMode, forwardMostMove);
7647             }
7648             return;
7649         }
7650
7651     if (appData.debugMode) { int f = forwardMostMove;
7652         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7653                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7654                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7655     }
7656         if(cps->alphaRank) AlphaRank(machineMove, 4);
7657         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7658                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7659             /* Machine move could not be parsed; ignore it. */
7660           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7661                     machineMove, _(cps->which));
7662             DisplayError(buf1, 0);
7663             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7664                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7665             if (gameMode == TwoMachinesPlay) {
7666               GameEnds(machineWhite ? BlackWins : WhiteWins,
7667                        buf1, GE_XBOARD);
7668             }
7669             return;
7670         }
7671
7672         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7673         /* So we have to redo legality test with true e.p. status here,  */
7674         /* to make sure an illegal e.p. capture does not slip through,   */
7675         /* to cause a forfeit on a justified illegal-move complaint      */
7676         /* of the opponent.                                              */
7677         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7678            ChessMove moveType;
7679            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7680                              fromY, fromX, toY, toX, promoChar);
7681             if (appData.debugMode) {
7682                 int i;
7683                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7684                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7685                 fprintf(debugFP, "castling rights\n");
7686             }
7687             if(moveType == IllegalMove) {
7688               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7689                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7690                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7691                            buf1, GE_XBOARD);
7692                 return;
7693            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7694            /* [HGM] Kludge to handle engines that send FRC-style castling
7695               when they shouldn't (like TSCP-Gothic) */
7696            switch(moveType) {
7697              case WhiteASideCastleFR:
7698              case BlackASideCastleFR:
7699                toX+=2;
7700                currentMoveString[2]++;
7701                break;
7702              case WhiteHSideCastleFR:
7703              case BlackHSideCastleFR:
7704                toX--;
7705                currentMoveString[2]--;
7706                break;
7707              default: ; // nothing to do, but suppresses warning of pedantic compilers
7708            }
7709         }
7710         hintRequested = FALSE;
7711         lastHint[0] = NULLCHAR;
7712         bookRequested = FALSE;
7713         /* Program may be pondering now */
7714         cps->maybeThinking = TRUE;
7715         if (cps->sendTime == 2) cps->sendTime = 1;
7716         if (cps->offeredDraw) cps->offeredDraw--;
7717
7718         /* [AS] Save move info*/
7719         pvInfoList[ forwardMostMove ].score = programStats.score;
7720         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7721         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7722
7723         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7724
7725         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7726         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7727             int count = 0;
7728
7729             while( count < adjudicateLossPlies ) {
7730                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7731
7732                 if( count & 1 ) {
7733                     score = -score; /* Flip score for winning side */
7734                 }
7735
7736                 if( score > adjudicateLossThreshold ) {
7737                     break;
7738                 }
7739
7740                 count++;
7741             }
7742
7743             if( count >= adjudicateLossPlies ) {
7744                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7745
7746                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7747                     "Xboard adjudication",
7748                     GE_XBOARD );
7749
7750                 return;
7751             }
7752         }
7753
7754         if(Adjudicate(cps)) {
7755             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7756             return; // [HGM] adjudicate: for all automatic game ends
7757         }
7758
7759 #if ZIPPY
7760         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7761             first.initDone) {
7762           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7763                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7764                 SendToICS("draw ");
7765                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7766           }
7767           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7768           ics_user_moved = 1;
7769           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7770                 char buf[3*MSG_SIZ];
7771
7772                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7773                         programStats.score / 100.,
7774                         programStats.depth,
7775                         programStats.time / 100.,
7776                         (unsigned int)programStats.nodes,
7777                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7778                         programStats.movelist);
7779                 SendToICS(buf);
7780 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7781           }
7782         }
7783 #endif
7784
7785         /* [AS] Clear stats for next move */
7786         ClearProgramStats();
7787         thinkOutput[0] = NULLCHAR;
7788         hiddenThinkOutputState = 0;
7789
7790         bookHit = NULL;
7791         if (gameMode == TwoMachinesPlay) {
7792             /* [HGM] relaying draw offers moved to after reception of move */
7793             /* and interpreting offer as claim if it brings draw condition */
7794             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7795                 SendToProgram("draw\n", cps->other);
7796             }
7797             if (cps->other->sendTime) {
7798                 SendTimeRemaining(cps->other,
7799                                   cps->other->twoMachinesColor[0] == 'w');
7800             }
7801             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7802             if (firstMove && !bookHit) {
7803                 firstMove = FALSE;
7804                 if (cps->other->useColors) {
7805                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7806                 }
7807                 SendToProgram("go\n", cps->other);
7808             }
7809             cps->other->maybeThinking = TRUE;
7810         }
7811
7812         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7813
7814         if (!pausing && appData.ringBellAfterMoves) {
7815             RingBell();
7816         }
7817
7818         /*
7819          * Reenable menu items that were disabled while
7820          * machine was thinking
7821          */
7822         if (gameMode != TwoMachinesPlay)
7823             SetUserThinkingEnables();
7824
7825         // [HGM] book: after book hit opponent has received move and is now in force mode
7826         // force the book reply into it, and then fake that it outputted this move by jumping
7827         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7828         if(bookHit) {
7829                 static char bookMove[MSG_SIZ]; // a bit generous?
7830
7831                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7832                 strcat(bookMove, bookHit);
7833                 message = bookMove;
7834                 cps = cps->other;
7835                 programStats.nodes = programStats.depth = programStats.time =
7836                 programStats.score = programStats.got_only_move = 0;
7837                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7838
7839                 if(cps->lastPing != cps->lastPong) {
7840                     savedMessage = message; // args for deferred call
7841                     savedState = cps;
7842                     ScheduleDelayedEvent(DeferredBookMove, 10);
7843                     return;
7844                 }
7845                 goto FakeBookMove;
7846         }
7847
7848         return;
7849     }
7850
7851     /* Set special modes for chess engines.  Later something general
7852      *  could be added here; for now there is just one kludge feature,
7853      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7854      *  when "xboard" is given as an interactive command.
7855      */
7856     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7857         cps->useSigint = FALSE;
7858         cps->useSigterm = FALSE;
7859     }
7860     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7861       ParseFeatures(message+8, cps);
7862       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7863     }
7864
7865     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7866       int dummy, s=6; char buf[MSG_SIZ];
7867       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7868       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7869       ParseFEN(boards[0], &dummy, message+s);
7870       DrawPosition(TRUE, boards[0]);
7871       startedFromSetupPosition = TRUE;
7872       return;
7873     }
7874     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7875      * want this, I was asked to put it in, and obliged.
7876      */
7877     if (!strncmp(message, "setboard ", 9)) {
7878         Board initial_position;
7879
7880         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7881
7882         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7883             DisplayError(_("Bad FEN received from engine"), 0);
7884             return ;
7885         } else {
7886            Reset(TRUE, FALSE);
7887            CopyBoard(boards[0], initial_position);
7888            initialRulePlies = FENrulePlies;
7889            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7890            else gameMode = MachinePlaysBlack;
7891            DrawPosition(FALSE, boards[currentMove]);
7892         }
7893         return;
7894     }
7895
7896     /*
7897      * Look for communication commands
7898      */
7899     if (!strncmp(message, "telluser ", 9)) {
7900         if(message[9] == '\\' && message[10] == '\\')
7901             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7902         DisplayNote(message + 9);
7903         return;
7904     }
7905     if (!strncmp(message, "tellusererror ", 14)) {
7906         cps->userError = 1;
7907         if(message[14] == '\\' && message[15] == '\\')
7908             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7909         DisplayError(message + 14, 0);
7910         return;
7911     }
7912     if (!strncmp(message, "tellopponent ", 13)) {
7913       if (appData.icsActive) {
7914         if (loggedOn) {
7915           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7916           SendToICS(buf1);
7917         }
7918       } else {
7919         DisplayNote(message + 13);
7920       }
7921       return;
7922     }
7923     if (!strncmp(message, "tellothers ", 11)) {
7924       if (appData.icsActive) {
7925         if (loggedOn) {
7926           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7927           SendToICS(buf1);
7928         }
7929       }
7930       return;
7931     }
7932     if (!strncmp(message, "tellall ", 8)) {
7933       if (appData.icsActive) {
7934         if (loggedOn) {
7935           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7936           SendToICS(buf1);
7937         }
7938       } else {
7939         DisplayNote(message + 8);
7940       }
7941       return;
7942     }
7943     if (strncmp(message, "warning", 7) == 0) {
7944         /* Undocumented feature, use tellusererror in new code */
7945         DisplayError(message, 0);
7946         return;
7947     }
7948     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7949         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7950         strcat(realname, " query");
7951         AskQuestion(realname, buf2, buf1, cps->pr);
7952         return;
7953     }
7954     /* Commands from the engine directly to ICS.  We don't allow these to be
7955      *  sent until we are logged on. Crafty kibitzes have been known to
7956      *  interfere with the login process.
7957      */
7958     if (loggedOn) {
7959         if (!strncmp(message, "tellics ", 8)) {
7960             SendToICS(message + 8);
7961             SendToICS("\n");
7962             return;
7963         }
7964         if (!strncmp(message, "tellicsnoalias ", 15)) {
7965             SendToICS(ics_prefix);
7966             SendToICS(message + 15);
7967             SendToICS("\n");
7968             return;
7969         }
7970         /* The following are for backward compatibility only */
7971         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7972             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7973             SendToICS(ics_prefix);
7974             SendToICS(message);
7975             SendToICS("\n");
7976             return;
7977         }
7978     }
7979     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7980         return;
7981     }
7982     /*
7983      * If the move is illegal, cancel it and redraw the board.
7984      * Also deal with other error cases.  Matching is rather loose
7985      * here to accommodate engines written before the spec.
7986      */
7987     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7988         strncmp(message, "Error", 5) == 0) {
7989         if (StrStr(message, "name") ||
7990             StrStr(message, "rating") || StrStr(message, "?") ||
7991             StrStr(message, "result") || StrStr(message, "board") ||
7992             StrStr(message, "bk") || StrStr(message, "computer") ||
7993             StrStr(message, "variant") || StrStr(message, "hint") ||
7994             StrStr(message, "random") || StrStr(message, "depth") ||
7995             StrStr(message, "accepted")) {
7996             return;
7997         }
7998         if (StrStr(message, "protover")) {
7999           /* Program is responding to input, so it's apparently done
8000              initializing, and this error message indicates it is
8001              protocol version 1.  So we don't need to wait any longer
8002              for it to initialize and send feature commands. */
8003           FeatureDone(cps, 1);
8004           cps->protocolVersion = 1;
8005           return;
8006         }
8007         cps->maybeThinking = FALSE;
8008
8009         if (StrStr(message, "draw")) {
8010             /* Program doesn't have "draw" command */
8011             cps->sendDrawOffers = 0;
8012             return;
8013         }
8014         if (cps->sendTime != 1 &&
8015             (StrStr(message, "time") || StrStr(message, "otim"))) {
8016           /* Program apparently doesn't have "time" or "otim" command */
8017           cps->sendTime = 0;
8018           return;
8019         }
8020         if (StrStr(message, "analyze")) {
8021             cps->analysisSupport = FALSE;
8022             cps->analyzing = FALSE;
8023             Reset(FALSE, TRUE);
8024             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8025             DisplayError(buf2, 0);
8026             return;
8027         }
8028         if (StrStr(message, "(no matching move)st")) {
8029           /* Special kludge for GNU Chess 4 only */
8030           cps->stKludge = TRUE;
8031           SendTimeControl(cps, movesPerSession, timeControl,
8032                           timeIncrement, appData.searchDepth,
8033                           searchTime);
8034           return;
8035         }
8036         if (StrStr(message, "(no matching move)sd")) {
8037           /* Special kludge for GNU Chess 4 only */
8038           cps->sdKludge = TRUE;
8039           SendTimeControl(cps, movesPerSession, timeControl,
8040                           timeIncrement, appData.searchDepth,
8041                           searchTime);
8042           return;
8043         }
8044         if (!StrStr(message, "llegal")) {
8045             return;
8046         }
8047         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8048             gameMode == IcsIdle) return;
8049         if (forwardMostMove <= backwardMostMove) return;
8050         if (pausing) PauseEvent();
8051       if(appData.forceIllegal) {
8052             // [HGM] illegal: machine refused move; force position after move into it
8053           SendToProgram("force\n", cps);
8054           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8055                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8056                 // when black is to move, while there might be nothing on a2 or black
8057                 // might already have the move. So send the board as if white has the move.
8058                 // But first we must change the stm of the engine, as it refused the last move
8059                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8060                 if(WhiteOnMove(forwardMostMove)) {
8061                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8062                     SendBoard(cps, forwardMostMove); // kludgeless board
8063                 } else {
8064                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8065                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8066                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8067                 }
8068           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8069             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8070                  gameMode == TwoMachinesPlay)
8071               SendToProgram("go\n", cps);
8072             return;
8073       } else
8074         if (gameMode == PlayFromGameFile) {
8075             /* Stop reading this game file */
8076             gameMode = EditGame;
8077             ModeHighlight();
8078         }
8079         /* [HGM] illegal-move claim should forfeit game when Xboard */
8080         /* only passes fully legal moves                            */
8081         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8082             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8083                                 "False illegal-move claim", GE_XBOARD );
8084             return; // do not take back move we tested as valid
8085         }
8086         currentMove = forwardMostMove-1;
8087         DisplayMove(currentMove-1); /* before DisplayMoveError */
8088         SwitchClocks(forwardMostMove-1); // [HGM] race
8089         DisplayBothClocks();
8090         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8091                 parseList[currentMove], _(cps->which));
8092         DisplayMoveError(buf1);
8093         DrawPosition(FALSE, boards[currentMove]);
8094         return;
8095     }
8096     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8097         /* Program has a broken "time" command that
8098            outputs a string not ending in newline.
8099            Don't use it. */
8100         cps->sendTime = 0;
8101     }
8102
8103     /*
8104      * If chess program startup fails, exit with an error message.
8105      * Attempts to recover here are futile.
8106      */
8107     if ((StrStr(message, "unknown host") != NULL)
8108         || (StrStr(message, "No remote directory") != NULL)
8109         || (StrStr(message, "not found") != NULL)
8110         || (StrStr(message, "No such file") != NULL)
8111         || (StrStr(message, "can't alloc") != NULL)
8112         || (StrStr(message, "Permission denied") != NULL)) {
8113
8114         cps->maybeThinking = FALSE;
8115         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8116                 _(cps->which), cps->program, cps->host, message);
8117         RemoveInputSource(cps->isr);
8118         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8119             if(cps == &first) appData.noChessProgram = TRUE;
8120             DisplayError(buf1, 0);
8121         }
8122         return;
8123     }
8124
8125     /*
8126      * Look for hint output
8127      */
8128     if (sscanf(message, "Hint: %s", buf1) == 1) {
8129         if (cps == &first && hintRequested) {
8130             hintRequested = FALSE;
8131             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8132                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8133                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8134                                     PosFlags(forwardMostMove),
8135                                     fromY, fromX, toY, toX, promoChar, buf1);
8136                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8137                 DisplayInformation(buf2);
8138             } else {
8139                 /* Hint move could not be parsed!? */
8140               snprintf(buf2, sizeof(buf2),
8141                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8142                         buf1, _(cps->which));
8143                 DisplayError(buf2, 0);
8144             }
8145         } else {
8146           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8147         }
8148         return;
8149     }
8150
8151     /*
8152      * Ignore other messages if game is not in progress
8153      */
8154     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8155         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8156
8157     /*
8158      * look for win, lose, draw, or draw offer
8159      */
8160     if (strncmp(message, "1-0", 3) == 0) {
8161         char *p, *q, *r = "";
8162         p = strchr(message, '{');
8163         if (p) {
8164             q = strchr(p, '}');
8165             if (q) {
8166                 *q = NULLCHAR;
8167                 r = p + 1;
8168             }
8169         }
8170         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8171         return;
8172     } else if (strncmp(message, "0-1", 3) == 0) {
8173         char *p, *q, *r = "";
8174         p = strchr(message, '{');
8175         if (p) {
8176             q = strchr(p, '}');
8177             if (q) {
8178                 *q = NULLCHAR;
8179                 r = p + 1;
8180             }
8181         }
8182         /* Kludge for Arasan 4.1 bug */
8183         if (strcmp(r, "Black resigns") == 0) {
8184             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8185             return;
8186         }
8187         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8188         return;
8189     } else if (strncmp(message, "1/2", 3) == 0) {
8190         char *p, *q, *r = "";
8191         p = strchr(message, '{');
8192         if (p) {
8193             q = strchr(p, '}');
8194             if (q) {
8195                 *q = NULLCHAR;
8196                 r = p + 1;
8197             }
8198         }
8199
8200         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8201         return;
8202
8203     } else if (strncmp(message, "White resign", 12) == 0) {
8204         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8205         return;
8206     } else if (strncmp(message, "Black resign", 12) == 0) {
8207         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8208         return;
8209     } else if (strncmp(message, "White matches", 13) == 0 ||
8210                strncmp(message, "Black matches", 13) == 0   ) {
8211         /* [HGM] ignore GNUShogi noises */
8212         return;
8213     } else if (strncmp(message, "White", 5) == 0 &&
8214                message[5] != '(' &&
8215                StrStr(message, "Black") == NULL) {
8216         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8217         return;
8218     } else if (strncmp(message, "Black", 5) == 0 &&
8219                message[5] != '(') {
8220         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8221         return;
8222     } else if (strcmp(message, "resign") == 0 ||
8223                strcmp(message, "computer resigns") == 0) {
8224         switch (gameMode) {
8225           case MachinePlaysBlack:
8226           case IcsPlayingBlack:
8227             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8228             break;
8229           case MachinePlaysWhite:
8230           case IcsPlayingWhite:
8231             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8232             break;
8233           case TwoMachinesPlay:
8234             if (cps->twoMachinesColor[0] == 'w')
8235               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8236             else
8237               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8238             break;
8239           default:
8240             /* can't happen */
8241             break;
8242         }
8243         return;
8244     } else if (strncmp(message, "opponent mates", 14) == 0) {
8245         switch (gameMode) {
8246           case MachinePlaysBlack:
8247           case IcsPlayingBlack:
8248             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8249             break;
8250           case MachinePlaysWhite:
8251           case IcsPlayingWhite:
8252             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8253             break;
8254           case TwoMachinesPlay:
8255             if (cps->twoMachinesColor[0] == 'w')
8256               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8257             else
8258               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8259             break;
8260           default:
8261             /* can't happen */
8262             break;
8263         }
8264         return;
8265     } else if (strncmp(message, "computer mates", 14) == 0) {
8266         switch (gameMode) {
8267           case MachinePlaysBlack:
8268           case IcsPlayingBlack:
8269             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8270             break;
8271           case MachinePlaysWhite:
8272           case IcsPlayingWhite:
8273             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8274             break;
8275           case TwoMachinesPlay:
8276             if (cps->twoMachinesColor[0] == 'w')
8277               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8278             else
8279               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8280             break;
8281           default:
8282             /* can't happen */
8283             break;
8284         }
8285         return;
8286     } else if (strncmp(message, "checkmate", 9) == 0) {
8287         if (WhiteOnMove(forwardMostMove)) {
8288             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8289         } else {
8290             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8291         }
8292         return;
8293     } else if (strstr(message, "Draw") != NULL ||
8294                strstr(message, "game is a draw") != NULL) {
8295         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8296         return;
8297     } else if (strstr(message, "offer") != NULL &&
8298                strstr(message, "draw") != NULL) {
8299 #if ZIPPY
8300         if (appData.zippyPlay && first.initDone) {
8301             /* Relay offer to ICS */
8302             SendToICS(ics_prefix);
8303             SendToICS("draw\n");
8304         }
8305 #endif
8306         cps->offeredDraw = 2; /* valid until this engine moves twice */
8307         if (gameMode == TwoMachinesPlay) {
8308             if (cps->other->offeredDraw) {
8309                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8310             /* [HGM] in two-machine mode we delay relaying draw offer      */
8311             /* until after we also have move, to see if it is really claim */
8312             }
8313         } else if (gameMode == MachinePlaysWhite ||
8314                    gameMode == MachinePlaysBlack) {
8315           if (userOfferedDraw) {
8316             DisplayInformation(_("Machine accepts your draw offer"));
8317             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8318           } else {
8319             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8320           }
8321         }
8322     }
8323
8324
8325     /*
8326      * Look for thinking output
8327      */
8328     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8329           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8330                                 ) {
8331         int plylev, mvleft, mvtot, curscore, time;
8332         char mvname[MOVE_LEN];
8333         u64 nodes; // [DM]
8334         char plyext;
8335         int ignore = FALSE;
8336         int prefixHint = FALSE;
8337         mvname[0] = NULLCHAR;
8338
8339         switch (gameMode) {
8340           case MachinePlaysBlack:
8341           case IcsPlayingBlack:
8342             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8343             break;
8344           case MachinePlaysWhite:
8345           case IcsPlayingWhite:
8346             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8347             break;
8348           case AnalyzeMode:
8349           case AnalyzeFile:
8350             break;
8351           case IcsObserving: /* [DM] icsEngineAnalyze */
8352             if (!appData.icsEngineAnalyze) ignore = TRUE;
8353             break;
8354           case TwoMachinesPlay:
8355             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8356                 ignore = TRUE;
8357             }
8358             break;
8359           default:
8360             ignore = TRUE;
8361             break;
8362         }
8363
8364         if (!ignore) {
8365             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8366             buf1[0] = NULLCHAR;
8367             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8368                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8369
8370                 if (plyext != ' ' && plyext != '\t') {
8371                     time *= 100;
8372                 }
8373
8374                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8375                 if( cps->scoreIsAbsolute &&
8376                     ( gameMode == MachinePlaysBlack ||
8377                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8378                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8379                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8380                      !WhiteOnMove(currentMove)
8381                     ) )
8382                 {
8383                     curscore = -curscore;
8384                 }
8385
8386
8387                 tempStats.depth = plylev;
8388                 tempStats.nodes = nodes;
8389                 tempStats.time = time;
8390                 tempStats.score = curscore;
8391                 tempStats.got_only_move = 0;
8392
8393                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8394                         int ticklen;
8395
8396                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8397                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8398                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8399                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8400                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8401                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8402                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8403                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8404                 }
8405
8406                 /* Buffer overflow protection */
8407                 if (buf1[0] != NULLCHAR) {
8408                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8409                         && appData.debugMode) {
8410                         fprintf(debugFP,
8411                                 "PV is too long; using the first %u bytes.\n",
8412                                 (unsigned) sizeof(tempStats.movelist) - 1);
8413                     }
8414
8415                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8416                 } else {
8417                     sprintf(tempStats.movelist, " no PV\n");
8418                 }
8419
8420                 if (tempStats.seen_stat) {
8421                     tempStats.ok_to_send = 1;
8422                 }
8423
8424                 if (strchr(tempStats.movelist, '(') != NULL) {
8425                     tempStats.line_is_book = 1;
8426                     tempStats.nr_moves = 0;
8427                     tempStats.moves_left = 0;
8428                 } else {
8429                     tempStats.line_is_book = 0;
8430                 }
8431
8432                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8433                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8434
8435                 SendProgramStatsToFrontend( cps, &tempStats );
8436
8437                 /*
8438                     [AS] Protect the thinkOutput buffer from overflow... this
8439                     is only useful if buf1 hasn't overflowed first!
8440                 */
8441                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8442                          plylev,
8443                          (gameMode == TwoMachinesPlay ?
8444                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8445                          ((double) curscore) / 100.0,
8446                          prefixHint ? lastHint : "",
8447                          prefixHint ? " " : "" );
8448
8449                 if( buf1[0] != NULLCHAR ) {
8450                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8451
8452                     if( strlen(buf1) > max_len ) {
8453                         if( appData.debugMode) {
8454                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8455                         }
8456                         buf1[max_len+1] = '\0';
8457                     }
8458
8459                     strcat( thinkOutput, buf1 );
8460                 }
8461
8462                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8463                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8464                     DisplayMove(currentMove - 1);
8465                 }
8466                 return;
8467
8468             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8469                 /* crafty (9.25+) says "(only move) <move>"
8470                  * if there is only 1 legal move
8471                  */
8472                 sscanf(p, "(only move) %s", buf1);
8473                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8474                 sprintf(programStats.movelist, "%s (only move)", buf1);
8475                 programStats.depth = 1;
8476                 programStats.nr_moves = 1;
8477                 programStats.moves_left = 1;
8478                 programStats.nodes = 1;
8479                 programStats.time = 1;
8480                 programStats.got_only_move = 1;
8481
8482                 /* Not really, but we also use this member to
8483                    mean "line isn't going to change" (Crafty
8484                    isn't searching, so stats won't change) */
8485                 programStats.line_is_book = 1;
8486
8487                 SendProgramStatsToFrontend( cps, &programStats );
8488
8489                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8490                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8491                     DisplayMove(currentMove - 1);
8492                 }
8493                 return;
8494             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8495                               &time, &nodes, &plylev, &mvleft,
8496                               &mvtot, mvname) >= 5) {
8497                 /* The stat01: line is from Crafty (9.29+) in response
8498                    to the "." command */
8499                 programStats.seen_stat = 1;
8500                 cps->maybeThinking = TRUE;
8501
8502                 if (programStats.got_only_move || !appData.periodicUpdates)
8503                   return;
8504
8505                 programStats.depth = plylev;
8506                 programStats.time = time;
8507                 programStats.nodes = nodes;
8508                 programStats.moves_left = mvleft;
8509                 programStats.nr_moves = mvtot;
8510                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8511                 programStats.ok_to_send = 1;
8512                 programStats.movelist[0] = '\0';
8513
8514                 SendProgramStatsToFrontend( cps, &programStats );
8515
8516                 return;
8517
8518             } else if (strncmp(message,"++",2) == 0) {
8519                 /* Crafty 9.29+ outputs this */
8520                 programStats.got_fail = 2;
8521                 return;
8522
8523             } else if (strncmp(message,"--",2) == 0) {
8524                 /* Crafty 9.29+ outputs this */
8525                 programStats.got_fail = 1;
8526                 return;
8527
8528             } else if (thinkOutput[0] != NULLCHAR &&
8529                        strncmp(message, "    ", 4) == 0) {
8530                 unsigned message_len;
8531
8532                 p = message;
8533                 while (*p && *p == ' ') p++;
8534
8535                 message_len = strlen( p );
8536
8537                 /* [AS] Avoid buffer overflow */
8538                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8539                     strcat(thinkOutput, " ");
8540                     strcat(thinkOutput, p);
8541                 }
8542
8543                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8544                     strcat(programStats.movelist, " ");
8545                     strcat(programStats.movelist, p);
8546                 }
8547
8548                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8549                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8550                     DisplayMove(currentMove - 1);
8551                 }
8552                 return;
8553             }
8554         }
8555         else {
8556             buf1[0] = NULLCHAR;
8557
8558             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8559                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8560             {
8561                 ChessProgramStats cpstats;
8562
8563                 if (plyext != ' ' && plyext != '\t') {
8564                     time *= 100;
8565                 }
8566
8567                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8568                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8569                     curscore = -curscore;
8570                 }
8571
8572                 cpstats.depth = plylev;
8573                 cpstats.nodes = nodes;
8574                 cpstats.time = time;
8575                 cpstats.score = curscore;
8576                 cpstats.got_only_move = 0;
8577                 cpstats.movelist[0] = '\0';
8578
8579                 if (buf1[0] != NULLCHAR) {
8580                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8581                 }
8582
8583                 cpstats.ok_to_send = 0;
8584                 cpstats.line_is_book = 0;
8585                 cpstats.nr_moves = 0;
8586                 cpstats.moves_left = 0;
8587
8588                 SendProgramStatsToFrontend( cps, &cpstats );
8589             }
8590         }
8591     }
8592 }
8593
8594
8595 /* Parse a game score from the character string "game", and
8596    record it as the history of the current game.  The game
8597    score is NOT assumed to start from the standard position.
8598    The display is not updated in any way.
8599    */
8600 void
8601 ParseGameHistory(game)
8602      char *game;
8603 {
8604     ChessMove moveType;
8605     int fromX, fromY, toX, toY, boardIndex;
8606     char promoChar;
8607     char *p, *q;
8608     char buf[MSG_SIZ];
8609
8610     if (appData.debugMode)
8611       fprintf(debugFP, "Parsing game history: %s\n", game);
8612
8613     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8614     gameInfo.site = StrSave(appData.icsHost);
8615     gameInfo.date = PGNDate();
8616     gameInfo.round = StrSave("-");
8617
8618     /* Parse out names of players */
8619     while (*game == ' ') game++;
8620     p = buf;
8621     while (*game != ' ') *p++ = *game++;
8622     *p = NULLCHAR;
8623     gameInfo.white = StrSave(buf);
8624     while (*game == ' ') game++;
8625     p = buf;
8626     while (*game != ' ' && *game != '\n') *p++ = *game++;
8627     *p = NULLCHAR;
8628     gameInfo.black = StrSave(buf);
8629
8630     /* Parse moves */
8631     boardIndex = blackPlaysFirst ? 1 : 0;
8632     yynewstr(game);
8633     for (;;) {
8634         yyboardindex = boardIndex;
8635         moveType = (ChessMove) Myylex();
8636         switch (moveType) {
8637           case IllegalMove:             /* maybe suicide chess, etc. */
8638   if (appData.debugMode) {
8639     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8640     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8641     setbuf(debugFP, NULL);
8642   }
8643           case WhitePromotion:
8644           case BlackPromotion:
8645           case WhiteNonPromotion:
8646           case BlackNonPromotion:
8647           case NormalMove:
8648           case WhiteCapturesEnPassant:
8649           case BlackCapturesEnPassant:
8650           case WhiteKingSideCastle:
8651           case WhiteQueenSideCastle:
8652           case BlackKingSideCastle:
8653           case BlackQueenSideCastle:
8654           case WhiteKingSideCastleWild:
8655           case WhiteQueenSideCastleWild:
8656           case BlackKingSideCastleWild:
8657           case BlackQueenSideCastleWild:
8658           /* PUSH Fabien */
8659           case WhiteHSideCastleFR:
8660           case WhiteASideCastleFR:
8661           case BlackHSideCastleFR:
8662           case BlackASideCastleFR:
8663           /* POP Fabien */
8664             fromX = currentMoveString[0] - AAA;
8665             fromY = currentMoveString[1] - ONE;
8666             toX = currentMoveString[2] - AAA;
8667             toY = currentMoveString[3] - ONE;
8668             promoChar = currentMoveString[4];
8669             break;
8670           case WhiteDrop:
8671           case BlackDrop:
8672             fromX = moveType == WhiteDrop ?
8673               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8674             (int) CharToPiece(ToLower(currentMoveString[0]));
8675             fromY = DROP_RANK;
8676             toX = currentMoveString[2] - AAA;
8677             toY = currentMoveString[3] - ONE;
8678             promoChar = NULLCHAR;
8679             break;
8680           case AmbiguousMove:
8681             /* bug? */
8682             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8683   if (appData.debugMode) {
8684     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8685     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8686     setbuf(debugFP, NULL);
8687   }
8688             DisplayError(buf, 0);
8689             return;
8690           case ImpossibleMove:
8691             /* bug? */
8692             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8693   if (appData.debugMode) {
8694     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8695     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8696     setbuf(debugFP, NULL);
8697   }
8698             DisplayError(buf, 0);
8699             return;
8700           case EndOfFile:
8701             if (boardIndex < backwardMostMove) {
8702                 /* Oops, gap.  How did that happen? */
8703                 DisplayError(_("Gap in move list"), 0);
8704                 return;
8705             }
8706             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8707             if (boardIndex > forwardMostMove) {
8708                 forwardMostMove = boardIndex;
8709             }
8710             return;
8711           case ElapsedTime:
8712             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8713                 strcat(parseList[boardIndex-1], " ");
8714                 strcat(parseList[boardIndex-1], yy_text);
8715             }
8716             continue;
8717           case Comment:
8718           case PGNTag:
8719           case NAG:
8720           default:
8721             /* ignore */
8722             continue;
8723           case WhiteWins:
8724           case BlackWins:
8725           case GameIsDrawn:
8726           case GameUnfinished:
8727             if (gameMode == IcsExamining) {
8728                 if (boardIndex < backwardMostMove) {
8729                     /* Oops, gap.  How did that happen? */
8730                     return;
8731                 }
8732                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8733                 return;
8734             }
8735             gameInfo.result = moveType;
8736             p = strchr(yy_text, '{');
8737             if (p == NULL) p = strchr(yy_text, '(');
8738             if (p == NULL) {
8739                 p = yy_text;
8740                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8741             } else {
8742                 q = strchr(p, *p == '{' ? '}' : ')');
8743                 if (q != NULL) *q = NULLCHAR;
8744                 p++;
8745             }
8746             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8747             gameInfo.resultDetails = StrSave(p);
8748             continue;
8749         }
8750         if (boardIndex >= forwardMostMove &&
8751             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8752             backwardMostMove = blackPlaysFirst ? 1 : 0;
8753             return;
8754         }
8755         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8756                                  fromY, fromX, toY, toX, promoChar,
8757                                  parseList[boardIndex]);
8758         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8759         /* currentMoveString is set as a side-effect of yylex */
8760         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8761         strcat(moveList[boardIndex], "\n");
8762         boardIndex++;
8763         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8764         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8765           case MT_NONE:
8766           case MT_STALEMATE:
8767           default:
8768             break;
8769           case MT_CHECK:
8770             if(gameInfo.variant != VariantShogi)
8771                 strcat(parseList[boardIndex - 1], "+");
8772             break;
8773           case MT_CHECKMATE:
8774           case MT_STAINMATE:
8775             strcat(parseList[boardIndex - 1], "#");
8776             break;
8777         }
8778     }
8779 }
8780
8781
8782 /* Apply a move to the given board  */
8783 void
8784 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8785      int fromX, fromY, toX, toY;
8786      int promoChar;
8787      Board board;
8788 {
8789   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8790   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8791
8792     /* [HGM] compute & store e.p. status and castling rights for new position */
8793     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8794
8795       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8796       oldEP = (signed char)board[EP_STATUS];
8797       board[EP_STATUS] = EP_NONE;
8798
8799       if( board[toY][toX] != EmptySquare )
8800            board[EP_STATUS] = EP_CAPTURE;
8801
8802   if (fromY == DROP_RANK) {
8803         /* must be first */
8804         piece = board[toY][toX] = (ChessSquare) fromX;
8805   } else {
8806       int i;
8807
8808       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8809            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8810                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8811       } else
8812       if( board[fromY][fromX] == WhitePawn ) {
8813            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8814                board[EP_STATUS] = EP_PAWN_MOVE;
8815            if( toY-fromY==2) {
8816                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8817                         gameInfo.variant != VariantBerolina || toX < fromX)
8818                       board[EP_STATUS] = toX | berolina;
8819                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8820                         gameInfo.variant != VariantBerolina || toX > fromX)
8821                       board[EP_STATUS] = toX;
8822            }
8823       } else
8824       if( board[fromY][fromX] == BlackPawn ) {
8825            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8826                board[EP_STATUS] = EP_PAWN_MOVE;
8827            if( toY-fromY== -2) {
8828                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8829                         gameInfo.variant != VariantBerolina || toX < fromX)
8830                       board[EP_STATUS] = toX | berolina;
8831                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8832                         gameInfo.variant != VariantBerolina || toX > fromX)
8833                       board[EP_STATUS] = toX;
8834            }
8835        }
8836
8837        for(i=0; i<nrCastlingRights; i++) {
8838            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8839               board[CASTLING][i] == toX   && castlingRank[i] == toY
8840              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8841        }
8842
8843      if (fromX == toX && fromY == toY) return;
8844
8845      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8846      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8847      if(gameInfo.variant == VariantKnightmate)
8848          king += (int) WhiteUnicorn - (int) WhiteKing;
8849
8850     /* Code added by Tord: */
8851     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8852     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8853         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8854       board[fromY][fromX] = EmptySquare;
8855       board[toY][toX] = EmptySquare;
8856       if((toX > fromX) != (piece == WhiteRook)) {
8857         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8858       } else {
8859         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8860       }
8861     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8862                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8863       board[fromY][fromX] = EmptySquare;
8864       board[toY][toX] = EmptySquare;
8865       if((toX > fromX) != (piece == BlackRook)) {
8866         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8867       } else {
8868         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8869       }
8870     /* End of code added by Tord */
8871
8872     } else if (board[fromY][fromX] == king
8873         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8874         && toY == fromY && toX > fromX+1) {
8875         board[fromY][fromX] = EmptySquare;
8876         board[toY][toX] = king;
8877         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8878         board[fromY][BOARD_RGHT-1] = EmptySquare;
8879     } else if (board[fromY][fromX] == king
8880         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8881                && toY == fromY && toX < fromX-1) {
8882         board[fromY][fromX] = EmptySquare;
8883         board[toY][toX] = king;
8884         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8885         board[fromY][BOARD_LEFT] = EmptySquare;
8886     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8887                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8888                && toY >= BOARD_HEIGHT-promoRank
8889                ) {
8890         /* white pawn promotion */
8891         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8892         if (board[toY][toX] == EmptySquare) {
8893             board[toY][toX] = WhiteQueen;
8894         }
8895         if(gameInfo.variant==VariantBughouse ||
8896            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8897             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8898         board[fromY][fromX] = EmptySquare;
8899     } else if ((fromY == BOARD_HEIGHT-4)
8900                && (toX != fromX)
8901                && gameInfo.variant != VariantXiangqi
8902                && gameInfo.variant != VariantBerolina
8903                && (board[fromY][fromX] == WhitePawn)
8904                && (board[toY][toX] == EmptySquare)) {
8905         board[fromY][fromX] = EmptySquare;
8906         board[toY][toX] = WhitePawn;
8907         captured = board[toY - 1][toX];
8908         board[toY - 1][toX] = EmptySquare;
8909     } else if ((fromY == BOARD_HEIGHT-4)
8910                && (toX == fromX)
8911                && gameInfo.variant == VariantBerolina
8912                && (board[fromY][fromX] == WhitePawn)
8913                && (board[toY][toX] == EmptySquare)) {
8914         board[fromY][fromX] = EmptySquare;
8915         board[toY][toX] = WhitePawn;
8916         if(oldEP & EP_BEROLIN_A) {
8917                 captured = board[fromY][fromX-1];
8918                 board[fromY][fromX-1] = EmptySquare;
8919         }else{  captured = board[fromY][fromX+1];
8920                 board[fromY][fromX+1] = EmptySquare;
8921         }
8922     } else if (board[fromY][fromX] == king
8923         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8924                && toY == fromY && toX > fromX+1) {
8925         board[fromY][fromX] = EmptySquare;
8926         board[toY][toX] = king;
8927         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8928         board[fromY][BOARD_RGHT-1] = EmptySquare;
8929     } else if (board[fromY][fromX] == king
8930         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8931                && toY == fromY && toX < fromX-1) {
8932         board[fromY][fromX] = EmptySquare;
8933         board[toY][toX] = king;
8934         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8935         board[fromY][BOARD_LEFT] = EmptySquare;
8936     } else if (fromY == 7 && fromX == 3
8937                && board[fromY][fromX] == BlackKing
8938                && toY == 7 && toX == 5) {
8939         board[fromY][fromX] = EmptySquare;
8940         board[toY][toX] = BlackKing;
8941         board[fromY][7] = EmptySquare;
8942         board[toY][4] = BlackRook;
8943     } else if (fromY == 7 && fromX == 3
8944                && board[fromY][fromX] == BlackKing
8945                && toY == 7 && toX == 1) {
8946         board[fromY][fromX] = EmptySquare;
8947         board[toY][toX] = BlackKing;
8948         board[fromY][0] = EmptySquare;
8949         board[toY][2] = BlackRook;
8950     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8951                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8952                && toY < promoRank
8953                ) {
8954         /* black pawn promotion */
8955         board[toY][toX] = CharToPiece(ToLower(promoChar));
8956         if (board[toY][toX] == EmptySquare) {
8957             board[toY][toX] = BlackQueen;
8958         }
8959         if(gameInfo.variant==VariantBughouse ||
8960            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8961             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8962         board[fromY][fromX] = EmptySquare;
8963     } else if ((fromY == 3)
8964                && (toX != fromX)
8965                && gameInfo.variant != VariantXiangqi
8966                && gameInfo.variant != VariantBerolina
8967                && (board[fromY][fromX] == BlackPawn)
8968                && (board[toY][toX] == EmptySquare)) {
8969         board[fromY][fromX] = EmptySquare;
8970         board[toY][toX] = BlackPawn;
8971         captured = board[toY + 1][toX];
8972         board[toY + 1][toX] = EmptySquare;
8973     } else if ((fromY == 3)
8974                && (toX == fromX)
8975                && gameInfo.variant == VariantBerolina
8976                && (board[fromY][fromX] == BlackPawn)
8977                && (board[toY][toX] == EmptySquare)) {
8978         board[fromY][fromX] = EmptySquare;
8979         board[toY][toX] = BlackPawn;
8980         if(oldEP & EP_BEROLIN_A) {
8981                 captured = board[fromY][fromX-1];
8982                 board[fromY][fromX-1] = EmptySquare;
8983         }else{  captured = board[fromY][fromX+1];
8984                 board[fromY][fromX+1] = EmptySquare;
8985         }
8986     } else {
8987         board[toY][toX] = board[fromY][fromX];
8988         board[fromY][fromX] = EmptySquare;
8989     }
8990   }
8991
8992     if (gameInfo.holdingsWidth != 0) {
8993
8994       /* !!A lot more code needs to be written to support holdings  */
8995       /* [HGM] OK, so I have written it. Holdings are stored in the */
8996       /* penultimate board files, so they are automaticlly stored   */
8997       /* in the game history.                                       */
8998       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8999                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9000         /* Delete from holdings, by decreasing count */
9001         /* and erasing image if necessary            */
9002         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9003         if(p < (int) BlackPawn) { /* white drop */
9004              p -= (int)WhitePawn;
9005                  p = PieceToNumber((ChessSquare)p);
9006              if(p >= gameInfo.holdingsSize) p = 0;
9007              if(--board[p][BOARD_WIDTH-2] <= 0)
9008                   board[p][BOARD_WIDTH-1] = EmptySquare;
9009              if((int)board[p][BOARD_WIDTH-2] < 0)
9010                         board[p][BOARD_WIDTH-2] = 0;
9011         } else {                  /* black drop */
9012              p -= (int)BlackPawn;
9013                  p = PieceToNumber((ChessSquare)p);
9014              if(p >= gameInfo.holdingsSize) p = 0;
9015              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9016                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9017              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9018                         board[BOARD_HEIGHT-1-p][1] = 0;
9019         }
9020       }
9021       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9022           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9023         /* [HGM] holdings: Add to holdings, if holdings exist */
9024         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9025                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9026                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9027         }
9028         p = (int) captured;
9029         if (p >= (int) BlackPawn) {
9030           p -= (int)BlackPawn;
9031           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9032                   /* in Shogi restore piece to its original  first */
9033                   captured = (ChessSquare) (DEMOTED captured);
9034                   p = DEMOTED p;
9035           }
9036           p = PieceToNumber((ChessSquare)p);
9037           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9038           board[p][BOARD_WIDTH-2]++;
9039           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9040         } else {
9041           p -= (int)WhitePawn;
9042           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9043                   captured = (ChessSquare) (DEMOTED captured);
9044                   p = DEMOTED p;
9045           }
9046           p = PieceToNumber((ChessSquare)p);
9047           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9048           board[BOARD_HEIGHT-1-p][1]++;
9049           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9050         }
9051       }
9052     } else if (gameInfo.variant == VariantAtomic) {
9053       if (captured != EmptySquare) {
9054         int y, x;
9055         for (y = toY-1; y <= toY+1; y++) {
9056           for (x = toX-1; x <= toX+1; x++) {
9057             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9058                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9059               board[y][x] = EmptySquare;
9060             }
9061           }
9062         }
9063         board[toY][toX] = EmptySquare;
9064       }
9065     }
9066     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9067         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9068     } else
9069     if(promoChar == '+') {
9070         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9071         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9072     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9073         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9074     }
9075     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9076                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9077         // [HGM] superchess: take promotion piece out of holdings
9078         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9079         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9080             if(!--board[k][BOARD_WIDTH-2])
9081                 board[k][BOARD_WIDTH-1] = EmptySquare;
9082         } else {
9083             if(!--board[BOARD_HEIGHT-1-k][1])
9084                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9085         }
9086     }
9087
9088 }
9089
9090 /* Updates forwardMostMove */
9091 void
9092 MakeMove(fromX, fromY, toX, toY, promoChar)
9093      int fromX, fromY, toX, toY;
9094      int promoChar;
9095 {
9096 //    forwardMostMove++; // [HGM] bare: moved downstream
9097
9098     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9099         int timeLeft; static int lastLoadFlag=0; int king, piece;
9100         piece = boards[forwardMostMove][fromY][fromX];
9101         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9102         if(gameInfo.variant == VariantKnightmate)
9103             king += (int) WhiteUnicorn - (int) WhiteKing;
9104         if(forwardMostMove == 0) {
9105             if(blackPlaysFirst)
9106                 fprintf(serverMoves, "%s;", second.tidy);
9107             fprintf(serverMoves, "%s;", first.tidy);
9108             if(!blackPlaysFirst)
9109                 fprintf(serverMoves, "%s;", second.tidy);
9110         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9111         lastLoadFlag = loadFlag;
9112         // print base move
9113         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9114         // print castling suffix
9115         if( toY == fromY && piece == king ) {
9116             if(toX-fromX > 1)
9117                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9118             if(fromX-toX >1)
9119                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9120         }
9121         // e.p. suffix
9122         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9123              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9124              boards[forwardMostMove][toY][toX] == EmptySquare
9125              && fromX != toX && fromY != toY)
9126                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9127         // promotion suffix
9128         if(promoChar != NULLCHAR)
9129                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9130         if(!loadFlag) {
9131             fprintf(serverMoves, "/%d/%d",
9132                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9133             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9134             else                      timeLeft = blackTimeRemaining/1000;
9135             fprintf(serverMoves, "/%d", timeLeft);
9136         }
9137         fflush(serverMoves);
9138     }
9139
9140     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9141       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9142                         0, 1);
9143       return;
9144     }
9145     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9146     if (commentList[forwardMostMove+1] != NULL) {
9147         free(commentList[forwardMostMove+1]);
9148         commentList[forwardMostMove+1] = NULL;
9149     }
9150     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9151     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9152     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9153     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9154     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9155     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9156     gameInfo.result = GameUnfinished;
9157     if (gameInfo.resultDetails != NULL) {
9158         free(gameInfo.resultDetails);
9159         gameInfo.resultDetails = NULL;
9160     }
9161     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9162                               moveList[forwardMostMove - 1]);
9163     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9164                              PosFlags(forwardMostMove - 1),
9165                              fromY, fromX, toY, toX, promoChar,
9166                              parseList[forwardMostMove - 1]);
9167     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9168       case MT_NONE:
9169       case MT_STALEMATE:
9170       default:
9171         break;
9172       case MT_CHECK:
9173         if(gameInfo.variant != VariantShogi)
9174             strcat(parseList[forwardMostMove - 1], "+");
9175         break;
9176       case MT_CHECKMATE:
9177       case MT_STAINMATE:
9178         strcat(parseList[forwardMostMove - 1], "#");
9179         break;
9180     }
9181     if (appData.debugMode) {
9182         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9183     }
9184
9185 }
9186
9187 /* Updates currentMove if not pausing */
9188 void
9189 ShowMove(fromX, fromY, toX, toY)
9190 {
9191     int instant = (gameMode == PlayFromGameFile) ?
9192         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9193     if(appData.noGUI) return;
9194     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9195         if (!instant) {
9196             if (forwardMostMove == currentMove + 1) {
9197                 AnimateMove(boards[forwardMostMove - 1],
9198                             fromX, fromY, toX, toY);
9199             }
9200             if (appData.highlightLastMove) {
9201                 SetHighlights(fromX, fromY, toX, toY);
9202             }
9203         }
9204         currentMove = forwardMostMove;
9205     }
9206
9207     if (instant) return;
9208
9209     DisplayMove(currentMove - 1);
9210     DrawPosition(FALSE, boards[currentMove]);
9211     DisplayBothClocks();
9212     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9213 }
9214
9215 void SendEgtPath(ChessProgramState *cps)
9216 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9217         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9218
9219         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9220
9221         while(*p) {
9222             char c, *q = name+1, *r, *s;
9223
9224             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9225             while(*p && *p != ',') *q++ = *p++;
9226             *q++ = ':'; *q = 0;
9227             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9228                 strcmp(name, ",nalimov:") == 0 ) {
9229                 // take nalimov path from the menu-changeable option first, if it is defined
9230               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9231                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9232             } else
9233             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9234                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9235                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9236                 s = r = StrStr(s, ":") + 1; // beginning of path info
9237                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9238                 c = *r; *r = 0;             // temporarily null-terminate path info
9239                     *--q = 0;               // strip of trailig ':' from name
9240                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9241                 *r = c;
9242                 SendToProgram(buf,cps);     // send egtbpath command for this format
9243             }
9244             if(*p == ',') p++; // read away comma to position for next format name
9245         }
9246 }
9247
9248 void
9249 InitChessProgram(cps, setup)
9250      ChessProgramState *cps;
9251      int setup; /* [HGM] needed to setup FRC opening position */
9252 {
9253     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9254     if (appData.noChessProgram) return;
9255     hintRequested = FALSE;
9256     bookRequested = FALSE;
9257
9258     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9259     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9260     if(cps->memSize) { /* [HGM] memory */
9261       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9262         SendToProgram(buf, cps);
9263     }
9264     SendEgtPath(cps); /* [HGM] EGT */
9265     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9266       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9267         SendToProgram(buf, cps);
9268     }
9269
9270     SendToProgram(cps->initString, cps);
9271     if (gameInfo.variant != VariantNormal &&
9272         gameInfo.variant != VariantLoadable
9273         /* [HGM] also send variant if board size non-standard */
9274         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9275                                             ) {
9276       char *v = VariantName(gameInfo.variant);
9277       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9278         /* [HGM] in protocol 1 we have to assume all variants valid */
9279         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9280         DisplayFatalError(buf, 0, 1);
9281         return;
9282       }
9283
9284       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9285       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9286       if( gameInfo.variant == VariantXiangqi )
9287            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9288       if( gameInfo.variant == VariantShogi )
9289            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9290       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9291            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9292       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9293           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9294            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9295       if( gameInfo.variant == VariantCourier )
9296            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9297       if( gameInfo.variant == VariantSuper )
9298            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9299       if( gameInfo.variant == VariantGreat )
9300            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9301       if( gameInfo.variant == VariantSChess )
9302            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9303
9304       if(overruled) {
9305         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9306                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9307            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9308            if(StrStr(cps->variants, b) == NULL) {
9309                // specific sized variant not known, check if general sizing allowed
9310                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9311                    if(StrStr(cps->variants, "boardsize") == NULL) {
9312                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9313                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9314                        DisplayFatalError(buf, 0, 1);
9315                        return;
9316                    }
9317                    /* [HGM] here we really should compare with the maximum supported board size */
9318                }
9319            }
9320       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9321       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9322       SendToProgram(buf, cps);
9323     }
9324     currentlyInitializedVariant = gameInfo.variant;
9325
9326     /* [HGM] send opening position in FRC to first engine */
9327     if(setup) {
9328           SendToProgram("force\n", cps);
9329           SendBoard(cps, 0);
9330           /* engine is now in force mode! Set flag to wake it up after first move. */
9331           setboardSpoiledMachineBlack = 1;
9332     }
9333
9334     if (cps->sendICS) {
9335       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9336       SendToProgram(buf, cps);
9337     }
9338     cps->maybeThinking = FALSE;
9339     cps->offeredDraw = 0;
9340     if (!appData.icsActive) {
9341         SendTimeControl(cps, movesPerSession, timeControl,
9342                         timeIncrement, appData.searchDepth,
9343                         searchTime);
9344     }
9345     if (appData.showThinking
9346         // [HGM] thinking: four options require thinking output to be sent
9347         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9348                                 ) {
9349         SendToProgram("post\n", cps);
9350     }
9351     SendToProgram("hard\n", cps);
9352     if (!appData.ponderNextMove) {
9353         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9354            it without being sure what state we are in first.  "hard"
9355            is not a toggle, so that one is OK.
9356          */
9357         SendToProgram("easy\n", cps);
9358     }
9359     if (cps->usePing) {
9360       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9361       SendToProgram(buf, cps);
9362     }
9363     cps->initDone = TRUE;
9364 }
9365
9366
9367 void
9368 StartChessProgram(cps)
9369      ChessProgramState *cps;
9370 {
9371     char buf[MSG_SIZ];
9372     int err;
9373
9374     if (appData.noChessProgram) return;
9375     cps->initDone = FALSE;
9376
9377     if (strcmp(cps->host, "localhost") == 0) {
9378         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9379     } else if (*appData.remoteShell == NULLCHAR) {
9380         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9381     } else {
9382         if (*appData.remoteUser == NULLCHAR) {
9383           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9384                     cps->program);
9385         } else {
9386           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9387                     cps->host, appData.remoteUser, cps->program);
9388         }
9389         err = StartChildProcess(buf, "", &cps->pr);
9390     }
9391
9392     if (err != 0) {
9393       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9394         DisplayFatalError(buf, err, 1);
9395         cps->pr = NoProc;
9396         cps->isr = NULL;
9397         return;
9398     }
9399
9400     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9401     if (cps->protocolVersion > 1) {
9402       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9403       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9404       cps->comboCnt = 0;  //                and values of combo boxes
9405       SendToProgram(buf, cps);
9406     } else {
9407       SendToProgram("xboard\n", cps);
9408     }
9409 }
9410
9411 void
9412 TwoMachinesEventIfReady P((void))
9413 {
9414   static int curMess = 0;
9415   if (first.lastPing != first.lastPong) {
9416     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9417     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9418     return;
9419   }
9420   if (second.lastPing != second.lastPong) {
9421     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9422     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9423     return;
9424   }
9425   DisplayMessage("", ""); curMess = 0;
9426   ThawUI();
9427   TwoMachinesEvent();
9428 }
9429
9430 int
9431 CreateTourney(char *name)
9432 {
9433         FILE *f;
9434         if(name[0] == NULLCHAR) return 0;
9435         f = fopen(appData.tourneyFile, "r");
9436         if(f) { // file exists
9437             ParseArgsFromFile(f); // parse it
9438         } else {
9439             f = fopen(appData.tourneyFile, "w");
9440             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9441                 // create a file with tournament description
9442                 fprintf(f, "-participants {%s}\n", appData.participants);
9443                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9444                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9445                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9446                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9447                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9448                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9449                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9450                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9451                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9452                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9453                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9454                 fprintf(f, "-results \"\"\n");
9455             }
9456         }
9457         fclose(f);
9458         appData.noChessProgram = FALSE;
9459         appData.clockMode = TRUE;
9460         SetGNUMode();
9461         return 1;
9462 }
9463
9464 #define MAXENGINES 1000
9465 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9466
9467 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9468 {
9469     char buf[MSG_SIZ], *p, *q;
9470     int i=1;
9471     while(*names) {
9472         p = names; q = buf;
9473         while(*p && *p != '\n') *q++ = *p++;
9474         *q = 0;
9475         if(engineList[i]) free(engineList[i]);
9476         engineList[i] = strdup(buf);
9477         if(*p == '\n') p++;
9478         TidyProgramName(engineList[i], "localhost", buf);
9479         if(engineMnemonic[i]) free(engineMnemonic[i]);
9480         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9481             strcat(buf, " (");
9482             sscanf(q + 8, "%s", buf + strlen(buf));
9483             strcat(buf, ")");
9484         }
9485         engineMnemonic[i] = strdup(buf);
9486         names = p; i++;
9487       if(i > MAXENGINES - 2) break;
9488     }
9489     engineList[i] = NULL;
9490 }
9491
9492 // following implemented as macro to avoid type limitations
9493 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9494
9495 void SwapEngines(int n)
9496 {   // swap settings for first engine and other engine (so far only some selected options)
9497     int h;
9498     char *p;
9499     if(n == 0) return;
9500     SWAP(directory, p)
9501     SWAP(chessProgram, p)
9502     SWAP(isUCI, h)
9503     SWAP(hasOwnBookUCI, h)
9504     SWAP(protocolVersion, h)
9505     SWAP(reuse, h)
9506     SWAP(scoreIsAbsolute, h)
9507     SWAP(timeOdds, h)
9508     SWAP(logo, p)
9509 }
9510
9511 void
9512 SetPlayer(int player)
9513 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9514     int i;
9515     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9516     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9517                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9518     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9519     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9520     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9521     if(mnemonic[i]) {
9522         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9523         ParseArgsFromString(resetOptions);
9524         ParseArgsFromString(buf);
9525     }
9526     free(engineName);
9527 }
9528
9529 int
9530 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9531 {   // determine players from game number
9532     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9533
9534     if(appData.tourneyType == 0) {
9535         roundsPerCycle = (nPlayers - 1) | 1;
9536         pairingsPerRound = nPlayers / 2;
9537     } else if(appData.tourneyType > 0) {
9538         roundsPerCycle = nPlayers - appData.tourneyType;
9539         pairingsPerRound = appData.tourneyType;
9540     }
9541     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9542     gamesPerCycle = gamesPerRound * roundsPerCycle;
9543     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9544     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9545     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9546     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9547     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9548     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9549
9550     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9551     if(appData.roundSync) *syncInterval = gamesPerRound;
9552
9553     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9554
9555     if(appData.tourneyType == 0) {
9556         if(curPairing == (nPlayers-1)/2 ) {
9557             *whitePlayer = curRound;
9558             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9559         } else {
9560             *whitePlayer = curRound - pairingsPerRound + curPairing;
9561             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9562             *blackPlayer = curRound + pairingsPerRound - curPairing;
9563             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9564         }
9565     } else if(appData.tourneyType > 0) {
9566         *whitePlayer = curPairing;
9567         *blackPlayer = curRound + appData.tourneyType;
9568     }
9569
9570     // take care of white/black alternation per round. 
9571     // For cycles and games this is already taken care of by default, derived from matchGame!
9572     return curRound & 1;
9573 }
9574
9575 int
9576 NextTourneyGame(int nr, int *swapColors)
9577 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9578     char *p, *q;
9579     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9580     FILE *tf;
9581     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9582     tf = fopen(appData.tourneyFile, "r");
9583     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9584     ParseArgsFromFile(tf); fclose(tf);
9585
9586     p = appData.participants;
9587     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9588     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9589
9590     if(syncInterval) {
9591         p = q = appData.results;
9592         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9593         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9594             DisplayMessage(_("Waiting for other game(s)"),"");
9595             waitingForGame = TRUE;
9596             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9597             return 0;
9598         }
9599         waitingForGame = FALSE;
9600     }
9601
9602     if(first.pr != NoProc) return 1; // engines already loaded
9603
9604     // redefine engines, engine dir, etc.
9605     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9606     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9607     SwapEngines(1);
9608     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9609     SwapEngines(1);         // and make that valid for second engine by swapping
9610     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9611     InitEngine(&second, 1);
9612     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9613     return 1;
9614 }
9615
9616 void
9617 NextMatchGame()
9618 {   // performs game initialization that does not invoke engines, and then tries to start the game
9619     int firstWhite, swapColors = 0;
9620     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9621     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9622     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9623     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9624     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9625     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9626     Reset(FALSE, first.pr != NoProc);
9627     appData.noChessProgram = FALSE;
9628     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9629     TwoMachinesEvent();
9630 }
9631
9632 void UserAdjudicationEvent( int result )
9633 {
9634     ChessMove gameResult = GameIsDrawn;
9635
9636     if( result > 0 ) {
9637         gameResult = WhiteWins;
9638     }
9639     else if( result < 0 ) {
9640         gameResult = BlackWins;
9641     }
9642
9643     if( gameMode == TwoMachinesPlay ) {
9644         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9645     }
9646 }
9647
9648
9649 // [HGM] save: calculate checksum of game to make games easily identifiable
9650 int StringCheckSum(char *s)
9651 {
9652         int i = 0;
9653         if(s==NULL) return 0;
9654         while(*s) i = i*259 + *s++;
9655         return i;
9656 }
9657
9658 int GameCheckSum()
9659 {
9660         int i, sum=0;
9661         for(i=backwardMostMove; i<forwardMostMove; i++) {
9662                 sum += pvInfoList[i].depth;
9663                 sum += StringCheckSum(parseList[i]);
9664                 sum += StringCheckSum(commentList[i]);
9665                 sum *= 261;
9666         }
9667         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9668         return sum + StringCheckSum(commentList[i]);
9669 } // end of save patch
9670
9671 void
9672 GameEnds(result, resultDetails, whosays)
9673      ChessMove result;
9674      char *resultDetails;
9675      int whosays;
9676 {
9677     GameMode nextGameMode;
9678     int isIcsGame;
9679     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9680
9681     if(endingGame) return; /* [HGM] crash: forbid recursion */
9682     endingGame = 1;
9683     if(twoBoards) { // [HGM] dual: switch back to one board
9684         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9685         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9686     }
9687     if (appData.debugMode) {
9688       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9689               result, resultDetails ? resultDetails : "(null)", whosays);
9690     }
9691
9692     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9693
9694     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9695         /* If we are playing on ICS, the server decides when the
9696            game is over, but the engine can offer to draw, claim
9697            a draw, or resign.
9698          */
9699 #if ZIPPY
9700         if (appData.zippyPlay && first.initDone) {
9701             if (result == GameIsDrawn) {
9702                 /* In case draw still needs to be claimed */
9703                 SendToICS(ics_prefix);
9704                 SendToICS("draw\n");
9705             } else if (StrCaseStr(resultDetails, "resign")) {
9706                 SendToICS(ics_prefix);
9707                 SendToICS("resign\n");
9708             }
9709         }
9710 #endif
9711         endingGame = 0; /* [HGM] crash */
9712         return;
9713     }
9714
9715     /* If we're loading the game from a file, stop */
9716     if (whosays == GE_FILE) {
9717       (void) StopLoadGameTimer();
9718       gameFileFP = NULL;
9719     }
9720
9721     /* Cancel draw offers */
9722     first.offeredDraw = second.offeredDraw = 0;
9723
9724     /* If this is an ICS game, only ICS can really say it's done;
9725        if not, anyone can. */
9726     isIcsGame = (gameMode == IcsPlayingWhite ||
9727                  gameMode == IcsPlayingBlack ||
9728                  gameMode == IcsObserving    ||
9729                  gameMode == IcsExamining);
9730
9731     if (!isIcsGame || whosays == GE_ICS) {
9732         /* OK -- not an ICS game, or ICS said it was done */
9733         StopClocks();
9734         if (!isIcsGame && !appData.noChessProgram)
9735           SetUserThinkingEnables();
9736
9737         /* [HGM] if a machine claims the game end we verify this claim */
9738         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9739             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9740                 char claimer;
9741                 ChessMove trueResult = (ChessMove) -1;
9742
9743                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9744                                             first.twoMachinesColor[0] :
9745                                             second.twoMachinesColor[0] ;
9746
9747                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9748                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9749                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9750                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9751                 } else
9752                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9753                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9754                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9755                 } else
9756                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9757                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9758                 }
9759
9760                 // now verify win claims, but not in drop games, as we don't understand those yet
9761                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9762                                                  || gameInfo.variant == VariantGreat) &&
9763                     (result == WhiteWins && claimer == 'w' ||
9764                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9765                       if (appData.debugMode) {
9766                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9767                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9768                       }
9769                       if(result != trueResult) {
9770                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9771                               result = claimer == 'w' ? BlackWins : WhiteWins;
9772                               resultDetails = buf;
9773                       }
9774                 } else
9775                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9776                     && (forwardMostMove <= backwardMostMove ||
9777                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9778                         (claimer=='b')==(forwardMostMove&1))
9779                                                                                   ) {
9780                       /* [HGM] verify: draws that were not flagged are false claims */
9781                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9782                       result = claimer == 'w' ? BlackWins : WhiteWins;
9783                       resultDetails = buf;
9784                 }
9785                 /* (Claiming a loss is accepted no questions asked!) */
9786             }
9787             /* [HGM] bare: don't allow bare King to win */
9788             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9789                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9790                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9791                && result != GameIsDrawn)
9792             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9793                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9794                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9795                         if(p >= 0 && p <= (int)WhiteKing) k++;
9796                 }
9797                 if (appData.debugMode) {
9798                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9799                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9800                 }
9801                 if(k <= 1) {
9802                         result = GameIsDrawn;
9803                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9804                         resultDetails = buf;
9805                 }
9806             }
9807         }
9808
9809
9810         if(serverMoves != NULL && !loadFlag) { char c = '=';
9811             if(result==WhiteWins) c = '+';
9812             if(result==BlackWins) c = '-';
9813             if(resultDetails != NULL)
9814                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9815         }
9816         if (resultDetails != NULL) {
9817             gameInfo.result = result;
9818             gameInfo.resultDetails = StrSave(resultDetails);
9819
9820             /* display last move only if game was not loaded from file */
9821             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9822                 DisplayMove(currentMove - 1);
9823
9824             if (forwardMostMove != 0) {
9825                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9826                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9827                                                                 ) {
9828                     if (*appData.saveGameFile != NULLCHAR) {
9829                         SaveGameToFile(appData.saveGameFile, TRUE);
9830                     } else if (appData.autoSaveGames) {
9831                         AutoSaveGame();
9832                     }
9833                     if (*appData.savePositionFile != NULLCHAR) {
9834                         SavePositionToFile(appData.savePositionFile);
9835                     }
9836                 }
9837             }
9838
9839             /* Tell program how game ended in case it is learning */
9840             /* [HGM] Moved this to after saving the PGN, just in case */
9841             /* engine died and we got here through time loss. In that */
9842             /* case we will get a fatal error writing the pipe, which */
9843             /* would otherwise lose us the PGN.                       */
9844             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9845             /* output during GameEnds should never be fatal anymore   */
9846             if (gameMode == MachinePlaysWhite ||
9847                 gameMode == MachinePlaysBlack ||
9848                 gameMode == TwoMachinesPlay ||
9849                 gameMode == IcsPlayingWhite ||
9850                 gameMode == IcsPlayingBlack ||
9851                 gameMode == BeginningOfGame) {
9852                 char buf[MSG_SIZ];
9853                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9854                         resultDetails);
9855                 if (first.pr != NoProc) {
9856                     SendToProgram(buf, &first);
9857                 }
9858                 if (second.pr != NoProc &&
9859                     gameMode == TwoMachinesPlay) {
9860                     SendToProgram(buf, &second);
9861                 }
9862             }
9863         }
9864
9865         if (appData.icsActive) {
9866             if (appData.quietPlay &&
9867                 (gameMode == IcsPlayingWhite ||
9868                  gameMode == IcsPlayingBlack)) {
9869                 SendToICS(ics_prefix);
9870                 SendToICS("set shout 1\n");
9871             }
9872             nextGameMode = IcsIdle;
9873             ics_user_moved = FALSE;
9874             /* clean up premove.  It's ugly when the game has ended and the
9875              * premove highlights are still on the board.
9876              */
9877             if (gotPremove) {
9878               gotPremove = FALSE;
9879               ClearPremoveHighlights();
9880               DrawPosition(FALSE, boards[currentMove]);
9881             }
9882             if (whosays == GE_ICS) {
9883                 switch (result) {
9884                 case WhiteWins:
9885                     if (gameMode == IcsPlayingWhite)
9886                         PlayIcsWinSound();
9887                     else if(gameMode == IcsPlayingBlack)
9888                         PlayIcsLossSound();
9889                     break;
9890                 case BlackWins:
9891                     if (gameMode == IcsPlayingBlack)
9892                         PlayIcsWinSound();
9893                     else if(gameMode == IcsPlayingWhite)
9894                         PlayIcsLossSound();
9895                     break;
9896                 case GameIsDrawn:
9897                     PlayIcsDrawSound();
9898                     break;
9899                 default:
9900                     PlayIcsUnfinishedSound();
9901                 }
9902             }
9903         } else if (gameMode == EditGame ||
9904                    gameMode == PlayFromGameFile ||
9905                    gameMode == AnalyzeMode ||
9906                    gameMode == AnalyzeFile) {
9907             nextGameMode = gameMode;
9908         } else {
9909             nextGameMode = EndOfGame;
9910         }
9911         pausing = FALSE;
9912         ModeHighlight();
9913     } else {
9914         nextGameMode = gameMode;
9915     }
9916
9917     if (appData.noChessProgram) {
9918         gameMode = nextGameMode;
9919         ModeHighlight();
9920         endingGame = 0; /* [HGM] crash */
9921         return;
9922     }
9923
9924     if (first.reuse) {
9925         /* Put first chess program into idle state */
9926         if (first.pr != NoProc &&
9927             (gameMode == MachinePlaysWhite ||
9928              gameMode == MachinePlaysBlack ||
9929              gameMode == TwoMachinesPlay ||
9930              gameMode == IcsPlayingWhite ||
9931              gameMode == IcsPlayingBlack ||
9932              gameMode == BeginningOfGame)) {
9933             SendToProgram("force\n", &first);
9934             if (first.usePing) {
9935               char buf[MSG_SIZ];
9936               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9937               SendToProgram(buf, &first);
9938             }
9939         }
9940     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9941         /* Kill off first chess program */
9942         if (first.isr != NULL)
9943           RemoveInputSource(first.isr);
9944         first.isr = NULL;
9945
9946         if (first.pr != NoProc) {
9947             ExitAnalyzeMode();
9948             DoSleep( appData.delayBeforeQuit );
9949             SendToProgram("quit\n", &first);
9950             DoSleep( appData.delayAfterQuit );
9951             DestroyChildProcess(first.pr, first.useSigterm);
9952         }
9953         first.pr = NoProc;
9954     }
9955     if (second.reuse) {
9956         /* Put second chess program into idle state */
9957         if (second.pr != NoProc &&
9958             gameMode == TwoMachinesPlay) {
9959             SendToProgram("force\n", &second);
9960             if (second.usePing) {
9961               char buf[MSG_SIZ];
9962               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9963               SendToProgram(buf, &second);
9964             }
9965         }
9966     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9967         /* Kill off second chess program */
9968         if (second.isr != NULL)
9969           RemoveInputSource(second.isr);
9970         second.isr = NULL;
9971
9972         if (second.pr != NoProc) {
9973             DoSleep( appData.delayBeforeQuit );
9974             SendToProgram("quit\n", &second);
9975             DoSleep( appData.delayAfterQuit );
9976             DestroyChildProcess(second.pr, second.useSigterm);
9977         }
9978         second.pr = NoProc;
9979     }
9980
9981     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9982         char resChar = '=';
9983         switch (result) {
9984         case WhiteWins:
9985           resChar = '+';
9986           if (first.twoMachinesColor[0] == 'w') {
9987             first.matchWins++;
9988           } else {
9989             second.matchWins++;
9990           }
9991           break;
9992         case BlackWins:
9993           resChar = '-';
9994           if (first.twoMachinesColor[0] == 'b') {
9995             first.matchWins++;
9996           } else {
9997             second.matchWins++;
9998           }
9999           break;
10000         case GameUnfinished:
10001           resChar = ' ';
10002         default:
10003           break;
10004         }
10005
10006         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10007         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10008             ReserveGame(nextGame, resChar); // sets nextGame
10009             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10010         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10011
10012         if (nextGame <= appData.matchGames) {
10013             gameMode = nextGameMode;
10014             matchGame = nextGame; // this will be overruled in tourney mode!
10015             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10016             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10017             endingGame = 0; /* [HGM] crash */
10018             return;
10019         } else {
10020             gameMode = nextGameMode;
10021             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10022                      first.tidy, second.tidy,
10023                      first.matchWins, second.matchWins,
10024                      appData.matchGames - (first.matchWins + second.matchWins));
10025             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10026             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10027                 first.twoMachinesColor = "black\n";
10028                 second.twoMachinesColor = "white\n";
10029             } else {
10030                 first.twoMachinesColor = "white\n";
10031                 second.twoMachinesColor = "black\n";
10032             }
10033         }
10034     }
10035     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10036         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10037       ExitAnalyzeMode();
10038     gameMode = nextGameMode;
10039     ModeHighlight();
10040     endingGame = 0;  /* [HGM] crash */
10041     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10042       if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10043         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10044         DisplayNote(ranking ? ranking : buf);
10045       }
10046       if(ranking) free(ranking);
10047     }
10048 }
10049
10050 /* Assumes program was just initialized (initString sent).
10051    Leaves program in force mode. */
10052 void
10053 FeedMovesToProgram(cps, upto)
10054      ChessProgramState *cps;
10055      int upto;
10056 {
10057     int i;
10058
10059     if (appData.debugMode)
10060       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10061               startedFromSetupPosition ? "position and " : "",
10062               backwardMostMove, upto, cps->which);
10063     if(currentlyInitializedVariant != gameInfo.variant) {
10064       char buf[MSG_SIZ];
10065         // [HGM] variantswitch: make engine aware of new variant
10066         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10067                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10068         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10069         SendToProgram(buf, cps);
10070         currentlyInitializedVariant = gameInfo.variant;
10071     }
10072     SendToProgram("force\n", cps);
10073     if (startedFromSetupPosition) {
10074         SendBoard(cps, backwardMostMove);
10075     if (appData.debugMode) {
10076         fprintf(debugFP, "feedMoves\n");
10077     }
10078     }
10079     for (i = backwardMostMove; i < upto; i++) {
10080         SendMoveToProgram(i, cps);
10081     }
10082 }
10083
10084
10085 int
10086 ResurrectChessProgram()
10087 {
10088      /* The chess program may have exited.
10089         If so, restart it and feed it all the moves made so far. */
10090     static int doInit = 0;
10091
10092     if (appData.noChessProgram) return 1;
10093
10094     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10095         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10096         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10097         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10098     } else {
10099         if (first.pr != NoProc) return 1;
10100         StartChessProgram(&first);
10101     }
10102     InitChessProgram(&first, FALSE);
10103     FeedMovesToProgram(&first, currentMove);
10104
10105     if (!first.sendTime) {
10106         /* can't tell gnuchess what its clock should read,
10107            so we bow to its notion. */
10108         ResetClocks();
10109         timeRemaining[0][currentMove] = whiteTimeRemaining;
10110         timeRemaining[1][currentMove] = blackTimeRemaining;
10111     }
10112
10113     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10114                 appData.icsEngineAnalyze) && first.analysisSupport) {
10115       SendToProgram("analyze\n", &first);
10116       first.analyzing = TRUE;
10117     }
10118     return 1;
10119 }
10120
10121 /*
10122  * Button procedures
10123  */
10124 void
10125 Reset(redraw, init)
10126      int redraw, init;
10127 {
10128     int i;
10129
10130     if (appData.debugMode) {
10131         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10132                 redraw, init, gameMode);
10133     }
10134     CleanupTail(); // [HGM] vari: delete any stored variations
10135     pausing = pauseExamInvalid = FALSE;
10136     startedFromSetupPosition = blackPlaysFirst = FALSE;
10137     firstMove = TRUE;
10138     whiteFlag = blackFlag = FALSE;
10139     userOfferedDraw = FALSE;
10140     hintRequested = bookRequested = FALSE;
10141     first.maybeThinking = FALSE;
10142     second.maybeThinking = FALSE;
10143     first.bookSuspend = FALSE; // [HGM] book
10144     second.bookSuspend = FALSE;
10145     thinkOutput[0] = NULLCHAR;
10146     lastHint[0] = NULLCHAR;
10147     ClearGameInfo(&gameInfo);
10148     gameInfo.variant = StringToVariant(appData.variant);
10149     ics_user_moved = ics_clock_paused = FALSE;
10150     ics_getting_history = H_FALSE;
10151     ics_gamenum = -1;
10152     white_holding[0] = black_holding[0] = NULLCHAR;
10153     ClearProgramStats();
10154     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10155
10156     ResetFrontEnd();
10157     ClearHighlights();
10158     flipView = appData.flipView;
10159     ClearPremoveHighlights();
10160     gotPremove = FALSE;
10161     alarmSounded = FALSE;
10162
10163     GameEnds(EndOfFile, NULL, GE_PLAYER);
10164     if(appData.serverMovesName != NULL) {
10165         /* [HGM] prepare to make moves file for broadcasting */
10166         clock_t t = clock();
10167         if(serverMoves != NULL) fclose(serverMoves);
10168         serverMoves = fopen(appData.serverMovesName, "r");
10169         if(serverMoves != NULL) {
10170             fclose(serverMoves);
10171             /* delay 15 sec before overwriting, so all clients can see end */
10172             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10173         }
10174         serverMoves = fopen(appData.serverMovesName, "w");
10175     }
10176
10177     ExitAnalyzeMode();
10178     gameMode = BeginningOfGame;
10179     ModeHighlight();
10180     if(appData.icsActive) gameInfo.variant = VariantNormal;
10181     currentMove = forwardMostMove = backwardMostMove = 0;
10182     InitPosition(redraw);
10183     for (i = 0; i < MAX_MOVES; i++) {
10184         if (commentList[i] != NULL) {
10185             free(commentList[i]);
10186             commentList[i] = NULL;
10187         }
10188     }
10189     ResetClocks();
10190     timeRemaining[0][0] = whiteTimeRemaining;
10191     timeRemaining[1][0] = blackTimeRemaining;
10192
10193     if (first.pr == NULL) {
10194         StartChessProgram(&first);
10195     }
10196     if (init) {
10197             InitChessProgram(&first, startedFromSetupPosition);
10198     }
10199     DisplayTitle("");
10200     DisplayMessage("", "");
10201     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10202     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10203 }
10204
10205 void
10206 AutoPlayGameLoop()
10207 {
10208     for (;;) {
10209         if (!AutoPlayOneMove())
10210           return;
10211         if (matchMode || appData.timeDelay == 0)
10212           continue;
10213         if (appData.timeDelay < 0)
10214           return;
10215         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10216         break;
10217     }
10218 }
10219
10220
10221 int
10222 AutoPlayOneMove()
10223 {
10224     int fromX, fromY, toX, toY;
10225
10226     if (appData.debugMode) {
10227       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10228     }
10229
10230     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10231       return FALSE;
10232
10233     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10234       pvInfoList[currentMove].depth = programStats.depth;
10235       pvInfoList[currentMove].score = programStats.score;
10236       pvInfoList[currentMove].time  = 0;
10237       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10238     }
10239
10240     if (currentMove >= forwardMostMove) {
10241       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10242       gameMode = EditGame;
10243       ModeHighlight();
10244
10245       /* [AS] Clear current move marker at the end of a game */
10246       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10247
10248       return FALSE;
10249     }
10250
10251     toX = moveList[currentMove][2] - AAA;
10252     toY = moveList[currentMove][3] - ONE;
10253
10254     if (moveList[currentMove][1] == '@') {
10255         if (appData.highlightLastMove) {
10256             SetHighlights(-1, -1, toX, toY);
10257         }
10258     } else {
10259         fromX = moveList[currentMove][0] - AAA;
10260         fromY = moveList[currentMove][1] - ONE;
10261
10262         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10263
10264         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10265
10266         if (appData.highlightLastMove) {
10267             SetHighlights(fromX, fromY, toX, toY);
10268         }
10269     }
10270     DisplayMove(currentMove);
10271     SendMoveToProgram(currentMove++, &first);
10272     DisplayBothClocks();
10273     DrawPosition(FALSE, boards[currentMove]);
10274     // [HGM] PV info: always display, routine tests if empty
10275     DisplayComment(currentMove - 1, commentList[currentMove]);
10276     return TRUE;
10277 }
10278
10279
10280 int
10281 LoadGameOneMove(readAhead)
10282      ChessMove readAhead;
10283 {
10284     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10285     char promoChar = NULLCHAR;
10286     ChessMove moveType;
10287     char move[MSG_SIZ];
10288     char *p, *q;
10289
10290     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10291         gameMode != AnalyzeMode && gameMode != Training) {
10292         gameFileFP = NULL;
10293         return FALSE;
10294     }
10295
10296     yyboardindex = forwardMostMove;
10297     if (readAhead != EndOfFile) {
10298       moveType = readAhead;
10299     } else {
10300       if (gameFileFP == NULL)
10301           return FALSE;
10302       moveType = (ChessMove) Myylex();
10303     }
10304
10305     done = FALSE;
10306     switch (moveType) {
10307       case Comment:
10308         if (appData.debugMode)
10309           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10310         p = yy_text;
10311
10312         /* append the comment but don't display it */
10313         AppendComment(currentMove, p, FALSE);
10314         return TRUE;
10315
10316       case WhiteCapturesEnPassant:
10317       case BlackCapturesEnPassant:
10318       case WhitePromotion:
10319       case BlackPromotion:
10320       case WhiteNonPromotion:
10321       case BlackNonPromotion:
10322       case NormalMove:
10323       case WhiteKingSideCastle:
10324       case WhiteQueenSideCastle:
10325       case BlackKingSideCastle:
10326       case BlackQueenSideCastle:
10327       case WhiteKingSideCastleWild:
10328       case WhiteQueenSideCastleWild:
10329       case BlackKingSideCastleWild:
10330       case BlackQueenSideCastleWild:
10331       /* PUSH Fabien */
10332       case WhiteHSideCastleFR:
10333       case WhiteASideCastleFR:
10334       case BlackHSideCastleFR:
10335       case BlackASideCastleFR:
10336       /* POP Fabien */
10337         if (appData.debugMode)
10338           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10339         fromX = currentMoveString[0] - AAA;
10340         fromY = currentMoveString[1] - ONE;
10341         toX = currentMoveString[2] - AAA;
10342         toY = currentMoveString[3] - ONE;
10343         promoChar = currentMoveString[4];
10344         break;
10345
10346       case WhiteDrop:
10347       case BlackDrop:
10348         if (appData.debugMode)
10349           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10350         fromX = moveType == WhiteDrop ?
10351           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10352         (int) CharToPiece(ToLower(currentMoveString[0]));
10353         fromY = DROP_RANK;
10354         toX = currentMoveString[2] - AAA;
10355         toY = currentMoveString[3] - ONE;
10356         break;
10357
10358       case WhiteWins:
10359       case BlackWins:
10360       case GameIsDrawn:
10361       case GameUnfinished:
10362         if (appData.debugMode)
10363           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10364         p = strchr(yy_text, '{');
10365         if (p == NULL) p = strchr(yy_text, '(');
10366         if (p == NULL) {
10367             p = yy_text;
10368             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10369         } else {
10370             q = strchr(p, *p == '{' ? '}' : ')');
10371             if (q != NULL) *q = NULLCHAR;
10372             p++;
10373         }
10374         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10375         GameEnds(moveType, p, GE_FILE);
10376         done = TRUE;
10377         if (cmailMsgLoaded) {
10378             ClearHighlights();
10379             flipView = WhiteOnMove(currentMove);
10380             if (moveType == GameUnfinished) flipView = !flipView;
10381             if (appData.debugMode)
10382               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10383         }
10384         break;
10385
10386       case EndOfFile:
10387         if (appData.debugMode)
10388           fprintf(debugFP, "Parser hit end of file\n");
10389         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10390           case MT_NONE:
10391           case MT_CHECK:
10392             break;
10393           case MT_CHECKMATE:
10394           case MT_STAINMATE:
10395             if (WhiteOnMove(currentMove)) {
10396                 GameEnds(BlackWins, "Black mates", GE_FILE);
10397             } else {
10398                 GameEnds(WhiteWins, "White mates", GE_FILE);
10399             }
10400             break;
10401           case MT_STALEMATE:
10402             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10403             break;
10404         }
10405         done = TRUE;
10406         break;
10407
10408       case MoveNumberOne:
10409         if (lastLoadGameStart == GNUChessGame) {
10410             /* GNUChessGames have numbers, but they aren't move numbers */
10411             if (appData.debugMode)
10412               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10413                       yy_text, (int) moveType);
10414             return LoadGameOneMove(EndOfFile); /* tail recursion */
10415         }
10416         /* else fall thru */
10417
10418       case XBoardGame:
10419       case GNUChessGame:
10420       case PGNTag:
10421         /* Reached start of next game in file */
10422         if (appData.debugMode)
10423           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10424         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10425           case MT_NONE:
10426           case MT_CHECK:
10427             break;
10428           case MT_CHECKMATE:
10429           case MT_STAINMATE:
10430             if (WhiteOnMove(currentMove)) {
10431                 GameEnds(BlackWins, "Black mates", GE_FILE);
10432             } else {
10433                 GameEnds(WhiteWins, "White mates", GE_FILE);
10434             }
10435             break;
10436           case MT_STALEMATE:
10437             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10438             break;
10439         }
10440         done = TRUE;
10441         break;
10442
10443       case PositionDiagram:     /* should not happen; ignore */
10444       case ElapsedTime:         /* ignore */
10445       case NAG:                 /* ignore */
10446         if (appData.debugMode)
10447           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10448                   yy_text, (int) moveType);
10449         return LoadGameOneMove(EndOfFile); /* tail recursion */
10450
10451       case IllegalMove:
10452         if (appData.testLegality) {
10453             if (appData.debugMode)
10454               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10455             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10456                     (forwardMostMove / 2) + 1,
10457                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10458             DisplayError(move, 0);
10459             done = TRUE;
10460         } else {
10461             if (appData.debugMode)
10462               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10463                       yy_text, currentMoveString);
10464             fromX = currentMoveString[0] - AAA;
10465             fromY = currentMoveString[1] - ONE;
10466             toX = currentMoveString[2] - AAA;
10467             toY = currentMoveString[3] - ONE;
10468             promoChar = currentMoveString[4];
10469         }
10470         break;
10471
10472       case AmbiguousMove:
10473         if (appData.debugMode)
10474           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10475         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10476                 (forwardMostMove / 2) + 1,
10477                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10478         DisplayError(move, 0);
10479         done = TRUE;
10480         break;
10481
10482       default:
10483       case ImpossibleMove:
10484         if (appData.debugMode)
10485           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10486         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10487                 (forwardMostMove / 2) + 1,
10488                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10489         DisplayError(move, 0);
10490         done = TRUE;
10491         break;
10492     }
10493
10494     if (done) {
10495         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10496             DrawPosition(FALSE, boards[currentMove]);
10497             DisplayBothClocks();
10498             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10499               DisplayComment(currentMove - 1, commentList[currentMove]);
10500         }
10501         (void) StopLoadGameTimer();
10502         gameFileFP = NULL;
10503         cmailOldMove = forwardMostMove;
10504         return FALSE;
10505     } else {
10506         /* currentMoveString is set as a side-effect of yylex */
10507
10508         thinkOutput[0] = NULLCHAR;
10509         MakeMove(fromX, fromY, toX, toY, promoChar);
10510         currentMove = forwardMostMove;
10511         return TRUE;
10512     }
10513 }
10514
10515 /* Load the nth game from the given file */
10516 int
10517 LoadGameFromFile(filename, n, title, useList)
10518      char *filename;
10519      int n;
10520      char *title;
10521      /*Boolean*/ int useList;
10522 {
10523     FILE *f;
10524     char buf[MSG_SIZ];
10525
10526     if (strcmp(filename, "-") == 0) {
10527         f = stdin;
10528         title = "stdin";
10529     } else {
10530         f = fopen(filename, "rb");
10531         if (f == NULL) {
10532           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10533             DisplayError(buf, errno);
10534             return FALSE;
10535         }
10536     }
10537     if (fseek(f, 0, 0) == -1) {
10538         /* f is not seekable; probably a pipe */
10539         useList = FALSE;
10540     }
10541     if (useList && n == 0) {
10542         int error = GameListBuild(f);
10543         if (error) {
10544             DisplayError(_("Cannot build game list"), error);
10545         } else if (!ListEmpty(&gameList) &&
10546                    ((ListGame *) gameList.tailPred)->number > 1) {
10547             GameListPopUp(f, title);
10548             return TRUE;
10549         }
10550         GameListDestroy();
10551         n = 1;
10552     }
10553     if (n == 0) n = 1;
10554     return LoadGame(f, n, title, FALSE);
10555 }
10556
10557
10558 void
10559 MakeRegisteredMove()
10560 {
10561     int fromX, fromY, toX, toY;
10562     char promoChar;
10563     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10564         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10565           case CMAIL_MOVE:
10566           case CMAIL_DRAW:
10567             if (appData.debugMode)
10568               fprintf(debugFP, "Restoring %s for game %d\n",
10569                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10570
10571             thinkOutput[0] = NULLCHAR;
10572             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10573             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10574             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10575             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10576             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10577             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10578             MakeMove(fromX, fromY, toX, toY, promoChar);
10579             ShowMove(fromX, fromY, toX, toY);
10580
10581             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10582               case MT_NONE:
10583               case MT_CHECK:
10584                 break;
10585
10586               case MT_CHECKMATE:
10587               case MT_STAINMATE:
10588                 if (WhiteOnMove(currentMove)) {
10589                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10590                 } else {
10591                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10592                 }
10593                 break;
10594
10595               case MT_STALEMATE:
10596                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10597                 break;
10598             }
10599
10600             break;
10601
10602           case CMAIL_RESIGN:
10603             if (WhiteOnMove(currentMove)) {
10604                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10605             } else {
10606                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10607             }
10608             break;
10609
10610           case CMAIL_ACCEPT:
10611             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10612             break;
10613
10614           default:
10615             break;
10616         }
10617     }
10618
10619     return;
10620 }
10621
10622 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10623 int
10624 CmailLoadGame(f, gameNumber, title, useList)
10625      FILE *f;
10626      int gameNumber;
10627      char *title;
10628      int useList;
10629 {
10630     int retVal;
10631
10632     if (gameNumber > nCmailGames) {
10633         DisplayError(_("No more games in this message"), 0);
10634         return FALSE;
10635     }
10636     if (f == lastLoadGameFP) {
10637         int offset = gameNumber - lastLoadGameNumber;
10638         if (offset == 0) {
10639             cmailMsg[0] = NULLCHAR;
10640             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10641                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10642                 nCmailMovesRegistered--;
10643             }
10644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10645             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10646                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10647             }
10648         } else {
10649             if (! RegisterMove()) return FALSE;
10650         }
10651     }
10652
10653     retVal = LoadGame(f, gameNumber, title, useList);
10654
10655     /* Make move registered during previous look at this game, if any */
10656     MakeRegisteredMove();
10657
10658     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10659         commentList[currentMove]
10660           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10661         DisplayComment(currentMove - 1, commentList[currentMove]);
10662     }
10663
10664     return retVal;
10665 }
10666
10667 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10668 int
10669 ReloadGame(offset)
10670      int offset;
10671 {
10672     int gameNumber = lastLoadGameNumber + offset;
10673     if (lastLoadGameFP == NULL) {
10674         DisplayError(_("No game has been loaded yet"), 0);
10675         return FALSE;
10676     }
10677     if (gameNumber <= 0) {
10678         DisplayError(_("Can't back up any further"), 0);
10679         return FALSE;
10680     }
10681     if (cmailMsgLoaded) {
10682         return CmailLoadGame(lastLoadGameFP, gameNumber,
10683                              lastLoadGameTitle, lastLoadGameUseList);
10684     } else {
10685         return LoadGame(lastLoadGameFP, gameNumber,
10686                         lastLoadGameTitle, lastLoadGameUseList);
10687     }
10688 }
10689
10690
10691
10692 /* Load the nth game from open file f */
10693 int
10694 LoadGame(f, gameNumber, title, useList)
10695      FILE *f;
10696      int gameNumber;
10697      char *title;
10698      int useList;
10699 {
10700     ChessMove cm;
10701     char buf[MSG_SIZ];
10702     int gn = gameNumber;
10703     ListGame *lg = NULL;
10704     int numPGNTags = 0;
10705     int err;
10706     GameMode oldGameMode;
10707     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10708
10709     if (appData.debugMode)
10710         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10711
10712     if (gameMode == Training )
10713         SetTrainingModeOff();
10714
10715     oldGameMode = gameMode;
10716     if (gameMode != BeginningOfGame) {
10717       Reset(FALSE, TRUE);
10718     }
10719
10720     gameFileFP = f;
10721     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10722         fclose(lastLoadGameFP);
10723     }
10724
10725     if (useList) {
10726         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10727
10728         if (lg) {
10729             fseek(f, lg->offset, 0);
10730             GameListHighlight(gameNumber);
10731             gn = 1;
10732         }
10733         else {
10734             DisplayError(_("Game number out of range"), 0);
10735             return FALSE;
10736         }
10737     } else {
10738         GameListDestroy();
10739         if (fseek(f, 0, 0) == -1) {
10740             if (f == lastLoadGameFP ?
10741                 gameNumber == lastLoadGameNumber + 1 :
10742                 gameNumber == 1) {
10743                 gn = 1;
10744             } else {
10745                 DisplayError(_("Can't seek on game file"), 0);
10746                 return FALSE;
10747             }
10748         }
10749     }
10750     lastLoadGameFP = f;
10751     lastLoadGameNumber = gameNumber;
10752     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10753     lastLoadGameUseList = useList;
10754
10755     yynewfile(f);
10756
10757     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10758       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10759                 lg->gameInfo.black);
10760             DisplayTitle(buf);
10761     } else if (*title != NULLCHAR) {
10762         if (gameNumber > 1) {
10763           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10764             DisplayTitle(buf);
10765         } else {
10766             DisplayTitle(title);
10767         }
10768     }
10769
10770     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10771         gameMode = PlayFromGameFile;
10772         ModeHighlight();
10773     }
10774
10775     currentMove = forwardMostMove = backwardMostMove = 0;
10776     CopyBoard(boards[0], initialPosition);
10777     StopClocks();
10778
10779     /*
10780      * Skip the first gn-1 games in the file.
10781      * Also skip over anything that precedes an identifiable
10782      * start of game marker, to avoid being confused by
10783      * garbage at the start of the file.  Currently
10784      * recognized start of game markers are the move number "1",
10785      * the pattern "gnuchess .* game", the pattern
10786      * "^[#;%] [^ ]* game file", and a PGN tag block.
10787      * A game that starts with one of the latter two patterns
10788      * will also have a move number 1, possibly
10789      * following a position diagram.
10790      * 5-4-02: Let's try being more lenient and allowing a game to
10791      * start with an unnumbered move.  Does that break anything?
10792      */
10793     cm = lastLoadGameStart = EndOfFile;
10794     while (gn > 0) {
10795         yyboardindex = forwardMostMove;
10796         cm = (ChessMove) Myylex();
10797         switch (cm) {
10798           case EndOfFile:
10799             if (cmailMsgLoaded) {
10800                 nCmailGames = CMAIL_MAX_GAMES - gn;
10801             } else {
10802                 Reset(TRUE, TRUE);
10803                 DisplayError(_("Game not found in file"), 0);
10804             }
10805             return FALSE;
10806
10807           case GNUChessGame:
10808           case XBoardGame:
10809             gn--;
10810             lastLoadGameStart = cm;
10811             break;
10812
10813           case MoveNumberOne:
10814             switch (lastLoadGameStart) {
10815               case GNUChessGame:
10816               case XBoardGame:
10817               case PGNTag:
10818                 break;
10819               case MoveNumberOne:
10820               case EndOfFile:
10821                 gn--;           /* count this game */
10822                 lastLoadGameStart = cm;
10823                 break;
10824               default:
10825                 /* impossible */
10826                 break;
10827             }
10828             break;
10829
10830           case PGNTag:
10831             switch (lastLoadGameStart) {
10832               case GNUChessGame:
10833               case PGNTag:
10834               case MoveNumberOne:
10835               case EndOfFile:
10836                 gn--;           /* count this game */
10837                 lastLoadGameStart = cm;
10838                 break;
10839               case XBoardGame:
10840                 lastLoadGameStart = cm; /* game counted already */
10841                 break;
10842               default:
10843                 /* impossible */
10844                 break;
10845             }
10846             if (gn > 0) {
10847                 do {
10848                     yyboardindex = forwardMostMove;
10849                     cm = (ChessMove) Myylex();
10850                 } while (cm == PGNTag || cm == Comment);
10851             }
10852             break;
10853
10854           case WhiteWins:
10855           case BlackWins:
10856           case GameIsDrawn:
10857             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10858                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10859                     != CMAIL_OLD_RESULT) {
10860                     nCmailResults ++ ;
10861                     cmailResult[  CMAIL_MAX_GAMES
10862                                 - gn - 1] = CMAIL_OLD_RESULT;
10863                 }
10864             }
10865             break;
10866
10867           case NormalMove:
10868             /* Only a NormalMove can be at the start of a game
10869              * without a position diagram. */
10870             if (lastLoadGameStart == EndOfFile ) {
10871               gn--;
10872               lastLoadGameStart = MoveNumberOne;
10873             }
10874             break;
10875
10876           default:
10877             break;
10878         }
10879     }
10880
10881     if (appData.debugMode)
10882       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10883
10884     if (cm == XBoardGame) {
10885         /* Skip any header junk before position diagram and/or move 1 */
10886         for (;;) {
10887             yyboardindex = forwardMostMove;
10888             cm = (ChessMove) Myylex();
10889
10890             if (cm == EndOfFile ||
10891                 cm == GNUChessGame || cm == XBoardGame) {
10892                 /* Empty game; pretend end-of-file and handle later */
10893                 cm = EndOfFile;
10894                 break;
10895             }
10896
10897             if (cm == MoveNumberOne || cm == PositionDiagram ||
10898                 cm == PGNTag || cm == Comment)
10899               break;
10900         }
10901     } else if (cm == GNUChessGame) {
10902         if (gameInfo.event != NULL) {
10903             free(gameInfo.event);
10904         }
10905         gameInfo.event = StrSave(yy_text);
10906     }
10907
10908     startedFromSetupPosition = FALSE;
10909     while (cm == PGNTag) {
10910         if (appData.debugMode)
10911           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10912         err = ParsePGNTag(yy_text, &gameInfo);
10913         if (!err) numPGNTags++;
10914
10915         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10916         if(gameInfo.variant != oldVariant) {
10917             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10918             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10919             InitPosition(TRUE);
10920             oldVariant = gameInfo.variant;
10921             if (appData.debugMode)
10922               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10923         }
10924
10925
10926         if (gameInfo.fen != NULL) {
10927           Board initial_position;
10928           startedFromSetupPosition = TRUE;
10929           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10930             Reset(TRUE, TRUE);
10931             DisplayError(_("Bad FEN position in file"), 0);
10932             return FALSE;
10933           }
10934           CopyBoard(boards[0], initial_position);
10935           if (blackPlaysFirst) {
10936             currentMove = forwardMostMove = backwardMostMove = 1;
10937             CopyBoard(boards[1], initial_position);
10938             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10939             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10940             timeRemaining[0][1] = whiteTimeRemaining;
10941             timeRemaining[1][1] = blackTimeRemaining;
10942             if (commentList[0] != NULL) {
10943               commentList[1] = commentList[0];
10944               commentList[0] = NULL;
10945             }
10946           } else {
10947             currentMove = forwardMostMove = backwardMostMove = 0;
10948           }
10949           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10950           {   int i;
10951               initialRulePlies = FENrulePlies;
10952               for( i=0; i< nrCastlingRights; i++ )
10953                   initialRights[i] = initial_position[CASTLING][i];
10954           }
10955           yyboardindex = forwardMostMove;
10956           free(gameInfo.fen);
10957           gameInfo.fen = NULL;
10958         }
10959
10960         yyboardindex = forwardMostMove;
10961         cm = (ChessMove) Myylex();
10962
10963         /* Handle comments interspersed among the tags */
10964         while (cm == Comment) {
10965             char *p;
10966             if (appData.debugMode)
10967               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10968             p = yy_text;
10969             AppendComment(currentMove, p, FALSE);
10970             yyboardindex = forwardMostMove;
10971             cm = (ChessMove) Myylex();
10972         }
10973     }
10974
10975     /* don't rely on existence of Event tag since if game was
10976      * pasted from clipboard the Event tag may not exist
10977      */
10978     if (numPGNTags > 0){
10979         char *tags;
10980         if (gameInfo.variant == VariantNormal) {
10981           VariantClass v = StringToVariant(gameInfo.event);
10982           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10983           if(v < VariantShogi) gameInfo.variant = v;
10984         }
10985         if (!matchMode) {
10986           if( appData.autoDisplayTags ) {
10987             tags = PGNTags(&gameInfo);
10988             TagsPopUp(tags, CmailMsg());
10989             free(tags);
10990           }
10991         }
10992     } else {
10993         /* Make something up, but don't display it now */
10994         SetGameInfo();
10995         TagsPopDown();
10996     }
10997
10998     if (cm == PositionDiagram) {
10999         int i, j;
11000         char *p;
11001         Board initial_position;
11002
11003         if (appData.debugMode)
11004           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11005
11006         if (!startedFromSetupPosition) {
11007             p = yy_text;
11008             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11009               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11010                 switch (*p) {
11011                   case '{':
11012                   case '[':
11013                   case '-':
11014                   case ' ':
11015                   case '\t':
11016                   case '\n':
11017                   case '\r':
11018                     break;
11019                   default:
11020                     initial_position[i][j++] = CharToPiece(*p);
11021                     break;
11022                 }
11023             while (*p == ' ' || *p == '\t' ||
11024                    *p == '\n' || *p == '\r') p++;
11025
11026             if (strncmp(p, "black", strlen("black"))==0)
11027               blackPlaysFirst = TRUE;
11028             else
11029               blackPlaysFirst = FALSE;
11030             startedFromSetupPosition = TRUE;
11031
11032             CopyBoard(boards[0], initial_position);
11033             if (blackPlaysFirst) {
11034                 currentMove = forwardMostMove = backwardMostMove = 1;
11035                 CopyBoard(boards[1], initial_position);
11036                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11037                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11038                 timeRemaining[0][1] = whiteTimeRemaining;
11039                 timeRemaining[1][1] = blackTimeRemaining;
11040                 if (commentList[0] != NULL) {
11041                     commentList[1] = commentList[0];
11042                     commentList[0] = NULL;
11043                 }
11044             } else {
11045                 currentMove = forwardMostMove = backwardMostMove = 0;
11046             }
11047         }
11048         yyboardindex = forwardMostMove;
11049         cm = (ChessMove) Myylex();
11050     }
11051
11052     if (first.pr == NoProc) {
11053         StartChessProgram(&first);
11054     }
11055     InitChessProgram(&first, FALSE);
11056     SendToProgram("force\n", &first);
11057     if (startedFromSetupPosition) {
11058         SendBoard(&first, forwardMostMove);
11059     if (appData.debugMode) {
11060         fprintf(debugFP, "Load Game\n");
11061     }
11062         DisplayBothClocks();
11063     }
11064
11065     /* [HGM] server: flag to write setup moves in broadcast file as one */
11066     loadFlag = appData.suppressLoadMoves;
11067
11068     while (cm == Comment) {
11069         char *p;
11070         if (appData.debugMode)
11071           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11072         p = yy_text;
11073         AppendComment(currentMove, p, FALSE);
11074         yyboardindex = forwardMostMove;
11075         cm = (ChessMove) Myylex();
11076     }
11077
11078     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11079         cm == WhiteWins || cm == BlackWins ||
11080         cm == GameIsDrawn || cm == GameUnfinished) {
11081         DisplayMessage("", _("No moves in game"));
11082         if (cmailMsgLoaded) {
11083             if (appData.debugMode)
11084               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11085             ClearHighlights();
11086             flipView = FALSE;
11087         }
11088         DrawPosition(FALSE, boards[currentMove]);
11089         DisplayBothClocks();
11090         gameMode = EditGame;
11091         ModeHighlight();
11092         gameFileFP = NULL;
11093         cmailOldMove = 0;
11094         return TRUE;
11095     }
11096
11097     // [HGM] PV info: routine tests if comment empty
11098     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11099         DisplayComment(currentMove - 1, commentList[currentMove]);
11100     }
11101     if (!matchMode && appData.timeDelay != 0)
11102       DrawPosition(FALSE, boards[currentMove]);
11103
11104     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11105       programStats.ok_to_send = 1;
11106     }
11107
11108     /* if the first token after the PGN tags is a move
11109      * and not move number 1, retrieve it from the parser
11110      */
11111     if (cm != MoveNumberOne)
11112         LoadGameOneMove(cm);
11113
11114     /* load the remaining moves from the file */
11115     while (LoadGameOneMove(EndOfFile)) {
11116       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11117       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11118     }
11119
11120     /* rewind to the start of the game */
11121     currentMove = backwardMostMove;
11122
11123     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11124
11125     if (oldGameMode == AnalyzeFile ||
11126         oldGameMode == AnalyzeMode) {
11127       AnalyzeFileEvent();
11128     }
11129
11130     if (matchMode || appData.timeDelay == 0) {
11131       ToEndEvent();
11132       gameMode = EditGame;
11133       ModeHighlight();
11134     } else if (appData.timeDelay > 0) {
11135       AutoPlayGameLoop();
11136     }
11137
11138     if (appData.debugMode)
11139         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11140
11141     loadFlag = 0; /* [HGM] true game starts */
11142     return TRUE;
11143 }
11144
11145 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11146 int
11147 ReloadPosition(offset)
11148      int offset;
11149 {
11150     int positionNumber = lastLoadPositionNumber + offset;
11151     if (lastLoadPositionFP == NULL) {
11152         DisplayError(_("No position has been loaded yet"), 0);
11153         return FALSE;
11154     }
11155     if (positionNumber <= 0) {
11156         DisplayError(_("Can't back up any further"), 0);
11157         return FALSE;
11158     }
11159     return LoadPosition(lastLoadPositionFP, positionNumber,
11160                         lastLoadPositionTitle);
11161 }
11162
11163 /* Load the nth position from the given file */
11164 int
11165 LoadPositionFromFile(filename, n, title)
11166      char *filename;
11167      int n;
11168      char *title;
11169 {
11170     FILE *f;
11171     char buf[MSG_SIZ];
11172
11173     if (strcmp(filename, "-") == 0) {
11174         return LoadPosition(stdin, n, "stdin");
11175     } else {
11176         f = fopen(filename, "rb");
11177         if (f == NULL) {
11178             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11179             DisplayError(buf, errno);
11180             return FALSE;
11181         } else {
11182             return LoadPosition(f, n, title);
11183         }
11184     }
11185 }
11186
11187 /* Load the nth position from the given open file, and close it */
11188 int
11189 LoadPosition(f, positionNumber, title)
11190      FILE *f;
11191      int positionNumber;
11192      char *title;
11193 {
11194     char *p, line[MSG_SIZ];
11195     Board initial_position;
11196     int i, j, fenMode, pn;
11197
11198     if (gameMode == Training )
11199         SetTrainingModeOff();
11200
11201     if (gameMode != BeginningOfGame) {
11202         Reset(FALSE, TRUE);
11203     }
11204     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11205         fclose(lastLoadPositionFP);
11206     }
11207     if (positionNumber == 0) positionNumber = 1;
11208     lastLoadPositionFP = f;
11209     lastLoadPositionNumber = positionNumber;
11210     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11211     if (first.pr == NoProc) {
11212       StartChessProgram(&first);
11213       InitChessProgram(&first, FALSE);
11214     }
11215     pn = positionNumber;
11216     if (positionNumber < 0) {
11217         /* Negative position number means to seek to that byte offset */
11218         if (fseek(f, -positionNumber, 0) == -1) {
11219             DisplayError(_("Can't seek on position file"), 0);
11220             return FALSE;
11221         };
11222         pn = 1;
11223     } else {
11224         if (fseek(f, 0, 0) == -1) {
11225             if (f == lastLoadPositionFP ?
11226                 positionNumber == lastLoadPositionNumber + 1 :
11227                 positionNumber == 1) {
11228                 pn = 1;
11229             } else {
11230                 DisplayError(_("Can't seek on position file"), 0);
11231                 return FALSE;
11232             }
11233         }
11234     }
11235     /* See if this file is FEN or old-style xboard */
11236     if (fgets(line, MSG_SIZ, f) == NULL) {
11237         DisplayError(_("Position not found in file"), 0);
11238         return FALSE;
11239     }
11240     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11241     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11242
11243     if (pn >= 2) {
11244         if (fenMode || line[0] == '#') pn--;
11245         while (pn > 0) {
11246             /* skip positions before number pn */
11247             if (fgets(line, MSG_SIZ, f) == NULL) {
11248                 Reset(TRUE, TRUE);
11249                 DisplayError(_("Position not found in file"), 0);
11250                 return FALSE;
11251             }
11252             if (fenMode || line[0] == '#') pn--;
11253         }
11254     }
11255
11256     if (fenMode) {
11257         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11258             DisplayError(_("Bad FEN position in file"), 0);
11259             return FALSE;
11260         }
11261     } else {
11262         (void) fgets(line, MSG_SIZ, f);
11263         (void) fgets(line, MSG_SIZ, f);
11264
11265         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11266             (void) fgets(line, MSG_SIZ, f);
11267             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11268                 if (*p == ' ')
11269                   continue;
11270                 initial_position[i][j++] = CharToPiece(*p);
11271             }
11272         }
11273
11274         blackPlaysFirst = FALSE;
11275         if (!feof(f)) {
11276             (void) fgets(line, MSG_SIZ, f);
11277             if (strncmp(line, "black", strlen("black"))==0)
11278               blackPlaysFirst = TRUE;
11279         }
11280     }
11281     startedFromSetupPosition = TRUE;
11282
11283     SendToProgram("force\n", &first);
11284     CopyBoard(boards[0], initial_position);
11285     if (blackPlaysFirst) {
11286         currentMove = forwardMostMove = backwardMostMove = 1;
11287         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11288         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11289         CopyBoard(boards[1], initial_position);
11290         DisplayMessage("", _("Black to play"));
11291     } else {
11292         currentMove = forwardMostMove = backwardMostMove = 0;
11293         DisplayMessage("", _("White to play"));
11294     }
11295     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11296     SendBoard(&first, forwardMostMove);
11297     if (appData.debugMode) {
11298 int i, j;
11299   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11300   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11301         fprintf(debugFP, "Load Position\n");
11302     }
11303
11304     if (positionNumber > 1) {
11305       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11306         DisplayTitle(line);
11307     } else {
11308         DisplayTitle(title);
11309     }
11310     gameMode = EditGame;
11311     ModeHighlight();
11312     ResetClocks();
11313     timeRemaining[0][1] = whiteTimeRemaining;
11314     timeRemaining[1][1] = blackTimeRemaining;
11315     DrawPosition(FALSE, boards[currentMove]);
11316
11317     return TRUE;
11318 }
11319
11320
11321 void
11322 CopyPlayerNameIntoFileName(dest, src)
11323      char **dest, *src;
11324 {
11325     while (*src != NULLCHAR && *src != ',') {
11326         if (*src == ' ') {
11327             *(*dest)++ = '_';
11328             src++;
11329         } else {
11330             *(*dest)++ = *src++;
11331         }
11332     }
11333 }
11334
11335 char *DefaultFileName(ext)
11336      char *ext;
11337 {
11338     static char def[MSG_SIZ];
11339     char *p;
11340
11341     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11342         p = def;
11343         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11344         *p++ = '-';
11345         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11346         *p++ = '.';
11347         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11348     } else {
11349         def[0] = NULLCHAR;
11350     }
11351     return def;
11352 }
11353
11354 /* Save the current game to the given file */
11355 int
11356 SaveGameToFile(filename, append)
11357      char *filename;
11358      int append;
11359 {
11360     FILE *f;
11361     char buf[MSG_SIZ];
11362     int result;
11363
11364     if (strcmp(filename, "-") == 0) {
11365         return SaveGame(stdout, 0, NULL);
11366     } else {
11367         f = fopen(filename, append ? "a" : "w");
11368         if (f == NULL) {
11369             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11370             DisplayError(buf, errno);
11371             return FALSE;
11372         } else {
11373             safeStrCpy(buf, lastMsg, MSG_SIZ);
11374             DisplayMessage(_("Waiting for access to save file"), "");
11375             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11376             DisplayMessage(_("Saving game"), "");
11377             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11378             result = SaveGame(f, 0, NULL);
11379             DisplayMessage(buf, "");
11380             return result;
11381         }
11382     }
11383 }
11384
11385 char *
11386 SavePart(str)
11387      char *str;
11388 {
11389     static char buf[MSG_SIZ];
11390     char *p;
11391
11392     p = strchr(str, ' ');
11393     if (p == NULL) return str;
11394     strncpy(buf, str, p - str);
11395     buf[p - str] = NULLCHAR;
11396     return buf;
11397 }
11398
11399 #define PGN_MAX_LINE 75
11400
11401 #define PGN_SIDE_WHITE  0
11402 #define PGN_SIDE_BLACK  1
11403
11404 /* [AS] */
11405 static int FindFirstMoveOutOfBook( int side )
11406 {
11407     int result = -1;
11408
11409     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11410         int index = backwardMostMove;
11411         int has_book_hit = 0;
11412
11413         if( (index % 2) != side ) {
11414             index++;
11415         }
11416
11417         while( index < forwardMostMove ) {
11418             /* Check to see if engine is in book */
11419             int depth = pvInfoList[index].depth;
11420             int score = pvInfoList[index].score;
11421             int in_book = 0;
11422
11423             if( depth <= 2 ) {
11424                 in_book = 1;
11425             }
11426             else if( score == 0 && depth == 63 ) {
11427                 in_book = 1; /* Zappa */
11428             }
11429             else if( score == 2 && depth == 99 ) {
11430                 in_book = 1; /* Abrok */
11431             }
11432
11433             has_book_hit += in_book;
11434
11435             if( ! in_book ) {
11436                 result = index;
11437
11438                 break;
11439             }
11440
11441             index += 2;
11442         }
11443     }
11444
11445     return result;
11446 }
11447
11448 /* [AS] */
11449 void GetOutOfBookInfo( char * buf )
11450 {
11451     int oob[2];
11452     int i;
11453     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11454
11455     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11456     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11457
11458     *buf = '\0';
11459
11460     if( oob[0] >= 0 || oob[1] >= 0 ) {
11461         for( i=0; i<2; i++ ) {
11462             int idx = oob[i];
11463
11464             if( idx >= 0 ) {
11465                 if( i > 0 && oob[0] >= 0 ) {
11466                     strcat( buf, "   " );
11467                 }
11468
11469                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11470                 sprintf( buf+strlen(buf), "%s%.2f",
11471                     pvInfoList[idx].score >= 0 ? "+" : "",
11472                     pvInfoList[idx].score / 100.0 );
11473             }
11474         }
11475     }
11476 }
11477
11478 /* Save game in PGN style and close the file */
11479 int
11480 SaveGamePGN(f)
11481      FILE *f;
11482 {
11483     int i, offset, linelen, newblock;
11484     time_t tm;
11485 //    char *movetext;
11486     char numtext[32];
11487     int movelen, numlen, blank;
11488     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11489
11490     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11491
11492     tm = time((time_t *) NULL);
11493
11494     PrintPGNTags(f, &gameInfo);
11495
11496     if (backwardMostMove > 0 || startedFromSetupPosition) {
11497         char *fen = PositionToFEN(backwardMostMove, NULL);
11498         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11499         fprintf(f, "\n{--------------\n");
11500         PrintPosition(f, backwardMostMove);
11501         fprintf(f, "--------------}\n");
11502         free(fen);
11503     }
11504     else {
11505         /* [AS] Out of book annotation */
11506         if( appData.saveOutOfBookInfo ) {
11507             char buf[64];
11508
11509             GetOutOfBookInfo( buf );
11510
11511             if( buf[0] != '\0' ) {
11512                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11513             }
11514         }
11515
11516         fprintf(f, "\n");
11517     }
11518
11519     i = backwardMostMove;
11520     linelen = 0;
11521     newblock = TRUE;
11522
11523     while (i < forwardMostMove) {
11524         /* Print comments preceding this move */
11525         if (commentList[i] != NULL) {
11526             if (linelen > 0) fprintf(f, "\n");
11527             fprintf(f, "%s", commentList[i]);
11528             linelen = 0;
11529             newblock = TRUE;
11530         }
11531
11532         /* Format move number */
11533         if ((i % 2) == 0)
11534           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11535         else
11536           if (newblock)
11537             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11538           else
11539             numtext[0] = NULLCHAR;
11540
11541         numlen = strlen(numtext);
11542         newblock = FALSE;
11543
11544         /* Print move number */
11545         blank = linelen > 0 && numlen > 0;
11546         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11547             fprintf(f, "\n");
11548             linelen = 0;
11549             blank = 0;
11550         }
11551         if (blank) {
11552             fprintf(f, " ");
11553             linelen++;
11554         }
11555         fprintf(f, "%s", numtext);
11556         linelen += numlen;
11557
11558         /* Get move */
11559         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11560         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11561
11562         /* Print move */
11563         blank = linelen > 0 && movelen > 0;
11564         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11565             fprintf(f, "\n");
11566             linelen = 0;
11567             blank = 0;
11568         }
11569         if (blank) {
11570             fprintf(f, " ");
11571             linelen++;
11572         }
11573         fprintf(f, "%s", move_buffer);
11574         linelen += movelen;
11575
11576         /* [AS] Add PV info if present */
11577         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11578             /* [HGM] add time */
11579             char buf[MSG_SIZ]; int seconds;
11580
11581             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11582
11583             if( seconds <= 0)
11584               buf[0] = 0;
11585             else
11586               if( seconds < 30 )
11587                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11588               else
11589                 {
11590                   seconds = (seconds + 4)/10; // round to full seconds
11591                   if( seconds < 60 )
11592                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11593                   else
11594                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11595                 }
11596
11597             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11598                       pvInfoList[i].score >= 0 ? "+" : "",
11599                       pvInfoList[i].score / 100.0,
11600                       pvInfoList[i].depth,
11601                       buf );
11602
11603             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11604
11605             /* Print score/depth */
11606             blank = linelen > 0 && movelen > 0;
11607             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11608                 fprintf(f, "\n");
11609                 linelen = 0;
11610                 blank = 0;
11611             }
11612             if (blank) {
11613                 fprintf(f, " ");
11614                 linelen++;
11615             }
11616             fprintf(f, "%s", move_buffer);
11617             linelen += movelen;
11618         }
11619
11620         i++;
11621     }
11622
11623     /* Start a new line */
11624     if (linelen > 0) fprintf(f, "\n");
11625
11626     /* Print comments after last move */
11627     if (commentList[i] != NULL) {
11628         fprintf(f, "%s\n", commentList[i]);
11629     }
11630
11631     /* Print result */
11632     if (gameInfo.resultDetails != NULL &&
11633         gameInfo.resultDetails[0] != NULLCHAR) {
11634         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11635                 PGNResult(gameInfo.result));
11636     } else {
11637         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11638     }
11639
11640     fclose(f);
11641     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11642     return TRUE;
11643 }
11644
11645 /* Save game in old style and close the file */
11646 int
11647 SaveGameOldStyle(f)
11648      FILE *f;
11649 {
11650     int i, offset;
11651     time_t tm;
11652
11653     tm = time((time_t *) NULL);
11654
11655     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11656     PrintOpponents(f);
11657
11658     if (backwardMostMove > 0 || startedFromSetupPosition) {
11659         fprintf(f, "\n[--------------\n");
11660         PrintPosition(f, backwardMostMove);
11661         fprintf(f, "--------------]\n");
11662     } else {
11663         fprintf(f, "\n");
11664     }
11665
11666     i = backwardMostMove;
11667     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11668
11669     while (i < forwardMostMove) {
11670         if (commentList[i] != NULL) {
11671             fprintf(f, "[%s]\n", commentList[i]);
11672         }
11673
11674         if ((i % 2) == 1) {
11675             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11676             i++;
11677         } else {
11678             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11679             i++;
11680             if (commentList[i] != NULL) {
11681                 fprintf(f, "\n");
11682                 continue;
11683             }
11684             if (i >= forwardMostMove) {
11685                 fprintf(f, "\n");
11686                 break;
11687             }
11688             fprintf(f, "%s\n", parseList[i]);
11689             i++;
11690         }
11691     }
11692
11693     if (commentList[i] != NULL) {
11694         fprintf(f, "[%s]\n", commentList[i]);
11695     }
11696
11697     /* This isn't really the old style, but it's close enough */
11698     if (gameInfo.resultDetails != NULL &&
11699         gameInfo.resultDetails[0] != NULLCHAR) {
11700         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11701                 gameInfo.resultDetails);
11702     } else {
11703         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11704     }
11705
11706     fclose(f);
11707     return TRUE;
11708 }
11709
11710 /* Save the current game to open file f and close the file */
11711 int
11712 SaveGame(f, dummy, dummy2)
11713      FILE *f;
11714      int dummy;
11715      char *dummy2;
11716 {
11717     if (gameMode == EditPosition) EditPositionDone(TRUE);
11718     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11719     if (appData.oldSaveStyle)
11720       return SaveGameOldStyle(f);
11721     else
11722       return SaveGamePGN(f);
11723 }
11724
11725 /* Save the current position to the given file */
11726 int
11727 SavePositionToFile(filename)
11728      char *filename;
11729 {
11730     FILE *f;
11731     char buf[MSG_SIZ];
11732
11733     if (strcmp(filename, "-") == 0) {
11734         return SavePosition(stdout, 0, NULL);
11735     } else {
11736         f = fopen(filename, "a");
11737         if (f == NULL) {
11738             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11739             DisplayError(buf, errno);
11740             return FALSE;
11741         } else {
11742             safeStrCpy(buf, lastMsg, MSG_SIZ);
11743             DisplayMessage(_("Waiting for access to save file"), "");
11744             flock(fileno(f), LOCK_EX); // [HGM] lock
11745             DisplayMessage(_("Saving position"), "");
11746             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11747             SavePosition(f, 0, NULL);
11748             DisplayMessage(buf, "");
11749             return TRUE;
11750         }
11751     }
11752 }
11753
11754 /* Save the current position to the given open file and close the file */
11755 int
11756 SavePosition(f, dummy, dummy2)
11757      FILE *f;
11758      int dummy;
11759      char *dummy2;
11760 {
11761     time_t tm;
11762     char *fen;
11763
11764     if (gameMode == EditPosition) EditPositionDone(TRUE);
11765     if (appData.oldSaveStyle) {
11766         tm = time((time_t *) NULL);
11767
11768         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11769         PrintOpponents(f);
11770         fprintf(f, "[--------------\n");
11771         PrintPosition(f, currentMove);
11772         fprintf(f, "--------------]\n");
11773     } else {
11774         fen = PositionToFEN(currentMove, NULL);
11775         fprintf(f, "%s\n", fen);
11776         free(fen);
11777     }
11778     fclose(f);
11779     return TRUE;
11780 }
11781
11782 void
11783 ReloadCmailMsgEvent(unregister)
11784      int unregister;
11785 {
11786 #if !WIN32
11787     static char *inFilename = NULL;
11788     static char *outFilename;
11789     int i;
11790     struct stat inbuf, outbuf;
11791     int status;
11792
11793     /* Any registered moves are unregistered if unregister is set, */
11794     /* i.e. invoked by the signal handler */
11795     if (unregister) {
11796         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11797             cmailMoveRegistered[i] = FALSE;
11798             if (cmailCommentList[i] != NULL) {
11799                 free(cmailCommentList[i]);
11800                 cmailCommentList[i] = NULL;
11801             }
11802         }
11803         nCmailMovesRegistered = 0;
11804     }
11805
11806     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11807         cmailResult[i] = CMAIL_NOT_RESULT;
11808     }
11809     nCmailResults = 0;
11810
11811     if (inFilename == NULL) {
11812         /* Because the filenames are static they only get malloced once  */
11813         /* and they never get freed                                      */
11814         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11815         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11816
11817         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11818         sprintf(outFilename, "%s.out", appData.cmailGameName);
11819     }
11820
11821     status = stat(outFilename, &outbuf);
11822     if (status < 0) {
11823         cmailMailedMove = FALSE;
11824     } else {
11825         status = stat(inFilename, &inbuf);
11826         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11827     }
11828
11829     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11830        counts the games, notes how each one terminated, etc.
11831
11832        It would be nice to remove this kludge and instead gather all
11833        the information while building the game list.  (And to keep it
11834        in the game list nodes instead of having a bunch of fixed-size
11835        parallel arrays.)  Note this will require getting each game's
11836        termination from the PGN tags, as the game list builder does
11837        not process the game moves.  --mann
11838        */
11839     cmailMsgLoaded = TRUE;
11840     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11841
11842     /* Load first game in the file or popup game menu */
11843     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11844
11845 #endif /* !WIN32 */
11846     return;
11847 }
11848
11849 int
11850 RegisterMove()
11851 {
11852     FILE *f;
11853     char string[MSG_SIZ];
11854
11855     if (   cmailMailedMove
11856         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11857         return TRUE;            /* Allow free viewing  */
11858     }
11859
11860     /* Unregister move to ensure that we don't leave RegisterMove        */
11861     /* with the move registered when the conditions for registering no   */
11862     /* longer hold                                                       */
11863     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11864         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11865         nCmailMovesRegistered --;
11866
11867         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11868           {
11869               free(cmailCommentList[lastLoadGameNumber - 1]);
11870               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11871           }
11872     }
11873
11874     if (cmailOldMove == -1) {
11875         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11876         return FALSE;
11877     }
11878
11879     if (currentMove > cmailOldMove + 1) {
11880         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11881         return FALSE;
11882     }
11883
11884     if (currentMove < cmailOldMove) {
11885         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11886         return FALSE;
11887     }
11888
11889     if (forwardMostMove > currentMove) {
11890         /* Silently truncate extra moves */
11891         TruncateGame();
11892     }
11893
11894     if (   (currentMove == cmailOldMove + 1)
11895         || (   (currentMove == cmailOldMove)
11896             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11897                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11898         if (gameInfo.result != GameUnfinished) {
11899             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11900         }
11901
11902         if (commentList[currentMove] != NULL) {
11903             cmailCommentList[lastLoadGameNumber - 1]
11904               = StrSave(commentList[currentMove]);
11905         }
11906         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11907
11908         if (appData.debugMode)
11909           fprintf(debugFP, "Saving %s for game %d\n",
11910                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11911
11912         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11913
11914         f = fopen(string, "w");
11915         if (appData.oldSaveStyle) {
11916             SaveGameOldStyle(f); /* also closes the file */
11917
11918             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11919             f = fopen(string, "w");
11920             SavePosition(f, 0, NULL); /* also closes the file */
11921         } else {
11922             fprintf(f, "{--------------\n");
11923             PrintPosition(f, currentMove);
11924             fprintf(f, "--------------}\n\n");
11925
11926             SaveGame(f, 0, NULL); /* also closes the file*/
11927         }
11928
11929         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11930         nCmailMovesRegistered ++;
11931     } else if (nCmailGames == 1) {
11932         DisplayError(_("You have not made a move yet"), 0);
11933         return FALSE;
11934     }
11935
11936     return TRUE;
11937 }
11938
11939 void
11940 MailMoveEvent()
11941 {
11942 #if !WIN32
11943     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11944     FILE *commandOutput;
11945     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11946     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11947     int nBuffers;
11948     int i;
11949     int archived;
11950     char *arcDir;
11951
11952     if (! cmailMsgLoaded) {
11953         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11954         return;
11955     }
11956
11957     if (nCmailGames == nCmailResults) {
11958         DisplayError(_("No unfinished games"), 0);
11959         return;
11960     }
11961
11962 #if CMAIL_PROHIBIT_REMAIL
11963     if (cmailMailedMove) {
11964       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);
11965         DisplayError(msg, 0);
11966         return;
11967     }
11968 #endif
11969
11970     if (! (cmailMailedMove || RegisterMove())) return;
11971
11972     if (   cmailMailedMove
11973         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11974       snprintf(string, MSG_SIZ, partCommandString,
11975                appData.debugMode ? " -v" : "", appData.cmailGameName);
11976         commandOutput = popen(string, "r");
11977
11978         if (commandOutput == NULL) {
11979             DisplayError(_("Failed to invoke cmail"), 0);
11980         } else {
11981             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11982                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11983             }
11984             if (nBuffers > 1) {
11985                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11986                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11987                 nBytes = MSG_SIZ - 1;
11988             } else {
11989                 (void) memcpy(msg, buffer, nBytes);
11990             }
11991             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11992
11993             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11994                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11995
11996                 archived = TRUE;
11997                 for (i = 0; i < nCmailGames; i ++) {
11998                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11999                         archived = FALSE;
12000                     }
12001                 }
12002                 if (   archived
12003                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12004                         != NULL)) {
12005                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12006                            arcDir,
12007                            appData.cmailGameName,
12008                            gameInfo.date);
12009                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12010                     cmailMsgLoaded = FALSE;
12011                 }
12012             }
12013
12014             DisplayInformation(msg);
12015             pclose(commandOutput);
12016         }
12017     } else {
12018         if ((*cmailMsg) != '\0') {
12019             DisplayInformation(cmailMsg);
12020         }
12021     }
12022
12023     return;
12024 #endif /* !WIN32 */
12025 }
12026
12027 char *
12028 CmailMsg()
12029 {
12030 #if WIN32
12031     return NULL;
12032 #else
12033     int  prependComma = 0;
12034     char number[5];
12035     char string[MSG_SIZ];       /* Space for game-list */
12036     int  i;
12037
12038     if (!cmailMsgLoaded) return "";
12039
12040     if (cmailMailedMove) {
12041       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12042     } else {
12043         /* Create a list of games left */
12044       snprintf(string, MSG_SIZ, "[");
12045         for (i = 0; i < nCmailGames; i ++) {
12046             if (! (   cmailMoveRegistered[i]
12047                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12048                 if (prependComma) {
12049                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12050                 } else {
12051                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12052                     prependComma = 1;
12053                 }
12054
12055                 strcat(string, number);
12056             }
12057         }
12058         strcat(string, "]");
12059
12060         if (nCmailMovesRegistered + nCmailResults == 0) {
12061             switch (nCmailGames) {
12062               case 1:
12063                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12064                 break;
12065
12066               case 2:
12067                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12068                 break;
12069
12070               default:
12071                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12072                          nCmailGames);
12073                 break;
12074             }
12075         } else {
12076             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12077               case 1:
12078                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12079                          string);
12080                 break;
12081
12082               case 0:
12083                 if (nCmailResults == nCmailGames) {
12084                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12085                 } else {
12086                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12087                 }
12088                 break;
12089
12090               default:
12091                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12092                          string);
12093             }
12094         }
12095     }
12096     return cmailMsg;
12097 #endif /* WIN32 */
12098 }
12099
12100 void
12101 ResetGameEvent()
12102 {
12103     if (gameMode == Training)
12104       SetTrainingModeOff();
12105
12106     Reset(TRUE, TRUE);
12107     cmailMsgLoaded = FALSE;
12108     if (appData.icsActive) {
12109       SendToICS(ics_prefix);
12110       SendToICS("refresh\n");
12111     }
12112 }
12113
12114 void
12115 ExitEvent(status)
12116      int status;
12117 {
12118     exiting++;
12119     if (exiting > 2) {
12120       /* Give up on clean exit */
12121       exit(status);
12122     }
12123     if (exiting > 1) {
12124       /* Keep trying for clean exit */
12125       return;
12126     }
12127
12128     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12129
12130     if (telnetISR != NULL) {
12131       RemoveInputSource(telnetISR);
12132     }
12133     if (icsPR != NoProc) {
12134       DestroyChildProcess(icsPR, TRUE);
12135     }
12136
12137     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12138     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12139
12140     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12141     /* make sure this other one finishes before killing it!                  */
12142     if(endingGame) { int count = 0;
12143         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12144         while(endingGame && count++ < 10) DoSleep(1);
12145         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12146     }
12147
12148     /* Kill off chess programs */
12149     if (first.pr != NoProc) {
12150         ExitAnalyzeMode();
12151
12152         DoSleep( appData.delayBeforeQuit );
12153         SendToProgram("quit\n", &first);
12154         DoSleep( appData.delayAfterQuit );
12155         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12156     }
12157     if (second.pr != NoProc) {
12158         DoSleep( appData.delayBeforeQuit );
12159         SendToProgram("quit\n", &second);
12160         DoSleep( appData.delayAfterQuit );
12161         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12162     }
12163     if (first.isr != NULL) {
12164         RemoveInputSource(first.isr);
12165     }
12166     if (second.isr != NULL) {
12167         RemoveInputSource(second.isr);
12168     }
12169
12170     ShutDownFrontEnd();
12171     exit(status);
12172 }
12173
12174 void
12175 PauseEvent()
12176 {
12177     if (appData.debugMode)
12178         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12179     if (pausing) {
12180         pausing = FALSE;
12181         ModeHighlight();
12182         if (gameMode == MachinePlaysWhite ||
12183             gameMode == MachinePlaysBlack) {
12184             StartClocks();
12185         } else {
12186             DisplayBothClocks();
12187         }
12188         if (gameMode == PlayFromGameFile) {
12189             if (appData.timeDelay >= 0)
12190                 AutoPlayGameLoop();
12191         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12192             Reset(FALSE, TRUE);
12193             SendToICS(ics_prefix);
12194             SendToICS("refresh\n");
12195         } else if (currentMove < forwardMostMove) {
12196             ForwardInner(forwardMostMove);
12197         }
12198         pauseExamInvalid = FALSE;
12199     } else {
12200         switch (gameMode) {
12201           default:
12202             return;
12203           case IcsExamining:
12204             pauseExamForwardMostMove = forwardMostMove;
12205             pauseExamInvalid = FALSE;
12206             /* fall through */
12207           case IcsObserving:
12208           case IcsPlayingWhite:
12209           case IcsPlayingBlack:
12210             pausing = TRUE;
12211             ModeHighlight();
12212             return;
12213           case PlayFromGameFile:
12214             (void) StopLoadGameTimer();
12215             pausing = TRUE;
12216             ModeHighlight();
12217             break;
12218           case BeginningOfGame:
12219             if (appData.icsActive) return;
12220             /* else fall through */
12221           case MachinePlaysWhite:
12222           case MachinePlaysBlack:
12223           case TwoMachinesPlay:
12224             if (forwardMostMove == 0)
12225               return;           /* don't pause if no one has moved */
12226             if ((gameMode == MachinePlaysWhite &&
12227                  !WhiteOnMove(forwardMostMove)) ||
12228                 (gameMode == MachinePlaysBlack &&
12229                  WhiteOnMove(forwardMostMove))) {
12230                 StopClocks();
12231             }
12232             pausing = TRUE;
12233             ModeHighlight();
12234             break;
12235         }
12236     }
12237 }
12238
12239 void
12240 EditCommentEvent()
12241 {
12242     char title[MSG_SIZ];
12243
12244     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12245       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12246     } else {
12247       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12248                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12249                parseList[currentMove - 1]);
12250     }
12251
12252     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12253 }
12254
12255
12256 void
12257 EditTagsEvent()
12258 {
12259     char *tags = PGNTags(&gameInfo);
12260     EditTagsPopUp(tags, NULL);
12261     free(tags);
12262 }
12263
12264 void
12265 AnalyzeModeEvent()
12266 {
12267     if (appData.noChessProgram || gameMode == AnalyzeMode)
12268       return;
12269
12270     if (gameMode != AnalyzeFile) {
12271         if (!appData.icsEngineAnalyze) {
12272                EditGameEvent();
12273                if (gameMode != EditGame) return;
12274         }
12275         ResurrectChessProgram();
12276         SendToProgram("analyze\n", &first);
12277         first.analyzing = TRUE;
12278         /*first.maybeThinking = TRUE;*/
12279         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12280         EngineOutputPopUp();
12281     }
12282     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12283     pausing = FALSE;
12284     ModeHighlight();
12285     SetGameInfo();
12286
12287     StartAnalysisClock();
12288     GetTimeMark(&lastNodeCountTime);
12289     lastNodeCount = 0;
12290 }
12291
12292 void
12293 AnalyzeFileEvent()
12294 {
12295     if (appData.noChessProgram || gameMode == AnalyzeFile)
12296       return;
12297
12298     if (gameMode != AnalyzeMode) {
12299         EditGameEvent();
12300         if (gameMode != EditGame) return;
12301         ResurrectChessProgram();
12302         SendToProgram("analyze\n", &first);
12303         first.analyzing = TRUE;
12304         /*first.maybeThinking = TRUE;*/
12305         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12306         EngineOutputPopUp();
12307     }
12308     gameMode = AnalyzeFile;
12309     pausing = FALSE;
12310     ModeHighlight();
12311     SetGameInfo();
12312
12313     StartAnalysisClock();
12314     GetTimeMark(&lastNodeCountTime);
12315     lastNodeCount = 0;
12316 }
12317
12318 void
12319 MachineWhiteEvent()
12320 {
12321     char buf[MSG_SIZ];
12322     char *bookHit = NULL;
12323
12324     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12325       return;
12326
12327
12328     if (gameMode == PlayFromGameFile ||
12329         gameMode == TwoMachinesPlay  ||
12330         gameMode == Training         ||
12331         gameMode == AnalyzeMode      ||
12332         gameMode == EndOfGame)
12333         EditGameEvent();
12334
12335     if (gameMode == EditPosition)
12336         EditPositionDone(TRUE);
12337
12338     if (!WhiteOnMove(currentMove)) {
12339         DisplayError(_("It is not White's turn"), 0);
12340         return;
12341     }
12342
12343     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12344       ExitAnalyzeMode();
12345
12346     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12347         gameMode == AnalyzeFile)
12348         TruncateGame();
12349
12350     ResurrectChessProgram();    /* in case it isn't running */
12351     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12352         gameMode = MachinePlaysWhite;
12353         ResetClocks();
12354     } else
12355     gameMode = MachinePlaysWhite;
12356     pausing = FALSE;
12357     ModeHighlight();
12358     SetGameInfo();
12359     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12360     DisplayTitle(buf);
12361     if (first.sendName) {
12362       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12363       SendToProgram(buf, &first);
12364     }
12365     if (first.sendTime) {
12366       if (first.useColors) {
12367         SendToProgram("black\n", &first); /*gnu kludge*/
12368       }
12369       SendTimeRemaining(&first, TRUE);
12370     }
12371     if (first.useColors) {
12372       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12373     }
12374     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12375     SetMachineThinkingEnables();
12376     first.maybeThinking = TRUE;
12377     StartClocks();
12378     firstMove = FALSE;
12379
12380     if (appData.autoFlipView && !flipView) {
12381       flipView = !flipView;
12382       DrawPosition(FALSE, NULL);
12383       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12384     }
12385
12386     if(bookHit) { // [HGM] book: simulate book reply
12387         static char bookMove[MSG_SIZ]; // a bit generous?
12388
12389         programStats.nodes = programStats.depth = programStats.time =
12390         programStats.score = programStats.got_only_move = 0;
12391         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12392
12393         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12394         strcat(bookMove, bookHit);
12395         HandleMachineMove(bookMove, &first);
12396     }
12397 }
12398
12399 void
12400 MachineBlackEvent()
12401 {
12402   char buf[MSG_SIZ];
12403   char *bookHit = NULL;
12404
12405     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12406         return;
12407
12408
12409     if (gameMode == PlayFromGameFile ||
12410         gameMode == TwoMachinesPlay  ||
12411         gameMode == Training         ||
12412         gameMode == AnalyzeMode      ||
12413         gameMode == EndOfGame)
12414         EditGameEvent();
12415
12416     if (gameMode == EditPosition)
12417         EditPositionDone(TRUE);
12418
12419     if (WhiteOnMove(currentMove)) {
12420         DisplayError(_("It is not Black's turn"), 0);
12421         return;
12422     }
12423
12424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12425       ExitAnalyzeMode();
12426
12427     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12428         gameMode == AnalyzeFile)
12429         TruncateGame();
12430
12431     ResurrectChessProgram();    /* in case it isn't running */
12432     gameMode = MachinePlaysBlack;
12433     pausing = FALSE;
12434     ModeHighlight();
12435     SetGameInfo();
12436     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12437     DisplayTitle(buf);
12438     if (first.sendName) {
12439       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12440       SendToProgram(buf, &first);
12441     }
12442     if (first.sendTime) {
12443       if (first.useColors) {
12444         SendToProgram("white\n", &first); /*gnu kludge*/
12445       }
12446       SendTimeRemaining(&first, FALSE);
12447     }
12448     if (first.useColors) {
12449       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12450     }
12451     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12452     SetMachineThinkingEnables();
12453     first.maybeThinking = TRUE;
12454     StartClocks();
12455
12456     if (appData.autoFlipView && flipView) {
12457       flipView = !flipView;
12458       DrawPosition(FALSE, NULL);
12459       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12460     }
12461     if(bookHit) { // [HGM] book: simulate book reply
12462         static char bookMove[MSG_SIZ]; // a bit generous?
12463
12464         programStats.nodes = programStats.depth = programStats.time =
12465         programStats.score = programStats.got_only_move = 0;
12466         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12467
12468         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12469         strcat(bookMove, bookHit);
12470         HandleMachineMove(bookMove, &first);
12471     }
12472 }
12473
12474
12475 void
12476 DisplayTwoMachinesTitle()
12477 {
12478     char buf[MSG_SIZ];
12479     if (appData.matchGames > 0) {
12480         if (first.twoMachinesColor[0] == 'w') {
12481           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12482                    gameInfo.white, gameInfo.black,
12483                    first.matchWins, second.matchWins,
12484                    matchGame - 1 - (first.matchWins + second.matchWins));
12485         } else {
12486           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12487                    gameInfo.white, gameInfo.black,
12488                    second.matchWins, first.matchWins,
12489                    matchGame - 1 - (first.matchWins + second.matchWins));
12490         }
12491     } else {
12492       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12493     }
12494     DisplayTitle(buf);
12495 }
12496
12497 void
12498 SettingsMenuIfReady()
12499 {
12500   if (second.lastPing != second.lastPong) {
12501     DisplayMessage("", _("Waiting for second chess program"));
12502     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12503     return;
12504   }
12505   ThawUI();
12506   DisplayMessage("", "");
12507   SettingsPopUp(&second);
12508 }
12509
12510 int
12511 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12512 {
12513     char buf[MSG_SIZ];
12514     if (cps->pr == NULL) {
12515         StartChessProgram(cps);
12516         if (cps->protocolVersion == 1) {
12517           retry();
12518         } else {
12519           /* kludge: allow timeout for initial "feature" command */
12520           FreezeUI();
12521           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12522           DisplayMessage("", buf);
12523           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12524         }
12525         return 1;
12526     }
12527     return 0;
12528 }
12529
12530 void
12531 TwoMachinesEvent P((void))
12532 {
12533     int i;
12534     char buf[MSG_SIZ];
12535     ChessProgramState *onmove;
12536     char *bookHit = NULL;
12537     static int stalling = 0;
12538     TimeMark now;
12539     long wait;
12540
12541     if (appData.noChessProgram) return;
12542
12543     switch (gameMode) {
12544       case TwoMachinesPlay:
12545         return;
12546       case MachinePlaysWhite:
12547       case MachinePlaysBlack:
12548         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12549             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12550             return;
12551         }
12552         /* fall through */
12553       case BeginningOfGame:
12554       case PlayFromGameFile:
12555       case EndOfGame:
12556         EditGameEvent();
12557         if (gameMode != EditGame) return;
12558         break;
12559       case EditPosition:
12560         EditPositionDone(TRUE);
12561         break;
12562       case AnalyzeMode:
12563       case AnalyzeFile:
12564         ExitAnalyzeMode();
12565         break;
12566       case EditGame:
12567       default:
12568         break;
12569     }
12570
12571 //    forwardMostMove = currentMove;
12572     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12573
12574     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12575
12576     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12577     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12578       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12579       return;
12580     }
12581     if(!stalling) {
12582       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12583       SendToProgram("force\n", &second);
12584       stalling = 1;
12585       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12586       return;
12587     }
12588     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12589     if(appData.matchPause>10000 || appData.matchPause<10)
12590                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12591     wait = SubtractTimeMarks(&now, &pauseStart);
12592     if(wait < appData.matchPause) {
12593         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12594         return;
12595     }
12596     stalling = 0;
12597     DisplayMessage("", "");
12598     if (startedFromSetupPosition) {
12599         SendBoard(&second, backwardMostMove);
12600     if (appData.debugMode) {
12601         fprintf(debugFP, "Two Machines\n");
12602     }
12603     }
12604     for (i = backwardMostMove; i < forwardMostMove; i++) {
12605         SendMoveToProgram(i, &second);
12606     }
12607
12608     gameMode = TwoMachinesPlay;
12609     pausing = FALSE;
12610     ModeHighlight();
12611     SetGameInfo();
12612     DisplayTwoMachinesTitle();
12613     firstMove = TRUE;
12614     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12615         onmove = &first;
12616     } else {
12617         onmove = &second;
12618     }
12619     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12620     SendToProgram(first.computerString, &first);
12621     if (first.sendName) {
12622       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12623       SendToProgram(buf, &first);
12624     }
12625     SendToProgram(second.computerString, &second);
12626     if (second.sendName) {
12627       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12628       SendToProgram(buf, &second);
12629     }
12630
12631     ResetClocks();
12632     if (!first.sendTime || !second.sendTime) {
12633         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12634         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12635     }
12636     if (onmove->sendTime) {
12637       if (onmove->useColors) {
12638         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12639       }
12640       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12641     }
12642     if (onmove->useColors) {
12643       SendToProgram(onmove->twoMachinesColor, onmove);
12644     }
12645     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12646 //    SendToProgram("go\n", onmove);
12647     onmove->maybeThinking = TRUE;
12648     SetMachineThinkingEnables();
12649
12650     StartClocks();
12651
12652     if(bookHit) { // [HGM] book: simulate book reply
12653         static char bookMove[MSG_SIZ]; // a bit generous?
12654
12655         programStats.nodes = programStats.depth = programStats.time =
12656         programStats.score = programStats.got_only_move = 0;
12657         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12658
12659         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12660         strcat(bookMove, bookHit);
12661         savedMessage = bookMove; // args for deferred call
12662         savedState = onmove;
12663         ScheduleDelayedEvent(DeferredBookMove, 1);
12664     }
12665 }
12666
12667 void
12668 TrainingEvent()
12669 {
12670     if (gameMode == Training) {
12671       SetTrainingModeOff();
12672       gameMode = PlayFromGameFile;
12673       DisplayMessage("", _("Training mode off"));
12674     } else {
12675       gameMode = Training;
12676       animateTraining = appData.animate;
12677
12678       /* make sure we are not already at the end of the game */
12679       if (currentMove < forwardMostMove) {
12680         SetTrainingModeOn();
12681         DisplayMessage("", _("Training mode on"));
12682       } else {
12683         gameMode = PlayFromGameFile;
12684         DisplayError(_("Already at end of game"), 0);
12685       }
12686     }
12687     ModeHighlight();
12688 }
12689
12690 void
12691 IcsClientEvent()
12692 {
12693     if (!appData.icsActive) return;
12694     switch (gameMode) {
12695       case IcsPlayingWhite:
12696       case IcsPlayingBlack:
12697       case IcsObserving:
12698       case IcsIdle:
12699       case BeginningOfGame:
12700       case IcsExamining:
12701         return;
12702
12703       case EditGame:
12704         break;
12705
12706       case EditPosition:
12707         EditPositionDone(TRUE);
12708         break;
12709
12710       case AnalyzeMode:
12711       case AnalyzeFile:
12712         ExitAnalyzeMode();
12713         break;
12714
12715       default:
12716         EditGameEvent();
12717         break;
12718     }
12719
12720     gameMode = IcsIdle;
12721     ModeHighlight();
12722     return;
12723 }
12724
12725
12726 void
12727 EditGameEvent()
12728 {
12729     int i;
12730
12731     switch (gameMode) {
12732       case Training:
12733         SetTrainingModeOff();
12734         break;
12735       case MachinePlaysWhite:
12736       case MachinePlaysBlack:
12737       case BeginningOfGame:
12738         SendToProgram("force\n", &first);
12739         SetUserThinkingEnables();
12740         break;
12741       case PlayFromGameFile:
12742         (void) StopLoadGameTimer();
12743         if (gameFileFP != NULL) {
12744             gameFileFP = NULL;
12745         }
12746         break;
12747       case EditPosition:
12748         EditPositionDone(TRUE);
12749         break;
12750       case AnalyzeMode:
12751       case AnalyzeFile:
12752         ExitAnalyzeMode();
12753         SendToProgram("force\n", &first);
12754         break;
12755       case TwoMachinesPlay:
12756         GameEnds(EndOfFile, NULL, GE_PLAYER);
12757         ResurrectChessProgram();
12758         SetUserThinkingEnables();
12759         break;
12760       case EndOfGame:
12761         ResurrectChessProgram();
12762         break;
12763       case IcsPlayingBlack:
12764       case IcsPlayingWhite:
12765         DisplayError(_("Warning: You are still playing a game"), 0);
12766         break;
12767       case IcsObserving:
12768         DisplayError(_("Warning: You are still observing a game"), 0);
12769         break;
12770       case IcsExamining:
12771         DisplayError(_("Warning: You are still examining a game"), 0);
12772         break;
12773       case IcsIdle:
12774         break;
12775       case EditGame:
12776       default:
12777         return;
12778     }
12779
12780     pausing = FALSE;
12781     StopClocks();
12782     first.offeredDraw = second.offeredDraw = 0;
12783
12784     if (gameMode == PlayFromGameFile) {
12785         whiteTimeRemaining = timeRemaining[0][currentMove];
12786         blackTimeRemaining = timeRemaining[1][currentMove];
12787         DisplayTitle("");
12788     }
12789
12790     if (gameMode == MachinePlaysWhite ||
12791         gameMode == MachinePlaysBlack ||
12792         gameMode == TwoMachinesPlay ||
12793         gameMode == EndOfGame) {
12794         i = forwardMostMove;
12795         while (i > currentMove) {
12796             SendToProgram("undo\n", &first);
12797             i--;
12798         }
12799         whiteTimeRemaining = timeRemaining[0][currentMove];
12800         blackTimeRemaining = timeRemaining[1][currentMove];
12801         DisplayBothClocks();
12802         if (whiteFlag || blackFlag) {
12803             whiteFlag = blackFlag = 0;
12804         }
12805         DisplayTitle("");
12806     }
12807
12808     gameMode = EditGame;
12809     ModeHighlight();
12810     SetGameInfo();
12811 }
12812
12813
12814 void
12815 EditPositionEvent()
12816 {
12817     if (gameMode == EditPosition) {
12818         EditGameEvent();
12819         return;
12820     }
12821
12822     EditGameEvent();
12823     if (gameMode != EditGame) return;
12824
12825     gameMode = EditPosition;
12826     ModeHighlight();
12827     SetGameInfo();
12828     if (currentMove > 0)
12829       CopyBoard(boards[0], boards[currentMove]);
12830
12831     blackPlaysFirst = !WhiteOnMove(currentMove);
12832     ResetClocks();
12833     currentMove = forwardMostMove = backwardMostMove = 0;
12834     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12835     DisplayMove(-1);
12836 }
12837
12838 void
12839 ExitAnalyzeMode()
12840 {
12841     /* [DM] icsEngineAnalyze - possible call from other functions */
12842     if (appData.icsEngineAnalyze) {
12843         appData.icsEngineAnalyze = FALSE;
12844
12845         DisplayMessage("",_("Close ICS engine analyze..."));
12846     }
12847     if (first.analysisSupport && first.analyzing) {
12848       SendToProgram("exit\n", &first);
12849       first.analyzing = FALSE;
12850     }
12851     thinkOutput[0] = NULLCHAR;
12852 }
12853
12854 void
12855 EditPositionDone(Boolean fakeRights)
12856 {
12857     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12858
12859     startedFromSetupPosition = TRUE;
12860     InitChessProgram(&first, FALSE);
12861     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12862       boards[0][EP_STATUS] = EP_NONE;
12863       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12864     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12865         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12866         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12867       } else boards[0][CASTLING][2] = NoRights;
12868     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12869         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12870         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12871       } else boards[0][CASTLING][5] = NoRights;
12872     }
12873     SendToProgram("force\n", &first);
12874     if (blackPlaysFirst) {
12875         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12876         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12877         currentMove = forwardMostMove = backwardMostMove = 1;
12878         CopyBoard(boards[1], boards[0]);
12879     } else {
12880         currentMove = forwardMostMove = backwardMostMove = 0;
12881     }
12882     SendBoard(&first, forwardMostMove);
12883     if (appData.debugMode) {
12884         fprintf(debugFP, "EditPosDone\n");
12885     }
12886     DisplayTitle("");
12887     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12888     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12889     gameMode = EditGame;
12890     ModeHighlight();
12891     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12892     ClearHighlights(); /* [AS] */
12893 }
12894
12895 /* Pause for `ms' milliseconds */
12896 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12897 void
12898 TimeDelay(ms)
12899      long ms;
12900 {
12901     TimeMark m1, m2;
12902
12903     GetTimeMark(&m1);
12904     do {
12905         GetTimeMark(&m2);
12906     } while (SubtractTimeMarks(&m2, &m1) < ms);
12907 }
12908
12909 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12910 void
12911 SendMultiLineToICS(buf)
12912      char *buf;
12913 {
12914     char temp[MSG_SIZ+1], *p;
12915     int len;
12916
12917     len = strlen(buf);
12918     if (len > MSG_SIZ)
12919       len = MSG_SIZ;
12920
12921     strncpy(temp, buf, len);
12922     temp[len] = 0;
12923
12924     p = temp;
12925     while (*p) {
12926         if (*p == '\n' || *p == '\r')
12927           *p = ' ';
12928         ++p;
12929     }
12930
12931     strcat(temp, "\n");
12932     SendToICS(temp);
12933     SendToPlayer(temp, strlen(temp));
12934 }
12935
12936 void
12937 SetWhiteToPlayEvent()
12938 {
12939     if (gameMode == EditPosition) {
12940         blackPlaysFirst = FALSE;
12941         DisplayBothClocks();    /* works because currentMove is 0 */
12942     } else if (gameMode == IcsExamining) {
12943         SendToICS(ics_prefix);
12944         SendToICS("tomove white\n");
12945     }
12946 }
12947
12948 void
12949 SetBlackToPlayEvent()
12950 {
12951     if (gameMode == EditPosition) {
12952         blackPlaysFirst = TRUE;
12953         currentMove = 1;        /* kludge */
12954         DisplayBothClocks();
12955         currentMove = 0;
12956     } else if (gameMode == IcsExamining) {
12957         SendToICS(ics_prefix);
12958         SendToICS("tomove black\n");
12959     }
12960 }
12961
12962 void
12963 EditPositionMenuEvent(selection, x, y)
12964      ChessSquare selection;
12965      int x, y;
12966 {
12967     char buf[MSG_SIZ];
12968     ChessSquare piece = boards[0][y][x];
12969
12970     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12971
12972     switch (selection) {
12973       case ClearBoard:
12974         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12975             SendToICS(ics_prefix);
12976             SendToICS("bsetup clear\n");
12977         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12978             SendToICS(ics_prefix);
12979             SendToICS("clearboard\n");
12980         } else {
12981             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12982                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12983                 for (y = 0; y < BOARD_HEIGHT; y++) {
12984                     if (gameMode == IcsExamining) {
12985                         if (boards[currentMove][y][x] != EmptySquare) {
12986                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12987                                     AAA + x, ONE + y);
12988                             SendToICS(buf);
12989                         }
12990                     } else {
12991                         boards[0][y][x] = p;
12992                     }
12993                 }
12994             }
12995         }
12996         if (gameMode == EditPosition) {
12997             DrawPosition(FALSE, boards[0]);
12998         }
12999         break;
13000
13001       case WhitePlay:
13002         SetWhiteToPlayEvent();
13003         break;
13004
13005       case BlackPlay:
13006         SetBlackToPlayEvent();
13007         break;
13008
13009       case EmptySquare:
13010         if (gameMode == IcsExamining) {
13011             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13012             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13013             SendToICS(buf);
13014         } else {
13015             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13016                 if(x == BOARD_LEFT-2) {
13017                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13018                     boards[0][y][1] = 0;
13019                 } else
13020                 if(x == BOARD_RGHT+1) {
13021                     if(y >= gameInfo.holdingsSize) break;
13022                     boards[0][y][BOARD_WIDTH-2] = 0;
13023                 } else break;
13024             }
13025             boards[0][y][x] = EmptySquare;
13026             DrawPosition(FALSE, boards[0]);
13027         }
13028         break;
13029
13030       case PromotePiece:
13031         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13032            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13033             selection = (ChessSquare) (PROMOTED piece);
13034         } else if(piece == EmptySquare) selection = WhiteSilver;
13035         else selection = (ChessSquare)((int)piece - 1);
13036         goto defaultlabel;
13037
13038       case DemotePiece:
13039         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13040            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13041             selection = (ChessSquare) (DEMOTED piece);
13042         } else if(piece == EmptySquare) selection = BlackSilver;
13043         else selection = (ChessSquare)((int)piece + 1);
13044         goto defaultlabel;
13045
13046       case WhiteQueen:
13047       case BlackQueen:
13048         if(gameInfo.variant == VariantShatranj ||
13049            gameInfo.variant == VariantXiangqi  ||
13050            gameInfo.variant == VariantCourier  ||
13051            gameInfo.variant == VariantMakruk     )
13052             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13053         goto defaultlabel;
13054
13055       case WhiteKing:
13056       case BlackKing:
13057         if(gameInfo.variant == VariantXiangqi)
13058             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13059         if(gameInfo.variant == VariantKnightmate)
13060             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13061       default:
13062         defaultlabel:
13063         if (gameMode == IcsExamining) {
13064             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13065             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13066                      PieceToChar(selection), AAA + x, ONE + y);
13067             SendToICS(buf);
13068         } else {
13069             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13070                 int n;
13071                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13072                     n = PieceToNumber(selection - BlackPawn);
13073                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13074                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13075                     boards[0][BOARD_HEIGHT-1-n][1]++;
13076                 } else
13077                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13078                     n = PieceToNumber(selection);
13079                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13080                     boards[0][n][BOARD_WIDTH-1] = selection;
13081                     boards[0][n][BOARD_WIDTH-2]++;
13082                 }
13083             } else
13084             boards[0][y][x] = selection;
13085             DrawPosition(TRUE, boards[0]);
13086         }
13087         break;
13088     }
13089 }
13090
13091
13092 void
13093 DropMenuEvent(selection, x, y)
13094      ChessSquare selection;
13095      int x, y;
13096 {
13097     ChessMove moveType;
13098
13099     switch (gameMode) {
13100       case IcsPlayingWhite:
13101       case MachinePlaysBlack:
13102         if (!WhiteOnMove(currentMove)) {
13103             DisplayMoveError(_("It is Black's turn"));
13104             return;
13105         }
13106         moveType = WhiteDrop;
13107         break;
13108       case IcsPlayingBlack:
13109       case MachinePlaysWhite:
13110         if (WhiteOnMove(currentMove)) {
13111             DisplayMoveError(_("It is White's turn"));
13112             return;
13113         }
13114         moveType = BlackDrop;
13115         break;
13116       case EditGame:
13117         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13118         break;
13119       default:
13120         return;
13121     }
13122
13123     if (moveType == BlackDrop && selection < BlackPawn) {
13124       selection = (ChessSquare) ((int) selection
13125                                  + (int) BlackPawn - (int) WhitePawn);
13126     }
13127     if (boards[currentMove][y][x] != EmptySquare) {
13128         DisplayMoveError(_("That square is occupied"));
13129         return;
13130     }
13131
13132     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13133 }
13134
13135 void
13136 AcceptEvent()
13137 {
13138     /* Accept a pending offer of any kind from opponent */
13139
13140     if (appData.icsActive) {
13141         SendToICS(ics_prefix);
13142         SendToICS("accept\n");
13143     } else if (cmailMsgLoaded) {
13144         if (currentMove == cmailOldMove &&
13145             commentList[cmailOldMove] != NULL &&
13146             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13147                    "Black offers a draw" : "White offers a draw")) {
13148             TruncateGame();
13149             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13151         } else {
13152             DisplayError(_("There is no pending offer on this move"), 0);
13153             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13154         }
13155     } else {
13156         /* Not used for offers from chess program */
13157     }
13158 }
13159
13160 void
13161 DeclineEvent()
13162 {
13163     /* Decline a pending offer of any kind from opponent */
13164
13165     if (appData.icsActive) {
13166         SendToICS(ics_prefix);
13167         SendToICS("decline\n");
13168     } else if (cmailMsgLoaded) {
13169         if (currentMove == cmailOldMove &&
13170             commentList[cmailOldMove] != NULL &&
13171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13172                    "Black offers a draw" : "White offers a draw")) {
13173 #ifdef NOTDEF
13174             AppendComment(cmailOldMove, "Draw declined", TRUE);
13175             DisplayComment(cmailOldMove - 1, "Draw declined");
13176 #endif /*NOTDEF*/
13177         } else {
13178             DisplayError(_("There is no pending offer on this move"), 0);
13179         }
13180     } else {
13181         /* Not used for offers from chess program */
13182     }
13183 }
13184
13185 void
13186 RematchEvent()
13187 {
13188     /* Issue ICS rematch command */
13189     if (appData.icsActive) {
13190         SendToICS(ics_prefix);
13191         SendToICS("rematch\n");
13192     }
13193 }
13194
13195 void
13196 CallFlagEvent()
13197 {
13198     /* Call your opponent's flag (claim a win on time) */
13199     if (appData.icsActive) {
13200         SendToICS(ics_prefix);
13201         SendToICS("flag\n");
13202     } else {
13203         switch (gameMode) {
13204           default:
13205             return;
13206           case MachinePlaysWhite:
13207             if (whiteFlag) {
13208                 if (blackFlag)
13209                   GameEnds(GameIsDrawn, "Both players ran out of time",
13210                            GE_PLAYER);
13211                 else
13212                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13213             } else {
13214                 DisplayError(_("Your opponent is not out of time"), 0);
13215             }
13216             break;
13217           case MachinePlaysBlack:
13218             if (blackFlag) {
13219                 if (whiteFlag)
13220                   GameEnds(GameIsDrawn, "Both players ran out of time",
13221                            GE_PLAYER);
13222                 else
13223                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13224             } else {
13225                 DisplayError(_("Your opponent is not out of time"), 0);
13226             }
13227             break;
13228         }
13229     }
13230 }
13231
13232 void
13233 ClockClick(int which)
13234 {       // [HGM] code moved to back-end from winboard.c
13235         if(which) { // black clock
13236           if (gameMode == EditPosition || gameMode == IcsExamining) {
13237             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13238             SetBlackToPlayEvent();
13239           } else if (gameMode == EditGame || shiftKey) {
13240             AdjustClock(which, -1);
13241           } else if (gameMode == IcsPlayingWhite ||
13242                      gameMode == MachinePlaysBlack) {
13243             CallFlagEvent();
13244           }
13245         } else { // white clock
13246           if (gameMode == EditPosition || gameMode == IcsExamining) {
13247             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13248             SetWhiteToPlayEvent();
13249           } else if (gameMode == EditGame || shiftKey) {
13250             AdjustClock(which, -1);
13251           } else if (gameMode == IcsPlayingBlack ||
13252                    gameMode == MachinePlaysWhite) {
13253             CallFlagEvent();
13254           }
13255         }
13256 }
13257
13258 void
13259 DrawEvent()
13260 {
13261     /* Offer draw or accept pending draw offer from opponent */
13262
13263     if (appData.icsActive) {
13264         /* Note: tournament rules require draw offers to be
13265            made after you make your move but before you punch
13266            your clock.  Currently ICS doesn't let you do that;
13267            instead, you immediately punch your clock after making
13268            a move, but you can offer a draw at any time. */
13269
13270         SendToICS(ics_prefix);
13271         SendToICS("draw\n");
13272         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13273     } else if (cmailMsgLoaded) {
13274         if (currentMove == cmailOldMove &&
13275             commentList[cmailOldMove] != NULL &&
13276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13277                    "Black offers a draw" : "White offers a draw")) {
13278             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13279             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13280         } else if (currentMove == cmailOldMove + 1) {
13281             char *offer = WhiteOnMove(cmailOldMove) ?
13282               "White offers a draw" : "Black offers a draw";
13283             AppendComment(currentMove, offer, TRUE);
13284             DisplayComment(currentMove - 1, offer);
13285             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13286         } else {
13287             DisplayError(_("You must make your move before offering a draw"), 0);
13288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13289         }
13290     } else if (first.offeredDraw) {
13291         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13292     } else {
13293         if (first.sendDrawOffers) {
13294             SendToProgram("draw\n", &first);
13295             userOfferedDraw = TRUE;
13296         }
13297     }
13298 }
13299
13300 void
13301 AdjournEvent()
13302 {
13303     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13304
13305     if (appData.icsActive) {
13306         SendToICS(ics_prefix);
13307         SendToICS("adjourn\n");
13308     } else {
13309         /* Currently GNU Chess doesn't offer or accept Adjourns */
13310     }
13311 }
13312
13313
13314 void
13315 AbortEvent()
13316 {
13317     /* Offer Abort or accept pending Abort offer from opponent */
13318
13319     if (appData.icsActive) {
13320         SendToICS(ics_prefix);
13321         SendToICS("abort\n");
13322     } else {
13323         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13324     }
13325 }
13326
13327 void
13328 ResignEvent()
13329 {
13330     /* Resign.  You can do this even if it's not your turn. */
13331
13332     if (appData.icsActive) {
13333         SendToICS(ics_prefix);
13334         SendToICS("resign\n");
13335     } else {
13336         switch (gameMode) {
13337           case MachinePlaysWhite:
13338             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13339             break;
13340           case MachinePlaysBlack:
13341             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13342             break;
13343           case EditGame:
13344             if (cmailMsgLoaded) {
13345                 TruncateGame();
13346                 if (WhiteOnMove(cmailOldMove)) {
13347                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13348                 } else {
13349                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13350                 }
13351                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13352             }
13353             break;
13354           default:
13355             break;
13356         }
13357     }
13358 }
13359
13360
13361 void
13362 StopObservingEvent()
13363 {
13364     /* Stop observing current games */
13365     SendToICS(ics_prefix);
13366     SendToICS("unobserve\n");
13367 }
13368
13369 void
13370 StopExaminingEvent()
13371 {
13372     /* Stop observing current game */
13373     SendToICS(ics_prefix);
13374     SendToICS("unexamine\n");
13375 }
13376
13377 void
13378 ForwardInner(target)
13379      int target;
13380 {
13381     int limit;
13382
13383     if (appData.debugMode)
13384         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13385                 target, currentMove, forwardMostMove);
13386
13387     if (gameMode == EditPosition)
13388       return;
13389
13390     if (gameMode == PlayFromGameFile && !pausing)
13391       PauseEvent();
13392
13393     if (gameMode == IcsExamining && pausing)
13394       limit = pauseExamForwardMostMove;
13395     else
13396       limit = forwardMostMove;
13397
13398     if (target > limit) target = limit;
13399
13400     if (target > 0 && moveList[target - 1][0]) {
13401         int fromX, fromY, toX, toY;
13402         toX = moveList[target - 1][2] - AAA;
13403         toY = moveList[target - 1][3] - ONE;
13404         if (moveList[target - 1][1] == '@') {
13405             if (appData.highlightLastMove) {
13406                 SetHighlights(-1, -1, toX, toY);
13407             }
13408         } else {
13409             fromX = moveList[target - 1][0] - AAA;
13410             fromY = moveList[target - 1][1] - ONE;
13411             if (target == currentMove + 1) {
13412                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13413             }
13414             if (appData.highlightLastMove) {
13415                 SetHighlights(fromX, fromY, toX, toY);
13416             }
13417         }
13418     }
13419     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13420         gameMode == Training || gameMode == PlayFromGameFile ||
13421         gameMode == AnalyzeFile) {
13422         while (currentMove < target) {
13423             SendMoveToProgram(currentMove++, &first);
13424         }
13425     } else {
13426         currentMove = target;
13427     }
13428
13429     if (gameMode == EditGame || gameMode == EndOfGame) {
13430         whiteTimeRemaining = timeRemaining[0][currentMove];
13431         blackTimeRemaining = timeRemaining[1][currentMove];
13432     }
13433     DisplayBothClocks();
13434     DisplayMove(currentMove - 1);
13435     DrawPosition(FALSE, boards[currentMove]);
13436     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13437     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13438         DisplayComment(currentMove - 1, commentList[currentMove]);
13439     }
13440 }
13441
13442
13443 void
13444 ForwardEvent()
13445 {
13446     if (gameMode == IcsExamining && !pausing) {
13447         SendToICS(ics_prefix);
13448         SendToICS("forward\n");
13449     } else {
13450         ForwardInner(currentMove + 1);
13451     }
13452 }
13453
13454 void
13455 ToEndEvent()
13456 {
13457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13458         /* to optimze, we temporarily turn off analysis mode while we feed
13459          * the remaining moves to the engine. Otherwise we get analysis output
13460          * after each move.
13461          */
13462         if (first.analysisSupport) {
13463           SendToProgram("exit\nforce\n", &first);
13464           first.analyzing = FALSE;
13465         }
13466     }
13467
13468     if (gameMode == IcsExamining && !pausing) {
13469         SendToICS(ics_prefix);
13470         SendToICS("forward 999999\n");
13471     } else {
13472         ForwardInner(forwardMostMove);
13473     }
13474
13475     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13476         /* we have fed all the moves, so reactivate analysis mode */
13477         SendToProgram("analyze\n", &first);
13478         first.analyzing = TRUE;
13479         /*first.maybeThinking = TRUE;*/
13480         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13481     }
13482 }
13483
13484 void
13485 BackwardInner(target)
13486      int target;
13487 {
13488     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13489
13490     if (appData.debugMode)
13491         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13492                 target, currentMove, forwardMostMove);
13493
13494     if (gameMode == EditPosition) return;
13495     if (currentMove <= backwardMostMove) {
13496         ClearHighlights();
13497         DrawPosition(full_redraw, boards[currentMove]);
13498         return;
13499     }
13500     if (gameMode == PlayFromGameFile && !pausing)
13501       PauseEvent();
13502
13503     if (moveList[target][0]) {
13504         int fromX, fromY, toX, toY;
13505         toX = moveList[target][2] - AAA;
13506         toY = moveList[target][3] - ONE;
13507         if (moveList[target][1] == '@') {
13508             if (appData.highlightLastMove) {
13509                 SetHighlights(-1, -1, toX, toY);
13510             }
13511         } else {
13512             fromX = moveList[target][0] - AAA;
13513             fromY = moveList[target][1] - ONE;
13514             if (target == currentMove - 1) {
13515                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13516             }
13517             if (appData.highlightLastMove) {
13518                 SetHighlights(fromX, fromY, toX, toY);
13519             }
13520         }
13521     }
13522     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13523         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13524         while (currentMove > target) {
13525             SendToProgram("undo\n", &first);
13526             currentMove--;
13527         }
13528     } else {
13529         currentMove = target;
13530     }
13531
13532     if (gameMode == EditGame || gameMode == EndOfGame) {
13533         whiteTimeRemaining = timeRemaining[0][currentMove];
13534         blackTimeRemaining = timeRemaining[1][currentMove];
13535     }
13536     DisplayBothClocks();
13537     DisplayMove(currentMove - 1);
13538     DrawPosition(full_redraw, boards[currentMove]);
13539     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13540     // [HGM] PV info: routine tests if comment empty
13541     DisplayComment(currentMove - 1, commentList[currentMove]);
13542 }
13543
13544 void
13545 BackwardEvent()
13546 {
13547     if (gameMode == IcsExamining && !pausing) {
13548         SendToICS(ics_prefix);
13549         SendToICS("backward\n");
13550     } else {
13551         BackwardInner(currentMove - 1);
13552     }
13553 }
13554
13555 void
13556 ToStartEvent()
13557 {
13558     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13559         /* to optimize, we temporarily turn off analysis mode while we undo
13560          * all the moves. Otherwise we get analysis output after each undo.
13561          */
13562         if (first.analysisSupport) {
13563           SendToProgram("exit\nforce\n", &first);
13564           first.analyzing = FALSE;
13565         }
13566     }
13567
13568     if (gameMode == IcsExamining && !pausing) {
13569         SendToICS(ics_prefix);
13570         SendToICS("backward 999999\n");
13571     } else {
13572         BackwardInner(backwardMostMove);
13573     }
13574
13575     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13576         /* we have fed all the moves, so reactivate analysis mode */
13577         SendToProgram("analyze\n", &first);
13578         first.analyzing = TRUE;
13579         /*first.maybeThinking = TRUE;*/
13580         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13581     }
13582 }
13583
13584 void
13585 ToNrEvent(int to)
13586 {
13587   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13588   if (to >= forwardMostMove) to = forwardMostMove;
13589   if (to <= backwardMostMove) to = backwardMostMove;
13590   if (to < currentMove) {
13591     BackwardInner(to);
13592   } else {
13593     ForwardInner(to);
13594   }
13595 }
13596
13597 void
13598 RevertEvent(Boolean annotate)
13599 {
13600     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13601         return;
13602     }
13603     if (gameMode != IcsExamining) {
13604         DisplayError(_("You are not examining a game"), 0);
13605         return;
13606     }
13607     if (pausing) {
13608         DisplayError(_("You can't revert while pausing"), 0);
13609         return;
13610     }
13611     SendToICS(ics_prefix);
13612     SendToICS("revert\n");
13613 }
13614
13615 void
13616 RetractMoveEvent()
13617 {
13618     switch (gameMode) {
13619       case MachinePlaysWhite:
13620       case MachinePlaysBlack:
13621         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13622             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13623             return;
13624         }
13625         if (forwardMostMove < 2) return;
13626         currentMove = forwardMostMove = forwardMostMove - 2;
13627         whiteTimeRemaining = timeRemaining[0][currentMove];
13628         blackTimeRemaining = timeRemaining[1][currentMove];
13629         DisplayBothClocks();
13630         DisplayMove(currentMove - 1);
13631         ClearHighlights();/*!! could figure this out*/
13632         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13633         SendToProgram("remove\n", &first);
13634         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13635         break;
13636
13637       case BeginningOfGame:
13638       default:
13639         break;
13640
13641       case IcsPlayingWhite:
13642       case IcsPlayingBlack:
13643         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13644             SendToICS(ics_prefix);
13645             SendToICS("takeback 2\n");
13646         } else {
13647             SendToICS(ics_prefix);
13648             SendToICS("takeback 1\n");
13649         }
13650         break;
13651     }
13652 }
13653
13654 void
13655 MoveNowEvent()
13656 {
13657     ChessProgramState *cps;
13658
13659     switch (gameMode) {
13660       case MachinePlaysWhite:
13661         if (!WhiteOnMove(forwardMostMove)) {
13662             DisplayError(_("It is your turn"), 0);
13663             return;
13664         }
13665         cps = &first;
13666         break;
13667       case MachinePlaysBlack:
13668         if (WhiteOnMove(forwardMostMove)) {
13669             DisplayError(_("It is your turn"), 0);
13670             return;
13671         }
13672         cps = &first;
13673         break;
13674       case TwoMachinesPlay:
13675         if (WhiteOnMove(forwardMostMove) ==
13676             (first.twoMachinesColor[0] == 'w')) {
13677             cps = &first;
13678         } else {
13679             cps = &second;
13680         }
13681         break;
13682       case BeginningOfGame:
13683       default:
13684         return;
13685     }
13686     SendToProgram("?\n", cps);
13687 }
13688
13689 void
13690 TruncateGameEvent()
13691 {
13692     EditGameEvent();
13693     if (gameMode != EditGame) return;
13694     TruncateGame();
13695 }
13696
13697 void
13698 TruncateGame()
13699 {
13700     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13701     if (forwardMostMove > currentMove) {
13702         if (gameInfo.resultDetails != NULL) {
13703             free(gameInfo.resultDetails);
13704             gameInfo.resultDetails = NULL;
13705             gameInfo.result = GameUnfinished;
13706         }
13707         forwardMostMove = currentMove;
13708         HistorySet(parseList, backwardMostMove, forwardMostMove,
13709                    currentMove-1);
13710     }
13711 }
13712
13713 void
13714 HintEvent()
13715 {
13716     if (appData.noChessProgram) return;
13717     switch (gameMode) {
13718       case MachinePlaysWhite:
13719         if (WhiteOnMove(forwardMostMove)) {
13720             DisplayError(_("Wait until your turn"), 0);
13721             return;
13722         }
13723         break;
13724       case BeginningOfGame:
13725       case MachinePlaysBlack:
13726         if (!WhiteOnMove(forwardMostMove)) {
13727             DisplayError(_("Wait until your turn"), 0);
13728             return;
13729         }
13730         break;
13731       default:
13732         DisplayError(_("No hint available"), 0);
13733         return;
13734     }
13735     SendToProgram("hint\n", &first);
13736     hintRequested = TRUE;
13737 }
13738
13739 void
13740 BookEvent()
13741 {
13742     if (appData.noChessProgram) return;
13743     switch (gameMode) {
13744       case MachinePlaysWhite:
13745         if (WhiteOnMove(forwardMostMove)) {
13746             DisplayError(_("Wait until your turn"), 0);
13747             return;
13748         }
13749         break;
13750       case BeginningOfGame:
13751       case MachinePlaysBlack:
13752         if (!WhiteOnMove(forwardMostMove)) {
13753             DisplayError(_("Wait until your turn"), 0);
13754             return;
13755         }
13756         break;
13757       case EditPosition:
13758         EditPositionDone(TRUE);
13759         break;
13760       case TwoMachinesPlay:
13761         return;
13762       default:
13763         break;
13764     }
13765     SendToProgram("bk\n", &first);
13766     bookOutput[0] = NULLCHAR;
13767     bookRequested = TRUE;
13768 }
13769
13770 void
13771 AboutGameEvent()
13772 {
13773     char *tags = PGNTags(&gameInfo);
13774     TagsPopUp(tags, CmailMsg());
13775     free(tags);
13776 }
13777
13778 /* end button procedures */
13779
13780 void
13781 PrintPosition(fp, move)
13782      FILE *fp;
13783      int move;
13784 {
13785     int i, j;
13786
13787     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13788         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13789             char c = PieceToChar(boards[move][i][j]);
13790             fputc(c == 'x' ? '.' : c, fp);
13791             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13792         }
13793     }
13794     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13795       fprintf(fp, "white to play\n");
13796     else
13797       fprintf(fp, "black to play\n");
13798 }
13799
13800 void
13801 PrintOpponents(fp)
13802      FILE *fp;
13803 {
13804     if (gameInfo.white != NULL) {
13805         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13806     } else {
13807         fprintf(fp, "\n");
13808     }
13809 }
13810
13811 /* Find last component of program's own name, using some heuristics */
13812 void
13813 TidyProgramName(prog, host, buf)
13814      char *prog, *host, buf[MSG_SIZ];
13815 {
13816     char *p, *q;
13817     int local = (strcmp(host, "localhost") == 0);
13818     while (!local && (p = strchr(prog, ';')) != NULL) {
13819         p++;
13820         while (*p == ' ') p++;
13821         prog = p;
13822     }
13823     if (*prog == '"' || *prog == '\'') {
13824         q = strchr(prog + 1, *prog);
13825     } else {
13826         q = strchr(prog, ' ');
13827     }
13828     if (q == NULL) q = prog + strlen(prog);
13829     p = q;
13830     while (p >= prog && *p != '/' && *p != '\\') p--;
13831     p++;
13832     if(p == prog && *p == '"') p++;
13833     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13834     memcpy(buf, p, q - p);
13835     buf[q - p] = NULLCHAR;
13836     if (!local) {
13837         strcat(buf, "@");
13838         strcat(buf, host);
13839     }
13840 }
13841
13842 char *
13843 TimeControlTagValue()
13844 {
13845     char buf[MSG_SIZ];
13846     if (!appData.clockMode) {
13847       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13848     } else if (movesPerSession > 0) {
13849       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13850     } else if (timeIncrement == 0) {
13851       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13852     } else {
13853       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13854     }
13855     return StrSave(buf);
13856 }
13857
13858 void
13859 SetGameInfo()
13860 {
13861     /* This routine is used only for certain modes */
13862     VariantClass v = gameInfo.variant;
13863     ChessMove r = GameUnfinished;
13864     char *p = NULL;
13865
13866     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13867         r = gameInfo.result;
13868         p = gameInfo.resultDetails;
13869         gameInfo.resultDetails = NULL;
13870     }
13871     ClearGameInfo(&gameInfo);
13872     gameInfo.variant = v;
13873
13874     switch (gameMode) {
13875       case MachinePlaysWhite:
13876         gameInfo.event = StrSave( appData.pgnEventHeader );
13877         gameInfo.site = StrSave(HostName());
13878         gameInfo.date = PGNDate();
13879         gameInfo.round = StrSave("-");
13880         gameInfo.white = StrSave(first.tidy);
13881         gameInfo.black = StrSave(UserName());
13882         gameInfo.timeControl = TimeControlTagValue();
13883         break;
13884
13885       case MachinePlaysBlack:
13886         gameInfo.event = StrSave( appData.pgnEventHeader );
13887         gameInfo.site = StrSave(HostName());
13888         gameInfo.date = PGNDate();
13889         gameInfo.round = StrSave("-");
13890         gameInfo.white = StrSave(UserName());
13891         gameInfo.black = StrSave(first.tidy);
13892         gameInfo.timeControl = TimeControlTagValue();
13893         break;
13894
13895       case TwoMachinesPlay:
13896         gameInfo.event = StrSave( appData.pgnEventHeader );
13897         gameInfo.site = StrSave(HostName());
13898         gameInfo.date = PGNDate();
13899         if (roundNr > 0) {
13900             char buf[MSG_SIZ];
13901             snprintf(buf, MSG_SIZ, "%d", roundNr);
13902             gameInfo.round = StrSave(buf);
13903         } else {
13904             gameInfo.round = StrSave("-");
13905         }
13906         if (first.twoMachinesColor[0] == 'w') {
13907             gameInfo.white = StrSave(first.tidy);
13908             gameInfo.black = StrSave(second.tidy);
13909         } else {
13910             gameInfo.white = StrSave(second.tidy);
13911             gameInfo.black = StrSave(first.tidy);
13912         }
13913         gameInfo.timeControl = TimeControlTagValue();
13914         break;
13915
13916       case EditGame:
13917         gameInfo.event = StrSave("Edited game");
13918         gameInfo.site = StrSave(HostName());
13919         gameInfo.date = PGNDate();
13920         gameInfo.round = StrSave("-");
13921         gameInfo.white = StrSave("-");
13922         gameInfo.black = StrSave("-");
13923         gameInfo.result = r;
13924         gameInfo.resultDetails = p;
13925         break;
13926
13927       case EditPosition:
13928         gameInfo.event = StrSave("Edited position");
13929         gameInfo.site = StrSave(HostName());
13930         gameInfo.date = PGNDate();
13931         gameInfo.round = StrSave("-");
13932         gameInfo.white = StrSave("-");
13933         gameInfo.black = StrSave("-");
13934         break;
13935
13936       case IcsPlayingWhite:
13937       case IcsPlayingBlack:
13938       case IcsObserving:
13939       case IcsExamining:
13940         break;
13941
13942       case PlayFromGameFile:
13943         gameInfo.event = StrSave("Game from non-PGN file");
13944         gameInfo.site = StrSave(HostName());
13945         gameInfo.date = PGNDate();
13946         gameInfo.round = StrSave("-");
13947         gameInfo.white = StrSave("?");
13948         gameInfo.black = StrSave("?");
13949         break;
13950
13951       default:
13952         break;
13953     }
13954 }
13955
13956 void
13957 ReplaceComment(index, text)
13958      int index;
13959      char *text;
13960 {
13961     int len;
13962     char *p;
13963     float score;
13964
13965     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13966        pvInfoList[index-1].depth == len &&
13967        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13968        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13969     while (*text == '\n') text++;
13970     len = strlen(text);
13971     while (len > 0 && text[len - 1] == '\n') len--;
13972
13973     if (commentList[index] != NULL)
13974       free(commentList[index]);
13975
13976     if (len == 0) {
13977         commentList[index] = NULL;
13978         return;
13979     }
13980   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13981       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13982       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13983     commentList[index] = (char *) malloc(len + 2);
13984     strncpy(commentList[index], text, len);
13985     commentList[index][len] = '\n';
13986     commentList[index][len + 1] = NULLCHAR;
13987   } else {
13988     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13989     char *p;
13990     commentList[index] = (char *) malloc(len + 7);
13991     safeStrCpy(commentList[index], "{\n", 3);
13992     safeStrCpy(commentList[index]+2, text, len+1);
13993     commentList[index][len+2] = NULLCHAR;
13994     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13995     strcat(commentList[index], "\n}\n");
13996   }
13997 }
13998
13999 void
14000 CrushCRs(text)
14001      char *text;
14002 {
14003   char *p = text;
14004   char *q = text;
14005   char ch;
14006
14007   do {
14008     ch = *p++;
14009     if (ch == '\r') continue;
14010     *q++ = ch;
14011   } while (ch != '\0');
14012 }
14013
14014 void
14015 AppendComment(index, text, addBraces)
14016      int index;
14017      char *text;
14018      Boolean addBraces; // [HGM] braces: tells if we should add {}
14019 {
14020     int oldlen, len;
14021     char *old;
14022
14023 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14024     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14025
14026     CrushCRs(text);
14027     while (*text == '\n') text++;
14028     len = strlen(text);
14029     while (len > 0 && text[len - 1] == '\n') len--;
14030
14031     if (len == 0) return;
14032
14033     if (commentList[index] != NULL) {
14034         old = commentList[index];
14035         oldlen = strlen(old);
14036         while(commentList[index][oldlen-1] ==  '\n')
14037           commentList[index][--oldlen] = NULLCHAR;
14038         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14039         safeStrCpy(commentList[index], old, oldlen + len + 6);
14040         free(old);
14041         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14042         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14043           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14044           while (*text == '\n') { text++; len--; }
14045           commentList[index][--oldlen] = NULLCHAR;
14046       }
14047         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14048         else          strcat(commentList[index], "\n");
14049         strcat(commentList[index], text);
14050         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14051         else          strcat(commentList[index], "\n");
14052     } else {
14053         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14054         if(addBraces)
14055           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14056         else commentList[index][0] = NULLCHAR;
14057         strcat(commentList[index], text);
14058         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14059         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14060     }
14061 }
14062
14063 static char * FindStr( char * text, char * sub_text )
14064 {
14065     char * result = strstr( text, sub_text );
14066
14067     if( result != NULL ) {
14068         result += strlen( sub_text );
14069     }
14070
14071     return result;
14072 }
14073
14074 /* [AS] Try to extract PV info from PGN comment */
14075 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14076 char *GetInfoFromComment( int index, char * text )
14077 {
14078     char * sep = text, *p;
14079
14080     if( text != NULL && index > 0 ) {
14081         int score = 0;
14082         int depth = 0;
14083         int time = -1, sec = 0, deci;
14084         char * s_eval = FindStr( text, "[%eval " );
14085         char * s_emt = FindStr( text, "[%emt " );
14086
14087         if( s_eval != NULL || s_emt != NULL ) {
14088             /* New style */
14089             char delim;
14090
14091             if( s_eval != NULL ) {
14092                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14093                     return text;
14094                 }
14095
14096                 if( delim != ']' ) {
14097                     return text;
14098                 }
14099             }
14100
14101             if( s_emt != NULL ) {
14102             }
14103                 return text;
14104         }
14105         else {
14106             /* We expect something like: [+|-]nnn.nn/dd */
14107             int score_lo = 0;
14108
14109             if(*text != '{') return text; // [HGM] braces: must be normal comment
14110
14111             sep = strchr( text, '/' );
14112             if( sep == NULL || sep < (text+4) ) {
14113                 return text;
14114             }
14115
14116             p = text;
14117             if(p[1] == '(') { // comment starts with PV
14118                p = strchr(p, ')'); // locate end of PV
14119                if(p == NULL || sep < p+5) return text;
14120                // at this point we have something like "{(.*) +0.23/6 ..."
14121                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14122                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14123                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14124             }
14125             time = -1; sec = -1; deci = -1;
14126             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14127                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14128                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14129                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14130                 return text;
14131             }
14132
14133             if( score_lo < 0 || score_lo >= 100 ) {
14134                 return text;
14135             }
14136
14137             if(sec >= 0) time = 600*time + 10*sec; else
14138             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14139
14140             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14141
14142             /* [HGM] PV time: now locate end of PV info */
14143             while( *++sep >= '0' && *sep <= '9'); // strip depth
14144             if(time >= 0)
14145             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14146             if(sec >= 0)
14147             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14148             if(deci >= 0)
14149             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14150             while(*sep == ' ') sep++;
14151         }
14152
14153         if( depth <= 0 ) {
14154             return text;
14155         }
14156
14157         if( time < 0 ) {
14158             time = -1;
14159         }
14160
14161         pvInfoList[index-1].depth = depth;
14162         pvInfoList[index-1].score = score;
14163         pvInfoList[index-1].time  = 10*time; // centi-sec
14164         if(*sep == '}') *sep = 0; else *--sep = '{';
14165         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14166     }
14167     return sep;
14168 }
14169
14170 void
14171 SendToProgram(message, cps)
14172      char *message;
14173      ChessProgramState *cps;
14174 {
14175     int count, outCount, error;
14176     char buf[MSG_SIZ];
14177
14178     if (cps->pr == NULL) return;
14179     Attention(cps);
14180
14181     if (appData.debugMode) {
14182         TimeMark now;
14183         GetTimeMark(&now);
14184         fprintf(debugFP, "%ld >%-6s: %s",
14185                 SubtractTimeMarks(&now, &programStartTime),
14186                 cps->which, message);
14187     }
14188
14189     count = strlen(message);
14190     outCount = OutputToProcess(cps->pr, message, count, &error);
14191     if (outCount < count && !exiting
14192                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14193       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14194       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14195         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14196             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14197                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14198                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14199                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14200             } else {
14201                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14202                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14203                 gameInfo.result = res;
14204             }
14205             gameInfo.resultDetails = StrSave(buf);
14206         }
14207         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14208         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14209     }
14210 }
14211
14212 void
14213 ReceiveFromProgram(isr, closure, message, count, error)
14214      InputSourceRef isr;
14215      VOIDSTAR closure;
14216      char *message;
14217      int count;
14218      int error;
14219 {
14220     char *end_str;
14221     char buf[MSG_SIZ];
14222     ChessProgramState *cps = (ChessProgramState *)closure;
14223
14224     if (isr != cps->isr) return; /* Killed intentionally */
14225     if (count <= 0) {
14226         if (count == 0) {
14227             RemoveInputSource(cps->isr);
14228             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14229                     _(cps->which), cps->program);
14230         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14231                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14232                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14233                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14234                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14235                 } else {
14236                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14237                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14238                     gameInfo.result = res;
14239                 }
14240                 gameInfo.resultDetails = StrSave(buf);
14241             }
14242             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14243             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14244         } else {
14245             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14246                     _(cps->which), cps->program);
14247             RemoveInputSource(cps->isr);
14248
14249             /* [AS] Program is misbehaving badly... kill it */
14250             if( count == -2 ) {
14251                 DestroyChildProcess( cps->pr, 9 );
14252                 cps->pr = NoProc;
14253             }
14254
14255             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14256         }
14257         return;
14258     }
14259
14260     if ((end_str = strchr(message, '\r')) != NULL)
14261       *end_str = NULLCHAR;
14262     if ((end_str = strchr(message, '\n')) != NULL)
14263       *end_str = NULLCHAR;
14264
14265     if (appData.debugMode) {
14266         TimeMark now; int print = 1;
14267         char *quote = ""; char c; int i;
14268
14269         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14270                 char start = message[0];
14271                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14272                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14273                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14274                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14275                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14276                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14277                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14278                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14279                    sscanf(message, "hint: %c", &c)!=1 && 
14280                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14281                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14282                     print = (appData.engineComments >= 2);
14283                 }
14284                 message[0] = start; // restore original message
14285         }
14286         if(print) {
14287                 GetTimeMark(&now);
14288                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14289                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14290                         quote,
14291                         message);
14292         }
14293     }
14294
14295     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14296     if (appData.icsEngineAnalyze) {
14297         if (strstr(message, "whisper") != NULL ||
14298              strstr(message, "kibitz") != NULL ||
14299             strstr(message, "tellics") != NULL) return;
14300     }
14301
14302     HandleMachineMove(message, cps);
14303 }
14304
14305
14306 void
14307 SendTimeControl(cps, mps, tc, inc, sd, st)
14308      ChessProgramState *cps;
14309      int mps, inc, sd, st;
14310      long tc;
14311 {
14312     char buf[MSG_SIZ];
14313     int seconds;
14314
14315     if( timeControl_2 > 0 ) {
14316         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14317             tc = timeControl_2;
14318         }
14319     }
14320     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14321     inc /= cps->timeOdds;
14322     st  /= cps->timeOdds;
14323
14324     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14325
14326     if (st > 0) {
14327       /* Set exact time per move, normally using st command */
14328       if (cps->stKludge) {
14329         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14330         seconds = st % 60;
14331         if (seconds == 0) {
14332           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14333         } else {
14334           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14335         }
14336       } else {
14337         snprintf(buf, MSG_SIZ, "st %d\n", st);
14338       }
14339     } else {
14340       /* Set conventional or incremental time control, using level command */
14341       if (seconds == 0) {
14342         /* Note old gnuchess bug -- minutes:seconds used to not work.
14343            Fixed in later versions, but still avoid :seconds
14344            when seconds is 0. */
14345         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14346       } else {
14347         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14348                  seconds, inc/1000.);
14349       }
14350     }
14351     SendToProgram(buf, cps);
14352
14353     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14354     /* Orthogonally, limit search to given depth */
14355     if (sd > 0) {
14356       if (cps->sdKludge) {
14357         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14358       } else {
14359         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14360       }
14361       SendToProgram(buf, cps);
14362     }
14363
14364     if(cps->nps >= 0) { /* [HGM] nps */
14365         if(cps->supportsNPS == FALSE)
14366           cps->nps = -1; // don't use if engine explicitly says not supported!
14367         else {
14368           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14369           SendToProgram(buf, cps);
14370         }
14371     }
14372 }
14373
14374 ChessProgramState *WhitePlayer()
14375 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14376 {
14377     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14378        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14379         return &second;
14380     return &first;
14381 }
14382
14383 void
14384 SendTimeRemaining(cps, machineWhite)
14385      ChessProgramState *cps;
14386      int /*boolean*/ machineWhite;
14387 {
14388     char message[MSG_SIZ];
14389     long time, otime;
14390
14391     /* Note: this routine must be called when the clocks are stopped
14392        or when they have *just* been set or switched; otherwise
14393        it will be off by the time since the current tick started.
14394     */
14395     if (machineWhite) {
14396         time = whiteTimeRemaining / 10;
14397         otime = blackTimeRemaining / 10;
14398     } else {
14399         time = blackTimeRemaining / 10;
14400         otime = whiteTimeRemaining / 10;
14401     }
14402     /* [HGM] translate opponent's time by time-odds factor */
14403     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14404     if (appData.debugMode) {
14405         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14406     }
14407
14408     if (time <= 0) time = 1;
14409     if (otime <= 0) otime = 1;
14410
14411     snprintf(message, MSG_SIZ, "time %ld\n", time);
14412     SendToProgram(message, cps);
14413
14414     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14415     SendToProgram(message, cps);
14416 }
14417
14418 int
14419 BoolFeature(p, name, loc, cps)
14420      char **p;
14421      char *name;
14422      int *loc;
14423      ChessProgramState *cps;
14424 {
14425   char buf[MSG_SIZ];
14426   int len = strlen(name);
14427   int val;
14428
14429   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14430     (*p) += len + 1;
14431     sscanf(*p, "%d", &val);
14432     *loc = (val != 0);
14433     while (**p && **p != ' ')
14434       (*p)++;
14435     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14436     SendToProgram(buf, cps);
14437     return TRUE;
14438   }
14439   return FALSE;
14440 }
14441
14442 int
14443 IntFeature(p, name, loc, cps)
14444      char **p;
14445      char *name;
14446      int *loc;
14447      ChessProgramState *cps;
14448 {
14449   char buf[MSG_SIZ];
14450   int len = strlen(name);
14451   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14452     (*p) += len + 1;
14453     sscanf(*p, "%d", loc);
14454     while (**p && **p != ' ') (*p)++;
14455     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14456     SendToProgram(buf, cps);
14457     return TRUE;
14458   }
14459   return FALSE;
14460 }
14461
14462 int
14463 StringFeature(p, name, loc, cps)
14464      char **p;
14465      char *name;
14466      char loc[];
14467      ChessProgramState *cps;
14468 {
14469   char buf[MSG_SIZ];
14470   int len = strlen(name);
14471   if (strncmp((*p), name, len) == 0
14472       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14473     (*p) += len + 2;
14474     sscanf(*p, "%[^\"]", loc);
14475     while (**p && **p != '\"') (*p)++;
14476     if (**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 ParseOption(Option *opt, ChessProgramState *cps)
14486 // [HGM] options: process the string that defines an engine option, and determine
14487 // name, type, default value, and allowed value range
14488 {
14489         char *p, *q, buf[MSG_SIZ];
14490         int n, min = (-1)<<31, max = 1<<31, def;
14491
14492         if(p = strstr(opt->name, " -spin ")) {
14493             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14494             if(max < min) max = min; // enforce consistency
14495             if(def < min) def = min;
14496             if(def > max) def = max;
14497             opt->value = def;
14498             opt->min = min;
14499             opt->max = max;
14500             opt->type = Spin;
14501         } else if((p = strstr(opt->name, " -slider "))) {
14502             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14503             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14504             if(max < min) max = min; // enforce consistency
14505             if(def < min) def = min;
14506             if(def > max) def = max;
14507             opt->value = def;
14508             opt->min = min;
14509             opt->max = max;
14510             opt->type = Spin; // Slider;
14511         } else if((p = strstr(opt->name, " -string "))) {
14512             opt->textValue = p+9;
14513             opt->type = TextBox;
14514         } else if((p = strstr(opt->name, " -file "))) {
14515             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14516             opt->textValue = p+7;
14517             opt->type = FileName; // FileName;
14518         } else if((p = strstr(opt->name, " -path "))) {
14519             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14520             opt->textValue = p+7;
14521             opt->type = PathName; // PathName;
14522         } else if(p = strstr(opt->name, " -check ")) {
14523             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14524             opt->value = (def != 0);
14525             opt->type = CheckBox;
14526         } else if(p = strstr(opt->name, " -combo ")) {
14527             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14528             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14529             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14530             opt->value = n = 0;
14531             while(q = StrStr(q, " /// ")) {
14532                 n++; *q = 0;    // count choices, and null-terminate each of them
14533                 q += 5;
14534                 if(*q == '*') { // remember default, which is marked with * prefix
14535                     q++;
14536                     opt->value = n;
14537                 }
14538                 cps->comboList[cps->comboCnt++] = q;
14539             }
14540             cps->comboList[cps->comboCnt++] = NULL;
14541             opt->max = n + 1;
14542             opt->type = ComboBox;
14543         } else if(p = strstr(opt->name, " -button")) {
14544             opt->type = Button;
14545         } else if(p = strstr(opt->name, " -save")) {
14546             opt->type = SaveButton;
14547         } else return FALSE;
14548         *p = 0; // terminate option name
14549         // now look if the command-line options define a setting for this engine option.
14550         if(cps->optionSettings && cps->optionSettings[0])
14551             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14552         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14553           snprintf(buf, MSG_SIZ, "option %s", p);
14554                 if(p = strstr(buf, ",")) *p = 0;
14555                 if(q = strchr(buf, '=')) switch(opt->type) {
14556                     case ComboBox:
14557                         for(n=0; n<opt->max; n++)
14558                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14559                         break;
14560                     case TextBox:
14561                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14562                         break;
14563                     case Spin:
14564                     case CheckBox:
14565                         opt->value = atoi(q+1);
14566                     default:
14567                         break;
14568                 }
14569                 strcat(buf, "\n");
14570                 SendToProgram(buf, cps);
14571         }
14572         return TRUE;
14573 }
14574
14575 void
14576 FeatureDone(cps, val)
14577      ChessProgramState* cps;
14578      int val;
14579 {
14580   DelayedEventCallback cb = GetDelayedEvent();
14581   if ((cb == InitBackEnd3 && cps == &first) ||
14582       (cb == SettingsMenuIfReady && cps == &second) ||
14583       (cb == LoadEngine) ||
14584       (cb == TwoMachinesEventIfReady)) {
14585     CancelDelayedEvent();
14586     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14587   }
14588   cps->initDone = val;
14589 }
14590
14591 /* Parse feature command from engine */
14592 void
14593 ParseFeatures(args, cps)
14594      char* args;
14595      ChessProgramState *cps;
14596 {
14597   char *p = args;
14598   char *q;
14599   int val;
14600   char buf[MSG_SIZ];
14601
14602   for (;;) {
14603     while (*p == ' ') p++;
14604     if (*p == NULLCHAR) return;
14605
14606     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14607     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14608     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14609     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14610     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14611     if (BoolFeature(&p, "reuse", &val, cps)) {
14612       /* Engine can disable reuse, but can't enable it if user said no */
14613       if (!val) cps->reuse = FALSE;
14614       continue;
14615     }
14616     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14617     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14618       if (gameMode == TwoMachinesPlay) {
14619         DisplayTwoMachinesTitle();
14620       } else {
14621         DisplayTitle("");
14622       }
14623       continue;
14624     }
14625     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14626     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14627     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14628     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14629     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14630     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14631     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14632     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14633     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14634     if (IntFeature(&p, "done", &val, cps)) {
14635       FeatureDone(cps, val);
14636       continue;
14637     }
14638     /* Added by Tord: */
14639     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14640     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14641     /* End of additions by Tord */
14642
14643     /* [HGM] added features: */
14644     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14645     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14646     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14647     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14648     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14649     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14650     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14651         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14652           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14653             SendToProgram(buf, cps);
14654             continue;
14655         }
14656         if(cps->nrOptions >= MAX_OPTIONS) {
14657             cps->nrOptions--;
14658             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14659             DisplayError(buf, 0);
14660         }
14661         continue;
14662     }
14663     /* End of additions by HGM */
14664
14665     /* unknown feature: complain and skip */
14666     q = p;
14667     while (*q && *q != '=') q++;
14668     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14669     SendToProgram(buf, cps);
14670     p = q;
14671     if (*p == '=') {
14672       p++;
14673       if (*p == '\"') {
14674         p++;
14675         while (*p && *p != '\"') p++;
14676         if (*p == '\"') p++;
14677       } else {
14678         while (*p && *p != ' ') p++;
14679       }
14680     }
14681   }
14682
14683 }
14684
14685 void
14686 PeriodicUpdatesEvent(newState)
14687      int newState;
14688 {
14689     if (newState == appData.periodicUpdates)
14690       return;
14691
14692     appData.periodicUpdates=newState;
14693
14694     /* Display type changes, so update it now */
14695 //    DisplayAnalysis();
14696
14697     /* Get the ball rolling again... */
14698     if (newState) {
14699         AnalysisPeriodicEvent(1);
14700         StartAnalysisClock();
14701     }
14702 }
14703
14704 void
14705 PonderNextMoveEvent(newState)
14706      int newState;
14707 {
14708     if (newState == appData.ponderNextMove) return;
14709     if (gameMode == EditPosition) EditPositionDone(TRUE);
14710     if (newState) {
14711         SendToProgram("hard\n", &first);
14712         if (gameMode == TwoMachinesPlay) {
14713             SendToProgram("hard\n", &second);
14714         }
14715     } else {
14716         SendToProgram("easy\n", &first);
14717         thinkOutput[0] = NULLCHAR;
14718         if (gameMode == TwoMachinesPlay) {
14719             SendToProgram("easy\n", &second);
14720         }
14721     }
14722     appData.ponderNextMove = newState;
14723 }
14724
14725 void
14726 NewSettingEvent(option, feature, command, value)
14727      char *command;
14728      int option, value, *feature;
14729 {
14730     char buf[MSG_SIZ];
14731
14732     if (gameMode == EditPosition) EditPositionDone(TRUE);
14733     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14734     if(feature == NULL || *feature) SendToProgram(buf, &first);
14735     if (gameMode == TwoMachinesPlay) {
14736         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14737     }
14738 }
14739
14740 void
14741 ShowThinkingEvent()
14742 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14743 {
14744     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14745     int newState = appData.showThinking
14746         // [HGM] thinking: other features now need thinking output as well
14747         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14748
14749     if (oldState == newState) return;
14750     oldState = newState;
14751     if (gameMode == EditPosition) EditPositionDone(TRUE);
14752     if (oldState) {
14753         SendToProgram("post\n", &first);
14754         if (gameMode == TwoMachinesPlay) {
14755             SendToProgram("post\n", &second);
14756         }
14757     } else {
14758         SendToProgram("nopost\n", &first);
14759         thinkOutput[0] = NULLCHAR;
14760         if (gameMode == TwoMachinesPlay) {
14761             SendToProgram("nopost\n", &second);
14762         }
14763     }
14764 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14765 }
14766
14767 void
14768 AskQuestionEvent(title, question, replyPrefix, which)
14769      char *title; char *question; char *replyPrefix; char *which;
14770 {
14771   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14772   if (pr == NoProc) return;
14773   AskQuestion(title, question, replyPrefix, pr);
14774 }
14775
14776 void
14777 TypeInEvent(char firstChar)
14778 {
14779     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14780         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14781         gameMode == AnalyzeMode || gameMode == EditGame || \r
14782         gameMode == EditPosition || gameMode == IcsExamining ||\r
14783         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14784         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14785                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14786                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14787         gameMode == Training) PopUpMoveDialog(firstChar);
14788 }
14789
14790 void
14791 TypeInDoneEvent(char *move)
14792 {
14793         Board board;
14794         int n, fromX, fromY, toX, toY;
14795         char promoChar;
14796         ChessMove moveType;\r
14797
14798         // [HGM] FENedit\r
14799         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14800                 EditPositionPasteFEN(move);\r
14801                 return;\r
14802         }\r
14803         // [HGM] movenum: allow move number to be typed in any mode\r
14804         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14805           ToNrEvent(2*n-1);\r
14806           return;\r
14807         }\r
14808
14809       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14810         gameMode != Training) {\r
14811         DisplayMoveError(_("Displayed move is not current"));\r
14812       } else {\r
14813         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14814           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14815         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14816         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14817           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14818           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14819         } else {\r
14820           DisplayMoveError(_("Could not parse move"));\r
14821         }
14822       }\r
14823 }\r
14824
14825 void
14826 DisplayMove(moveNumber)
14827      int moveNumber;
14828 {
14829     char message[MSG_SIZ];
14830     char res[MSG_SIZ];
14831     char cpThinkOutput[MSG_SIZ];
14832
14833     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14834
14835     if (moveNumber == forwardMostMove - 1 ||
14836         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14837
14838         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14839
14840         if (strchr(cpThinkOutput, '\n')) {
14841             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14842         }
14843     } else {
14844         *cpThinkOutput = NULLCHAR;
14845     }
14846
14847     /* [AS] Hide thinking from human user */
14848     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14849         *cpThinkOutput = NULLCHAR;
14850         if( thinkOutput[0] != NULLCHAR ) {
14851             int i;
14852
14853             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14854                 cpThinkOutput[i] = '.';
14855             }
14856             cpThinkOutput[i] = NULLCHAR;
14857             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14858         }
14859     }
14860
14861     if (moveNumber == forwardMostMove - 1 &&
14862         gameInfo.resultDetails != NULL) {
14863         if (gameInfo.resultDetails[0] == NULLCHAR) {
14864           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14865         } else {
14866           snprintf(res, MSG_SIZ, " {%s} %s",
14867                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14868         }
14869     } else {
14870         res[0] = NULLCHAR;
14871     }
14872
14873     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14874         DisplayMessage(res, cpThinkOutput);
14875     } else {
14876       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14877                 WhiteOnMove(moveNumber) ? " " : ".. ",
14878                 parseList[moveNumber], res);
14879         DisplayMessage(message, cpThinkOutput);
14880     }
14881 }
14882
14883 void
14884 DisplayComment(moveNumber, text)
14885      int moveNumber;
14886      char *text;
14887 {
14888     char title[MSG_SIZ];
14889     char buf[8000]; // comment can be long!
14890     int score, depth;
14891
14892     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14893       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14894     } else {
14895       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14896               WhiteOnMove(moveNumber) ? " " : ".. ",
14897               parseList[moveNumber]);
14898     }
14899     // [HGM] PV info: display PV info together with (or as) comment
14900     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14901       if(text == NULL) text = "";
14902       score = pvInfoList[moveNumber].score;
14903       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14904               depth, (pvInfoList[moveNumber].time+50)/100, text);
14905       text = buf;
14906     }
14907     if (text != NULL && (appData.autoDisplayComment || commentUp))
14908         CommentPopUp(title, text);
14909 }
14910
14911 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14912  * might be busy thinking or pondering.  It can be omitted if your
14913  * gnuchess is configured to stop thinking immediately on any user
14914  * input.  However, that gnuchess feature depends on the FIONREAD
14915  * ioctl, which does not work properly on some flavors of Unix.
14916  */
14917 void
14918 Attention(cps)
14919      ChessProgramState *cps;
14920 {
14921 #if ATTENTION
14922     if (!cps->useSigint) return;
14923     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14924     switch (gameMode) {
14925       case MachinePlaysWhite:
14926       case MachinePlaysBlack:
14927       case TwoMachinesPlay:
14928       case IcsPlayingWhite:
14929       case IcsPlayingBlack:
14930       case AnalyzeMode:
14931       case AnalyzeFile:
14932         /* Skip if we know it isn't thinking */
14933         if (!cps->maybeThinking) return;
14934         if (appData.debugMode)
14935           fprintf(debugFP, "Interrupting %s\n", cps->which);
14936         InterruptChildProcess(cps->pr);
14937         cps->maybeThinking = FALSE;
14938         break;
14939       default:
14940         break;
14941     }
14942 #endif /*ATTENTION*/
14943 }
14944
14945 int
14946 CheckFlags()
14947 {
14948     if (whiteTimeRemaining <= 0) {
14949         if (!whiteFlag) {
14950             whiteFlag = TRUE;
14951             if (appData.icsActive) {
14952                 if (appData.autoCallFlag &&
14953                     gameMode == IcsPlayingBlack && !blackFlag) {
14954                   SendToICS(ics_prefix);
14955                   SendToICS("flag\n");
14956                 }
14957             } else {
14958                 if (blackFlag) {
14959                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14960                 } else {
14961                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14962                     if (appData.autoCallFlag) {
14963                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14964                         return TRUE;
14965                     }
14966                 }
14967             }
14968         }
14969     }
14970     if (blackTimeRemaining <= 0) {
14971         if (!blackFlag) {
14972             blackFlag = TRUE;
14973             if (appData.icsActive) {
14974                 if (appData.autoCallFlag &&
14975                     gameMode == IcsPlayingWhite && !whiteFlag) {
14976                   SendToICS(ics_prefix);
14977                   SendToICS("flag\n");
14978                 }
14979             } else {
14980                 if (whiteFlag) {
14981                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14982                 } else {
14983                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14984                     if (appData.autoCallFlag) {
14985                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14986                         return TRUE;
14987                     }
14988                 }
14989             }
14990         }
14991     }
14992     return FALSE;
14993 }
14994
14995 void
14996 CheckTimeControl()
14997 {
14998     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14999         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15000
15001     /*
15002      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15003      */
15004     if ( !WhiteOnMove(forwardMostMove) ) {
15005         /* White made time control */
15006         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15007         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15008         /* [HGM] time odds: correct new time quota for time odds! */
15009                                             / WhitePlayer()->timeOdds;
15010         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15011     } else {
15012         lastBlack -= blackTimeRemaining;
15013         /* Black made time control */
15014         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15015                                             / WhitePlayer()->other->timeOdds;
15016         lastWhite = whiteTimeRemaining;
15017     }
15018 }
15019
15020 void
15021 DisplayBothClocks()
15022 {
15023     int wom = gameMode == EditPosition ?
15024       !blackPlaysFirst : WhiteOnMove(currentMove);
15025     DisplayWhiteClock(whiteTimeRemaining, wom);
15026     DisplayBlackClock(blackTimeRemaining, !wom);
15027 }
15028
15029
15030 /* Timekeeping seems to be a portability nightmare.  I think everyone
15031    has ftime(), but I'm really not sure, so I'm including some ifdefs
15032    to use other calls if you don't.  Clocks will be less accurate if
15033    you have neither ftime nor gettimeofday.
15034 */
15035
15036 /* VS 2008 requires the #include outside of the function */
15037 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15038 #include <sys/timeb.h>
15039 #endif
15040
15041 /* Get the current time as a TimeMark */
15042 void
15043 GetTimeMark(tm)
15044      TimeMark *tm;
15045 {
15046 #if HAVE_GETTIMEOFDAY
15047
15048     struct timeval timeVal;
15049     struct timezone timeZone;
15050
15051     gettimeofday(&timeVal, &timeZone);
15052     tm->sec = (long) timeVal.tv_sec;
15053     tm->ms = (int) (timeVal.tv_usec / 1000L);
15054
15055 #else /*!HAVE_GETTIMEOFDAY*/
15056 #if HAVE_FTIME
15057
15058 // include <sys/timeb.h> / moved to just above start of function
15059     struct timeb timeB;
15060
15061     ftime(&timeB);
15062     tm->sec = (long) timeB.time;
15063     tm->ms = (int) timeB.millitm;
15064
15065 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15066     tm->sec = (long) time(NULL);
15067     tm->ms = 0;
15068 #endif
15069 #endif
15070 }
15071
15072 /* Return the difference in milliseconds between two
15073    time marks.  We assume the difference will fit in a long!
15074 */
15075 long
15076 SubtractTimeMarks(tm2, tm1)
15077      TimeMark *tm2, *tm1;
15078 {
15079     return 1000L*(tm2->sec - tm1->sec) +
15080            (long) (tm2->ms - tm1->ms);
15081 }
15082
15083
15084 /*
15085  * Code to manage the game clocks.
15086  *
15087  * In tournament play, black starts the clock and then white makes a move.
15088  * We give the human user a slight advantage if he is playing white---the
15089  * clocks don't run until he makes his first move, so it takes zero time.
15090  * Also, we don't account for network lag, so we could get out of sync
15091  * with GNU Chess's clock -- but then, referees are always right.
15092  */
15093
15094 static TimeMark tickStartTM;
15095 static long intendedTickLength;
15096
15097 long
15098 NextTickLength(timeRemaining)
15099      long timeRemaining;
15100 {
15101     long nominalTickLength, nextTickLength;
15102
15103     if (timeRemaining > 0L && timeRemaining <= 10000L)
15104       nominalTickLength = 100L;
15105     else
15106       nominalTickLength = 1000L;
15107     nextTickLength = timeRemaining % nominalTickLength;
15108     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15109
15110     return nextTickLength;
15111 }
15112
15113 /* Adjust clock one minute up or down */
15114 void
15115 AdjustClock(Boolean which, int dir)
15116 {
15117     if(which) blackTimeRemaining += 60000*dir;
15118     else      whiteTimeRemaining += 60000*dir;
15119     DisplayBothClocks();
15120 }
15121
15122 /* Stop clocks and reset to a fresh time control */
15123 void
15124 ResetClocks()
15125 {
15126     (void) StopClockTimer();
15127     if (appData.icsActive) {
15128         whiteTimeRemaining = blackTimeRemaining = 0;
15129     } else if (searchTime) {
15130         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15131         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15132     } else { /* [HGM] correct new time quote for time odds */
15133         whiteTC = blackTC = fullTimeControlString;
15134         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15135         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15136     }
15137     if (whiteFlag || blackFlag) {
15138         DisplayTitle("");
15139         whiteFlag = blackFlag = FALSE;
15140     }
15141     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15142     DisplayBothClocks();
15143 }
15144
15145 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15146
15147 /* Decrement running clock by amount of time that has passed */
15148 void
15149 DecrementClocks()
15150 {
15151     long timeRemaining;
15152     long lastTickLength, fudge;
15153     TimeMark now;
15154
15155     if (!appData.clockMode) return;
15156     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15157
15158     GetTimeMark(&now);
15159
15160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15161
15162     /* Fudge if we woke up a little too soon */
15163     fudge = intendedTickLength - lastTickLength;
15164     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15165
15166     if (WhiteOnMove(forwardMostMove)) {
15167         if(whiteNPS >= 0) lastTickLength = 0;
15168         timeRemaining = whiteTimeRemaining -= lastTickLength;
15169         if(timeRemaining < 0 && !appData.icsActive) {
15170             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15171             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15172                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15173                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15174             }
15175         }
15176         DisplayWhiteClock(whiteTimeRemaining - fudge,
15177                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15178     } else {
15179         if(blackNPS >= 0) lastTickLength = 0;
15180         timeRemaining = blackTimeRemaining -= lastTickLength;
15181         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15182             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15183             if(suddenDeath) {
15184                 blackStartMove = forwardMostMove;
15185                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15186             }
15187         }
15188         DisplayBlackClock(blackTimeRemaining - fudge,
15189                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15190     }
15191     if (CheckFlags()) return;
15192
15193     tickStartTM = now;
15194     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15195     StartClockTimer(intendedTickLength);
15196
15197     /* if the time remaining has fallen below the alarm threshold, sound the
15198      * alarm. if the alarm has sounded and (due to a takeback or time control
15199      * with increment) the time remaining has increased to a level above the
15200      * threshold, reset the alarm so it can sound again.
15201      */
15202
15203     if (appData.icsActive && appData.icsAlarm) {
15204
15205         /* make sure we are dealing with the user's clock */
15206         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15207                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15208            )) return;
15209
15210         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15211             alarmSounded = FALSE;
15212         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15213             PlayAlarmSound();
15214             alarmSounded = TRUE;
15215         }
15216     }
15217 }
15218
15219
15220 /* A player has just moved, so stop the previously running
15221    clock and (if in clock mode) start the other one.
15222    We redisplay both clocks in case we're in ICS mode, because
15223    ICS gives us an update to both clocks after every move.
15224    Note that this routine is called *after* forwardMostMove
15225    is updated, so the last fractional tick must be subtracted
15226    from the color that is *not* on move now.
15227 */
15228 void
15229 SwitchClocks(int newMoveNr)
15230 {
15231     long lastTickLength;
15232     TimeMark now;
15233     int flagged = FALSE;
15234
15235     GetTimeMark(&now);
15236
15237     if (StopClockTimer() && appData.clockMode) {
15238         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15239         if (!WhiteOnMove(forwardMostMove)) {
15240             if(blackNPS >= 0) lastTickLength = 0;
15241             blackTimeRemaining -= lastTickLength;
15242            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15243 //         if(pvInfoList[forwardMostMove].time == -1)
15244                  pvInfoList[forwardMostMove].time =               // use GUI time
15245                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15246         } else {
15247            if(whiteNPS >= 0) lastTickLength = 0;
15248            whiteTimeRemaining -= lastTickLength;
15249            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15250 //         if(pvInfoList[forwardMostMove].time == -1)
15251                  pvInfoList[forwardMostMove].time =
15252                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15253         }
15254         flagged = CheckFlags();
15255     }
15256     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15257     CheckTimeControl();
15258
15259     if (flagged || !appData.clockMode) return;
15260
15261     switch (gameMode) {
15262       case MachinePlaysBlack:
15263       case MachinePlaysWhite:
15264       case BeginningOfGame:
15265         if (pausing) return;
15266         break;
15267
15268       case EditGame:
15269       case PlayFromGameFile:
15270       case IcsExamining:
15271         return;
15272
15273       default:
15274         break;
15275     }
15276
15277     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15278         if(WhiteOnMove(forwardMostMove))
15279              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15280         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15281     }
15282
15283     tickStartTM = now;
15284     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15285       whiteTimeRemaining : blackTimeRemaining);
15286     StartClockTimer(intendedTickLength);
15287 }
15288
15289
15290 /* Stop both clocks */
15291 void
15292 StopClocks()
15293 {
15294     long lastTickLength;
15295     TimeMark now;
15296
15297     if (!StopClockTimer()) return;
15298     if (!appData.clockMode) return;
15299
15300     GetTimeMark(&now);
15301
15302     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15303     if (WhiteOnMove(forwardMostMove)) {
15304         if(whiteNPS >= 0) lastTickLength = 0;
15305         whiteTimeRemaining -= lastTickLength;
15306         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15307     } else {
15308         if(blackNPS >= 0) lastTickLength = 0;
15309         blackTimeRemaining -= lastTickLength;
15310         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15311     }
15312     CheckFlags();
15313 }
15314
15315 /* Start clock of player on move.  Time may have been reset, so
15316    if clock is already running, stop and restart it. */
15317 void
15318 StartClocks()
15319 {
15320     (void) StopClockTimer(); /* in case it was running already */
15321     DisplayBothClocks();
15322     if (CheckFlags()) return;
15323
15324     if (!appData.clockMode) return;
15325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15326
15327     GetTimeMark(&tickStartTM);
15328     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15329       whiteTimeRemaining : blackTimeRemaining);
15330
15331    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15332     whiteNPS = blackNPS = -1;
15333     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15334        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15335         whiteNPS = first.nps;
15336     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15337        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15338         blackNPS = first.nps;
15339     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15340         whiteNPS = second.nps;
15341     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15342         blackNPS = second.nps;
15343     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15344
15345     StartClockTimer(intendedTickLength);
15346 }
15347
15348 char *
15349 TimeString(ms)
15350      long ms;
15351 {
15352     long second, minute, hour, day;
15353     char *sign = "";
15354     static char buf[32];
15355
15356     if (ms > 0 && ms <= 9900) {
15357       /* convert milliseconds to tenths, rounding up */
15358       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15359
15360       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15361       return buf;
15362     }
15363
15364     /* convert milliseconds to seconds, rounding up */
15365     /* use floating point to avoid strangeness of integer division
15366        with negative dividends on many machines */
15367     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15368
15369     if (second < 0) {
15370         sign = "-";
15371         second = -second;
15372     }
15373
15374     day = second / (60 * 60 * 24);
15375     second = second % (60 * 60 * 24);
15376     hour = second / (60 * 60);
15377     second = second % (60 * 60);
15378     minute = second / 60;
15379     second = second % 60;
15380
15381     if (day > 0)
15382       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15383               sign, day, hour, minute, second);
15384     else if (hour > 0)
15385       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15386     else
15387       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15388
15389     return buf;
15390 }
15391
15392
15393 /*
15394  * This is necessary because some C libraries aren't ANSI C compliant yet.
15395  */
15396 char *
15397 StrStr(string, match)
15398      char *string, *match;
15399 {
15400     int i, length;
15401
15402     length = strlen(match);
15403
15404     for (i = strlen(string) - length; i >= 0; i--, string++)
15405       if (!strncmp(match, string, length))
15406         return string;
15407
15408     return NULL;
15409 }
15410
15411 char *
15412 StrCaseStr(string, match)
15413      char *string, *match;
15414 {
15415     int i, j, length;
15416
15417     length = strlen(match);
15418
15419     for (i = strlen(string) - length; i >= 0; i--, string++) {
15420         for (j = 0; j < length; j++) {
15421             if (ToLower(match[j]) != ToLower(string[j]))
15422               break;
15423         }
15424         if (j == length) return string;
15425     }
15426
15427     return NULL;
15428 }
15429
15430 #ifndef _amigados
15431 int
15432 StrCaseCmp(s1, s2)
15433      char *s1, *s2;
15434 {
15435     char c1, c2;
15436
15437     for (;;) {
15438         c1 = ToLower(*s1++);
15439         c2 = ToLower(*s2++);
15440         if (c1 > c2) return 1;
15441         if (c1 < c2) return -1;
15442         if (c1 == NULLCHAR) return 0;
15443     }
15444 }
15445
15446
15447 int
15448 ToLower(c)
15449      int c;
15450 {
15451     return isupper(c) ? tolower(c) : c;
15452 }
15453
15454
15455 int
15456 ToUpper(c)
15457      int c;
15458 {
15459     return islower(c) ? toupper(c) : c;
15460 }
15461 #endif /* !_amigados    */
15462
15463 char *
15464 StrSave(s)
15465      char *s;
15466 {
15467   char *ret;
15468
15469   if ((ret = (char *) malloc(strlen(s) + 1)))
15470     {
15471       safeStrCpy(ret, s, strlen(s)+1);
15472     }
15473   return ret;
15474 }
15475
15476 char *
15477 StrSavePtr(s, savePtr)
15478      char *s, **savePtr;
15479 {
15480     if (*savePtr) {
15481         free(*savePtr);
15482     }
15483     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15484       safeStrCpy(*savePtr, s, strlen(s)+1);
15485     }
15486     return(*savePtr);
15487 }
15488
15489 char *
15490 PGNDate()
15491 {
15492     time_t clock;
15493     struct tm *tm;
15494     char buf[MSG_SIZ];
15495
15496     clock = time((time_t *)NULL);
15497     tm = localtime(&clock);
15498     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15499             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15500     return StrSave(buf);
15501 }
15502
15503
15504 char *
15505 PositionToFEN(move, overrideCastling)
15506      int move;
15507      char *overrideCastling;
15508 {
15509     int i, j, fromX, fromY, toX, toY;
15510     int whiteToPlay;
15511     char buf[128];
15512     char *p, *q;
15513     int emptycount;
15514     ChessSquare piece;
15515
15516     whiteToPlay = (gameMode == EditPosition) ?
15517       !blackPlaysFirst : (move % 2 == 0);
15518     p = buf;
15519
15520     /* Piece placement data */
15521     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15522         emptycount = 0;
15523         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15524             if (boards[move][i][j] == EmptySquare) {
15525                 emptycount++;
15526             } else { ChessSquare piece = boards[move][i][j];
15527                 if (emptycount > 0) {
15528                     if(emptycount<10) /* [HGM] can be >= 10 */
15529                         *p++ = '0' + emptycount;
15530                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15531                     emptycount = 0;
15532                 }
15533                 if(PieceToChar(piece) == '+') {
15534                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15535                     *p++ = '+';
15536                     piece = (ChessSquare)(DEMOTED piece);
15537                 }
15538                 *p++ = PieceToChar(piece);
15539                 if(p[-1] == '~') {
15540                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15541                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15542                     *p++ = '~';
15543                 }
15544             }
15545         }
15546         if (emptycount > 0) {
15547             if(emptycount<10) /* [HGM] can be >= 10 */
15548                 *p++ = '0' + emptycount;
15549             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15550             emptycount = 0;
15551         }
15552         *p++ = '/';
15553     }
15554     *(p - 1) = ' ';
15555
15556     /* [HGM] print Crazyhouse or Shogi holdings */
15557     if( gameInfo.holdingsWidth ) {
15558         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15559         q = p;
15560         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15561             piece = boards[move][i][BOARD_WIDTH-1];
15562             if( piece != EmptySquare )
15563               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15564                   *p++ = PieceToChar(piece);
15565         }
15566         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15567             piece = boards[move][BOARD_HEIGHT-i-1][0];
15568             if( piece != EmptySquare )
15569               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15570                   *p++ = PieceToChar(piece);
15571         }
15572
15573         if( q == p ) *p++ = '-';
15574         *p++ = ']';
15575         *p++ = ' ';
15576     }
15577
15578     /* Active color */
15579     *p++ = whiteToPlay ? 'w' : 'b';
15580     *p++ = ' ';
15581
15582   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15583     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15584   } else {
15585   if(nrCastlingRights) {
15586      q = p;
15587      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15588        /* [HGM] write directly from rights */
15589            if(boards[move][CASTLING][2] != NoRights &&
15590               boards[move][CASTLING][0] != NoRights   )
15591                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15592            if(boards[move][CASTLING][2] != NoRights &&
15593               boards[move][CASTLING][1] != NoRights   )
15594                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15595            if(boards[move][CASTLING][5] != NoRights &&
15596               boards[move][CASTLING][3] != NoRights   )
15597                 *p++ = boards[move][CASTLING][3] + AAA;
15598            if(boards[move][CASTLING][5] != NoRights &&
15599               boards[move][CASTLING][4] != NoRights   )
15600                 *p++ = boards[move][CASTLING][4] + AAA;
15601      } else {
15602
15603         /* [HGM] write true castling rights */
15604         if( nrCastlingRights == 6 ) {
15605             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15606                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15607             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15608                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15609             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15610                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15611             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15612                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15613         }
15614      }
15615      if (q == p) *p++ = '-'; /* No castling rights */
15616      *p++ = ' ';
15617   }
15618
15619   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15620      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15621     /* En passant target square */
15622     if (move > backwardMostMove) {
15623         fromX = moveList[move - 1][0] - AAA;
15624         fromY = moveList[move - 1][1] - ONE;
15625         toX = moveList[move - 1][2] - AAA;
15626         toY = moveList[move - 1][3] - ONE;
15627         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15628             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15629             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15630             fromX == toX) {
15631             /* 2-square pawn move just happened */
15632             *p++ = toX + AAA;
15633             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15634         } else {
15635             *p++ = '-';
15636         }
15637     } else if(move == backwardMostMove) {
15638         // [HGM] perhaps we should always do it like this, and forget the above?
15639         if((signed char)boards[move][EP_STATUS] >= 0) {
15640             *p++ = boards[move][EP_STATUS] + AAA;
15641             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15642         } else {
15643             *p++ = '-';
15644         }
15645     } else {
15646         *p++ = '-';
15647     }
15648     *p++ = ' ';
15649   }
15650   }
15651
15652     /* [HGM] find reversible plies */
15653     {   int i = 0, j=move;
15654
15655         if (appData.debugMode) { int k;
15656             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15657             for(k=backwardMostMove; k<=forwardMostMove; k++)
15658                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15659
15660         }
15661
15662         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15663         if( j == backwardMostMove ) i += initialRulePlies;
15664         sprintf(p, "%d ", i);
15665         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15666     }
15667     /* Fullmove number */
15668     sprintf(p, "%d", (move / 2) + 1);
15669
15670     return StrSave(buf);
15671 }
15672
15673 Boolean
15674 ParseFEN(board, blackPlaysFirst, fen)
15675     Board board;
15676      int *blackPlaysFirst;
15677      char *fen;
15678 {
15679     int i, j;
15680     char *p, c;
15681     int emptycount;
15682     ChessSquare piece;
15683
15684     p = fen;
15685
15686     /* [HGM] by default clear Crazyhouse holdings, if present */
15687     if(gameInfo.holdingsWidth) {
15688        for(i=0; i<BOARD_HEIGHT; i++) {
15689            board[i][0]             = EmptySquare; /* black holdings */
15690            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15691            board[i][1]             = (ChessSquare) 0; /* black counts */
15692            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15693        }
15694     }
15695
15696     /* Piece placement data */
15697     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15698         j = 0;
15699         for (;;) {
15700             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15701                 if (*p == '/') p++;
15702                 emptycount = gameInfo.boardWidth - j;
15703                 while (emptycount--)
15704                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15705                 break;
15706 #if(BOARD_FILES >= 10)
15707             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15708                 p++; emptycount=10;
15709                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15710                 while (emptycount--)
15711                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15712 #endif
15713             } else if (isdigit(*p)) {
15714                 emptycount = *p++ - '0';
15715                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15716                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15717                 while (emptycount--)
15718                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15719             } else if (*p == '+' || isalpha(*p)) {
15720                 if (j >= gameInfo.boardWidth) return FALSE;
15721                 if(*p=='+') {
15722                     piece = CharToPiece(*++p);
15723                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15724                     piece = (ChessSquare) (PROMOTED piece ); p++;
15725                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15726                 } else piece = CharToPiece(*p++);
15727
15728                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15729                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15730                     piece = (ChessSquare) (PROMOTED piece);
15731                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15732                     p++;
15733                 }
15734                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15735             } else {
15736                 return FALSE;
15737             }
15738         }
15739     }
15740     while (*p == '/' || *p == ' ') p++;
15741
15742     /* [HGM] look for Crazyhouse holdings here */
15743     while(*p==' ') p++;
15744     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15745         if(*p == '[') p++;
15746         if(*p == '-' ) p++; /* empty holdings */ else {
15747             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15748             /* if we would allow FEN reading to set board size, we would   */
15749             /* have to add holdings and shift the board read so far here   */
15750             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15751                 p++;
15752                 if((int) piece >= (int) BlackPawn ) {
15753                     i = (int)piece - (int)BlackPawn;
15754                     i = PieceToNumber((ChessSquare)i);
15755                     if( i >= gameInfo.holdingsSize ) return FALSE;
15756                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15757                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15758                 } else {
15759                     i = (int)piece - (int)WhitePawn;
15760                     i = PieceToNumber((ChessSquare)i);
15761                     if( i >= gameInfo.holdingsSize ) return FALSE;
15762                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15763                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15764                 }
15765             }
15766         }
15767         if(*p == ']') p++;
15768     }
15769
15770     while(*p == ' ') p++;
15771
15772     /* Active color */
15773     c = *p++;
15774     if(appData.colorNickNames) {
15775       if( c == appData.colorNickNames[0] ) c = 'w'; else
15776       if( c == appData.colorNickNames[1] ) c = 'b';
15777     }
15778     switch (c) {
15779       case 'w':
15780         *blackPlaysFirst = FALSE;
15781         break;
15782       case 'b':
15783         *blackPlaysFirst = TRUE;
15784         break;
15785       default:
15786         return FALSE;
15787     }
15788
15789     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15790     /* return the extra info in global variiables             */
15791
15792     /* set defaults in case FEN is incomplete */
15793     board[EP_STATUS] = EP_UNKNOWN;
15794     for(i=0; i<nrCastlingRights; i++ ) {
15795         board[CASTLING][i] =
15796             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15797     }   /* assume possible unless obviously impossible */
15798     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15799     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15800     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15801                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15802     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15803     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15804     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15805                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15806     FENrulePlies = 0;
15807
15808     while(*p==' ') p++;
15809     if(nrCastlingRights) {
15810       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15811           /* castling indicator present, so default becomes no castlings */
15812           for(i=0; i<nrCastlingRights; i++ ) {
15813                  board[CASTLING][i] = NoRights;
15814           }
15815       }
15816       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15817              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15818              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15819              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15820         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15821
15822         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15823             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15824             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15825         }
15826         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15827             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15828         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15829                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15830         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15831                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15832         switch(c) {
15833           case'K':
15834               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15835               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15836               board[CASTLING][2] = whiteKingFile;
15837               break;
15838           case'Q':
15839               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15840               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15841               board[CASTLING][2] = whiteKingFile;
15842               break;
15843           case'k':
15844               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15845               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15846               board[CASTLING][5] = blackKingFile;
15847               break;
15848           case'q':
15849               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15850               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15851               board[CASTLING][5] = blackKingFile;
15852           case '-':
15853               break;
15854           default: /* FRC castlings */
15855               if(c >= 'a') { /* black rights */
15856                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15857                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15858                   if(i == BOARD_RGHT) break;
15859                   board[CASTLING][5] = i;
15860                   c -= AAA;
15861                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15862                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15863                   if(c > i)
15864                       board[CASTLING][3] = c;
15865                   else
15866                       board[CASTLING][4] = c;
15867               } else { /* white rights */
15868                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15869                     if(board[0][i] == WhiteKing) break;
15870                   if(i == BOARD_RGHT) break;
15871                   board[CASTLING][2] = i;
15872                   c -= AAA - 'a' + 'A';
15873                   if(board[0][c] >= WhiteKing) break;
15874                   if(c > i)
15875                       board[CASTLING][0] = c;
15876                   else
15877                       board[CASTLING][1] = c;
15878               }
15879         }
15880       }
15881       for(i=0; i<nrCastlingRights; i++)
15882         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15883     if (appData.debugMode) {
15884         fprintf(debugFP, "FEN castling rights:");
15885         for(i=0; i<nrCastlingRights; i++)
15886         fprintf(debugFP, " %d", board[CASTLING][i]);
15887         fprintf(debugFP, "\n");
15888     }
15889
15890       while(*p==' ') p++;
15891     }
15892
15893     /* read e.p. field in games that know e.p. capture */
15894     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15895        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15896       if(*p=='-') {
15897         p++; board[EP_STATUS] = EP_NONE;
15898       } else {
15899          char c = *p++ - AAA;
15900
15901          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15902          if(*p >= '0' && *p <='9') p++;
15903          board[EP_STATUS] = c;
15904       }
15905     }
15906
15907
15908     if(sscanf(p, "%d", &i) == 1) {
15909         FENrulePlies = i; /* 50-move ply counter */
15910         /* (The move number is still ignored)    */
15911     }
15912
15913     return TRUE;
15914 }
15915
15916 void
15917 EditPositionPasteFEN(char *fen)
15918 {
15919   if (fen != NULL) {
15920     Board initial_position;
15921
15922     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15923       DisplayError(_("Bad FEN position in clipboard"), 0);
15924       return ;
15925     } else {
15926       int savedBlackPlaysFirst = blackPlaysFirst;
15927       EditPositionEvent();
15928       blackPlaysFirst = savedBlackPlaysFirst;
15929       CopyBoard(boards[0], initial_position);
15930       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15931       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15932       DisplayBothClocks();
15933       DrawPosition(FALSE, boards[currentMove]);
15934     }
15935   }
15936 }
15937
15938 static char cseq[12] = "\\   ";
15939
15940 Boolean set_cont_sequence(char *new_seq)
15941 {
15942     int len;
15943     Boolean ret;
15944
15945     // handle bad attempts to set the sequence
15946         if (!new_seq)
15947                 return 0; // acceptable error - no debug
15948
15949     len = strlen(new_seq);
15950     ret = (len > 0) && (len < sizeof(cseq));
15951     if (ret)
15952       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15953     else if (appData.debugMode)
15954       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15955     return ret;
15956 }
15957
15958 /*
15959     reformat a source message so words don't cross the width boundary.  internal
15960     newlines are not removed.  returns the wrapped size (no null character unless
15961     included in source message).  If dest is NULL, only calculate the size required
15962     for the dest buffer.  lp argument indicats line position upon entry, and it's
15963     passed back upon exit.
15964 */
15965 int wrap(char *dest, char *src, int count, int width, int *lp)
15966 {
15967     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15968
15969     cseq_len = strlen(cseq);
15970     old_line = line = *lp;
15971     ansi = len = clen = 0;
15972
15973     for (i=0; i < count; i++)
15974     {
15975         if (src[i] == '\033')
15976             ansi = 1;
15977
15978         // if we hit the width, back up
15979         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15980         {
15981             // store i & len in case the word is too long
15982             old_i = i, old_len = len;
15983
15984             // find the end of the last word
15985             while (i && src[i] != ' ' && src[i] != '\n')
15986             {
15987                 i--;
15988                 len--;
15989             }
15990
15991             // word too long?  restore i & len before splitting it
15992             if ((old_i-i+clen) >= width)
15993             {
15994                 i = old_i;
15995                 len = old_len;
15996             }
15997
15998             // extra space?
15999             if (i && src[i-1] == ' ')
16000                 len--;
16001
16002             if (src[i] != ' ' && src[i] != '\n')
16003             {
16004                 i--;
16005                 if (len)
16006                     len--;
16007             }
16008
16009             // now append the newline and continuation sequence
16010             if (dest)
16011                 dest[len] = '\n';
16012             len++;
16013             if (dest)
16014                 strncpy(dest+len, cseq, cseq_len);
16015             len += cseq_len;
16016             line = cseq_len;
16017             clen = cseq_len;
16018             continue;
16019         }
16020
16021         if (dest)
16022             dest[len] = src[i];
16023         len++;
16024         if (!ansi)
16025             line++;
16026         if (src[i] == '\n')
16027             line = 0;
16028         if (src[i] == 'm')
16029             ansi = 0;
16030     }
16031     if (dest && appData.debugMode)
16032     {
16033         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16034             count, width, line, len, *lp);
16035         show_bytes(debugFP, src, count);
16036         fprintf(debugFP, "\ndest: ");
16037         show_bytes(debugFP, dest, len);
16038         fprintf(debugFP, "\n");
16039     }
16040     *lp = dest ? line : old_line;
16041
16042     return len;
16043 }
16044
16045 // [HGM] vari: routines for shelving variations
16046
16047 void
16048 PushTail(int firstMove, int lastMove)
16049 {
16050         int i, j, nrMoves = lastMove - firstMove;
16051
16052         if(appData.icsActive) { // only in local mode
16053                 forwardMostMove = currentMove; // mimic old ICS behavior
16054                 return;
16055         }
16056         if(storedGames >= MAX_VARIATIONS-1) return;
16057
16058         // push current tail of game on stack
16059         savedResult[storedGames] = gameInfo.result;
16060         savedDetails[storedGames] = gameInfo.resultDetails;
16061         gameInfo.resultDetails = NULL;
16062         savedFirst[storedGames] = firstMove;
16063         savedLast [storedGames] = lastMove;
16064         savedFramePtr[storedGames] = framePtr;
16065         framePtr -= nrMoves; // reserve space for the boards
16066         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16067             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16068             for(j=0; j<MOVE_LEN; j++)
16069                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16070             for(j=0; j<2*MOVE_LEN; j++)
16071                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16072             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16073             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16074             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16075             pvInfoList[firstMove+i-1].depth = 0;
16076             commentList[framePtr+i] = commentList[firstMove+i];
16077             commentList[firstMove+i] = NULL;
16078         }
16079
16080         storedGames++;
16081         forwardMostMove = firstMove; // truncate game so we can start variation
16082         if(storedGames == 1) GreyRevert(FALSE);
16083 }
16084
16085 Boolean
16086 PopTail(Boolean annotate)
16087 {
16088         int i, j, nrMoves;
16089         char buf[8000], moveBuf[20];
16090
16091         if(appData.icsActive) return FALSE; // only in local mode
16092         if(!storedGames) return FALSE; // sanity
16093         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16094
16095         storedGames--;
16096         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16097         nrMoves = savedLast[storedGames] - currentMove;
16098         if(annotate) {
16099                 int cnt = 10;
16100                 if(!WhiteOnMove(currentMove))
16101                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16102                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16103                 for(i=currentMove; i<forwardMostMove; i++) {
16104                         if(WhiteOnMove(i))
16105                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16106                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16107                         strcat(buf, moveBuf);
16108                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16109                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16110                 }
16111                 strcat(buf, ")");
16112         }
16113         for(i=1; i<=nrMoves; i++) { // copy last variation back
16114             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16115             for(j=0; j<MOVE_LEN; j++)
16116                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16117             for(j=0; j<2*MOVE_LEN; j++)
16118                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16119             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16120             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16121             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16122             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16123             commentList[currentMove+i] = commentList[framePtr+i];
16124             commentList[framePtr+i] = NULL;
16125         }
16126         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16127         framePtr = savedFramePtr[storedGames];
16128         gameInfo.result = savedResult[storedGames];
16129         if(gameInfo.resultDetails != NULL) {
16130             free(gameInfo.resultDetails);
16131       }
16132         gameInfo.resultDetails = savedDetails[storedGames];
16133         forwardMostMove = currentMove + nrMoves;
16134         if(storedGames == 0) GreyRevert(TRUE);
16135         return TRUE;
16136 }
16137
16138 void
16139 CleanupTail()
16140 {       // remove all shelved variations
16141         int i;
16142         for(i=0; i<storedGames; i++) {
16143             if(savedDetails[i])
16144                 free(savedDetails[i]);
16145             savedDetails[i] = NULL;
16146         }
16147         for(i=framePtr; i<MAX_MOVES; i++) {
16148                 if(commentList[i]) free(commentList[i]);
16149                 commentList[i] = NULL;
16150         }
16151         framePtr = MAX_MOVES-1;
16152         storedGames = 0;
16153 }
16154
16155 void
16156 LoadVariation(int index, char *text)
16157 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16158         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16159         int level = 0, move;
16160
16161         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16162         // first find outermost bracketing variation
16163         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16164             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16165                 if(*p == '{') wait = '}'; else
16166                 if(*p == '[') wait = ']'; else
16167                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16168                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16169             }
16170             if(*p == wait) wait = NULLCHAR; // closing ]} found
16171             p++;
16172         }
16173         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16174         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16175         end[1] = NULLCHAR; // clip off comment beyond variation
16176         ToNrEvent(currentMove-1);
16177         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16178         // kludge: use ParsePV() to append variation to game
16179         move = currentMove;
16180         ParsePV(start, TRUE);
16181         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16182         ClearPremoveHighlights();
16183         CommentPopDown();
16184         ToNrEvent(currentMove+1);
16185 }
16186