Allow null move in analysis and edit-game mode
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #include <sys/file.h>
67 #define DoSleep( n ) if( (n) >= 0) sleep(n)
68 #define SLASH '/'
69
70 #endif
71
72 #include "config.h"
73
74 #include <assert.h>
75 #include <stdio.h>
76 #include <ctype.h>
77 #include <errno.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <math.h>
81 #include <ctype.h>
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 # include <stdarg.h>
87 #else /* not STDC_HEADERS */
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if TIME_WITH_SYS_TIME
104 # include <sys/time.h>
105 # include <time.h>
106 #else
107 # if HAVE_SYS_TIME_H
108 #  include <sys/time.h>
109 # else
110 #  include <time.h>
111 # endif
112 #endif
113
114 #if defined(_amigados) && !defined(__GNUC__)
115 struct timezone {
116     int tz_minuteswest;
117     int tz_dsttime;
118 };
119 extern int gettimeofday(struct timeval *, struct timezone *);
120 #endif
121
122 #if HAVE_UNISTD_H
123 # include <unistd.h>
124 #endif
125
126 #include "common.h"
127 #include "frontend.h"
128 #include "backend.h"
129 #include "parser.h"
130 #include "moves.h"
131 #if ZIPPY
132 # include "zippy.h"
133 #endif
134 #include "backendz.h"
135 #include "gettext.h"
136
137 #ifdef ENABLE_NLS
138 # define _(s) gettext (s)
139 # define N_(s) gettext_noop (s)
140 # define T_(s) gettext(s)
141 #else
142 # ifdef WIN32
143 #   define _(s) T_(s)
144 #   define N_(s) s
145 # else
146 #   define _(s) (s)
147 #   define N_(s) s
148 #   define T_(s) s
149 # endif
150 #endif
151
152
153 /* A point in time */
154 typedef struct {
155     long sec;  /* Assuming this is >= 32 bits */
156     int ms;    /* Assuming this is >= 16 bits */
157 } TimeMark;
158
159 int establish P((void));
160 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
161                          char *buf, int count, int error));
162 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
163                       char *buf, int count, int error));
164 void ics_printf P((char *format, ...));
165 void SendToICS P((char *s));
166 void SendToICSDelayed P((char *s, long msdelay));
167 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
168 void HandleMachineMove P((char *message, ChessProgramState *cps));
169 int AutoPlayOneMove P((void));
170 int LoadGameOneMove P((ChessMove readAhead));
171 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
172 int LoadPositionFromFile P((char *filename, int n, char *title));
173 int SavePositionToFile P((char *filename));
174 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175                                                                                 Board board));
176 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
177 void ShowMove P((int fromX, int fromY, int toX, int toY));
178 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
179                    /*char*/int promoChar));
180 void BackwardInner P((int target));
181 void ForwardInner P((int target));
182 int Adjudicate P((ChessProgramState *cps));
183 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
184 void EditPositionDone P((Boolean fakeRights));
185 void PrintOpponents P((FILE *fp));
186 void PrintPosition P((FILE *fp, int move));
187 void StartChessProgram P((ChessProgramState *cps));
188 void SendToProgram P((char *message, ChessProgramState *cps));
189 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
190 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
191                            char *buf, int count, int error));
192 void SendTimeControl P((ChessProgramState *cps,
193                         int mps, long tc, int inc, int sd, int st));
194 char *TimeControlTagValue P((void));
195 void Attention P((ChessProgramState *cps));
196 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
197 int ResurrectChessProgram P((void));
198 void DisplayComment P((int moveNumber, char *text));
199 void DisplayMove P((int moveNumber));
200
201 void ParseGameHistory P((char *game));
202 void ParseBoard12 P((char *string));
203 void KeepAlive P((void));
204 void StartClocks P((void));
205 void SwitchClocks P((int nr));
206 void StopClocks P((void));
207 void ResetClocks P((void));
208 char *PGNDate P((void));
209 void SetGameInfo P((void));
210 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
211 int RegisterMove P((void));
212 void MakeRegisteredMove P((void));
213 void TruncateGame P((void));
214 int looking_at P((char *, int *, char *));
215 void CopyPlayerNameIntoFileName P((char **, char *));
216 char *SavePart P((char *));
217 int SaveGameOldStyle P((FILE *));
218 int SaveGamePGN P((FILE *));
219 void GetTimeMark P((TimeMark *));
220 long SubtractTimeMarks P((TimeMark *, TimeMark *));
221 int CheckFlags P((void));
222 long NextTickLength P((long));
223 void CheckTimeControl P((void));
224 void show_bytes P((FILE *, char *, int));
225 int string_to_rating P((char *str));
226 void ParseFeatures P((char* args, ChessProgramState *cps));
227 void InitBackEnd3 P((void));
228 void FeatureDone P((ChessProgramState* cps, int val));
229 void InitChessProgram P((ChessProgramState *cps, int setup));
230 void OutputKibitz(int window, char *text);
231 int PerpetualChase(int first, int last);
232 int EngineOutputIsUp();
233 void InitDrawingSizes(int x, int y);
234 void NextMatchGame P((void));
235 int NextTourneyGame P((int nr, int *swap));
236 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
237 FILE *WriteTourneyFile P((char *results));
238 void DisplayTwoMachinesTitle P(());
239
240 #ifdef WIN32
241        extern void ConsoleCreate();
242 #endif
243
244 ChessProgramState *WhitePlayer();
245 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
246 int VerifyDisplayMode P(());
247
248 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
249 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
250 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
251 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
252 void ics_update_width P((int new_width));
253 extern char installDir[MSG_SIZ];
254 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 Boolean abortMatch;
256
257 extern int tinyLayout, smallLayout;
258 ChessProgramStats programStats;
259 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 int endPV = -1;
261 static int exiting = 0; /* [HGM] moved to top */
262 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
263 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
264 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
265 int partnerHighlight[2];
266 Boolean partnerBoardValid = 0;
267 char partnerStatus[MSG_SIZ];
268 Boolean partnerUp;
269 Boolean originalFlip;
270 Boolean twoBoards = 0;
271 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
272 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
273 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
274 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
275 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
276 int opponentKibitzes;
277 int lastSavedGame; /* [HGM] save: ID of game */
278 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
279 extern int chatCount;
280 int chattingPartner;
281 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
282 char lastMsg[MSG_SIZ];
283 ChessSquare pieceSweep = EmptySquare;
284 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
285 int promoDefaultAltered;
286
287 /* States for ics_getting_history */
288 #define H_FALSE 0
289 #define H_REQUESTED 1
290 #define H_GOT_REQ_HEADER 2
291 #define H_GOT_UNREQ_HEADER 3
292 #define H_GETTING_MOVES 4
293 #define H_GOT_UNWANTED_HEADER 5
294
295 /* whosays values for GameEnds */
296 #define GE_ICS 0
297 #define GE_ENGINE 1
298 #define GE_PLAYER 2
299 #define GE_FILE 3
300 #define GE_XBOARD 4
301 #define GE_ENGINE1 5
302 #define GE_ENGINE2 6
303
304 /* Maximum number of games in a cmail message */
305 #define CMAIL_MAX_GAMES 20
306
307 /* Different types of move when calling RegisterMove */
308 #define CMAIL_MOVE   0
309 #define CMAIL_RESIGN 1
310 #define CMAIL_DRAW   2
311 #define CMAIL_ACCEPT 3
312
313 /* Different types of result to remember for each game */
314 #define CMAIL_NOT_RESULT 0
315 #define CMAIL_OLD_RESULT 1
316 #define CMAIL_NEW_RESULT 2
317
318 /* Telnet protocol constants */
319 #define TN_WILL 0373
320 #define TN_WONT 0374
321 #define TN_DO   0375
322 #define TN_DONT 0376
323 #define TN_IAC  0377
324 #define TN_ECHO 0001
325 #define TN_SGA  0003
326 #define TN_PORT 23
327
328 char*
329 safeStrCpy( char *dst, const char *src, size_t count )
330 { // [HGM] made safe
331   int i;
332   assert( dst != NULL );
333   assert( src != NULL );
334   assert( count > 0 );
335
336   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
337   if(  i == count && dst[count-1] != NULLCHAR)
338     {
339       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
340       if(appData.debugMode)
341       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
342     }
343
344   return dst;
345 }
346
347 /* Some compiler can't cast u64 to double
348  * This function do the job for us:
349
350  * We use the highest bit for cast, this only
351  * works if the highest bit is not
352  * in use (This should not happen)
353  *
354  * We used this for all compiler
355  */
356 double
357 u64ToDouble(u64 value)
358 {
359   double r;
360   u64 tmp = value & u64Const(0x7fffffffffffffff);
361   r = (double)(s64)tmp;
362   if (value & u64Const(0x8000000000000000))
363        r +=  9.2233720368547758080e18; /* 2^63 */
364  return r;
365 }
366
367 /* Fake up flags for now, as we aren't keeping track of castling
368    availability yet. [HGM] Change of logic: the flag now only
369    indicates the type of castlings allowed by the rule of the game.
370    The actual rights themselves are maintained in the array
371    castlingRights, as part of the game history, and are not probed
372    by this function.
373  */
374 int
375 PosFlags(index)
376 {
377   int flags = F_ALL_CASTLE_OK;
378   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
379   switch (gameInfo.variant) {
380   case VariantSuicide:
381     flags &= ~F_ALL_CASTLE_OK;
382   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
383     flags |= F_IGNORE_CHECK;
384   case VariantLosers:
385     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386     break;
387   case VariantAtomic:
388     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389     break;
390   case VariantKriegspiel:
391     flags |= F_KRIEGSPIEL_CAPTURE;
392     break;
393   case VariantCapaRandom:
394   case VariantFischeRandom:
395     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
396   case VariantNoCastle:
397   case VariantShatranj:
398   case VariantCourier:
399   case VariantMakruk:
400   case VariantGrand:
401     flags &= ~F_ALL_CASTLE_OK;
402     break;
403   default:
404     break;
405   }
406   return flags;
407 }
408
409 FILE *gameFileFP, *debugFP;
410
411 /*
412     [AS] Note: sometimes, the sscanf() function is used to parse the input
413     into a fixed-size buffer. Because of this, we must be prepared to
414     receive strings as long as the size of the input buffer, which is currently
415     set to 4K for Windows and 8K for the rest.
416     So, we must either allocate sufficiently large buffers here, or
417     reduce the size of the input buffer in the input reading part.
418 */
419
420 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
421 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
422 char thinkOutput1[MSG_SIZ*10];
423
424 ChessProgramState first, second, pairing;
425
426 /* premove variables */
427 int premoveToX = 0;
428 int premoveToY = 0;
429 int premoveFromX = 0;
430 int premoveFromY = 0;
431 int premovePromoChar = 0;
432 int gotPremove = 0;
433 Boolean alarmSounded;
434 /* end premove variables */
435
436 char *ics_prefix = "$";
437 int ics_type = ICS_GENERIC;
438
439 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
440 int pauseExamForwardMostMove = 0;
441 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
442 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
443 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
444 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
445 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
446 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
447 int whiteFlag = FALSE, blackFlag = FALSE;
448 int userOfferedDraw = FALSE;
449 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
450 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
451 int cmailMoveType[CMAIL_MAX_GAMES];
452 long ics_clock_paused = 0;
453 ProcRef icsPR = NoProc, cmailPR = NoProc;
454 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
455 GameMode gameMode = BeginningOfGame;
456 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
457 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
458 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
459 int hiddenThinkOutputState = 0; /* [AS] */
460 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
461 int adjudicateLossPlies = 6;
462 char white_holding[64], black_holding[64];
463 TimeMark lastNodeCountTime;
464 long lastNodeCount=0;
465 int shiftKey; // [HGM] set by mouse handler
466
467 int have_sent_ICS_logon = 0;
468 int movesPerSession;
469 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
470 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
471 long timeControl_2; /* [AS] Allow separate time controls */
472 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
473 long timeRemaining[2][MAX_MOVES];
474 int matchGame = 0, nextGame = 0, roundNr = 0;
475 Boolean waitingForGame = FALSE;
476 TimeMark programStartTime, pauseStart;
477 char ics_handle[MSG_SIZ];
478 int have_set_title = 0;
479
480 /* animateTraining preserves the state of appData.animate
481  * when Training mode is activated. This allows the
482  * response to be animated when appData.animate == TRUE and
483  * appData.animateDragging == TRUE.
484  */
485 Boolean animateTraining;
486
487 GameInfo gameInfo;
488
489 AppData appData;
490
491 Board boards[MAX_MOVES];
492 /* [HGM] Following 7 needed for accurate legality tests: */
493 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
494 signed char  initialRights[BOARD_FILES];
495 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
496 int   initialRulePlies, FENrulePlies;
497 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
498 int loadFlag = 0;
499 Boolean shuffleOpenings;
500 int mute; // mute all sounds
501
502 // [HGM] vari: next 12 to save and restore variations
503 #define MAX_VARIATIONS 10
504 int framePtr = MAX_MOVES-1; // points to free stack entry
505 int storedGames = 0;
506 int savedFirst[MAX_VARIATIONS];
507 int savedLast[MAX_VARIATIONS];
508 int savedFramePtr[MAX_VARIATIONS];
509 char *savedDetails[MAX_VARIATIONS];
510 ChessMove savedResult[MAX_VARIATIONS];
511
512 void PushTail P((int firstMove, int lastMove));
513 Boolean PopTail P((Boolean annotate));
514 void PushInner P((int firstMove, int lastMove));
515 void PopInner P((Boolean annotate));
516 void CleanupTail P((void));
517
518 ChessSquare  FIDEArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackBishop, BlackKnight, BlackRook }
523 };
524
525 ChessSquare twoKingsArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
529         BlackKing, BlackKing, BlackKnight, BlackRook }
530 };
531
532 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
534         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
535     { BlackRook, BlackMan, BlackBishop, BlackQueen,
536         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 };
538
539 ChessSquare SpartanArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
543         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 };
545
546 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
550         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 };
552
553 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
555         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
557         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 };
559
560 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
561     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackMan, BlackFerz,
564         BlackKing, BlackMan, BlackKnight, BlackRook }
565 };
566
567
568 #if (BOARD_FILES>=10)
569 ChessSquare ShogiArray[2][BOARD_FILES] = {
570     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
571         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
572     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
573         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 };
575
576 ChessSquare XiangqiArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
578         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
580         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 };
582
583 ChessSquare CapablancaArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
585         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
587         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 };
589
590 ChessSquare GreatArray[2][BOARD_FILES] = {
591     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
592         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
593     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
594         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 };
596
597 ChessSquare JanusArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
599         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
600     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
601         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 };
603
604 ChessSquare GrandArray[2][BOARD_FILES] = {
605     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
606         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
607     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
608         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
609 };
610
611 #ifdef GOTHIC
612 ChessSquare GothicArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
614         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
616         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !GOTHIC
619 #define GothicArray CapablancaArray
620 #endif // !GOTHIC
621
622 #ifdef FALCON
623 ChessSquare FalconArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
625         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
627         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
628 };
629 #else // !FALCON
630 #define FalconArray CapablancaArray
631 #endif // !FALCON
632
633 #else // !(BOARD_FILES>=10)
634 #define XiangqiPosition FIDEArray
635 #define CapablancaArray FIDEArray
636 #define GothicArray FIDEArray
637 #define GreatArray FIDEArray
638 #endif // !(BOARD_FILES>=10)
639
640 #if (BOARD_FILES>=12)
641 ChessSquare CourierArray[2][BOARD_FILES] = {
642     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
643         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
644     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
645         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
646 };
647 #else // !(BOARD_FILES>=12)
648 #define CourierArray CapablancaArray
649 #endif // !(BOARD_FILES>=12)
650
651
652 Board initialPosition;
653
654
655 /* Convert str to a rating. Checks for special cases of "----",
656
657    "++++", etc. Also strips ()'s */
658 int
659 string_to_rating(str)
660   char *str;
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine(ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions(ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750 "first",
751 "second"
752 };
753
754 void
755 InitEngine(ChessProgramState *cps, int n)
756 {   // [HGM] all engine initialiation put in a function that does one engine
757
758     ClearOptions(cps);
759
760     cps->which = engineNames[n];
761     cps->maybeThinking = FALSE;
762     cps->pr = NoProc;
763     cps->isr = NULL;
764     cps->sendTime = 2;
765     cps->sendDrawOffers = 1;
766
767     cps->program = appData.chessProgram[n];
768     cps->host = appData.host[n];
769     cps->dir = appData.directory[n];
770     cps->initString = appData.engInitString[n];
771     cps->computerString = appData.computerString[n];
772     cps->useSigint  = TRUE;
773     cps->useSigterm = TRUE;
774     cps->reuse = appData.reuse[n];
775     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
776     cps->useSetboard = FALSE;
777     cps->useSAN = FALSE;
778     cps->usePing = FALSE;
779     cps->lastPing = 0;
780     cps->lastPong = 0;
781     cps->usePlayother = FALSE;
782     cps->useColors = TRUE;
783     cps->useUsermove = FALSE;
784     cps->sendICS = FALSE;
785     cps->sendName = appData.icsActive;
786     cps->sdKludge = FALSE;
787     cps->stKludge = FALSE;
788     TidyProgramName(cps->program, cps->host, cps->tidy);
789     cps->matchWins = 0;
790     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
791     cps->analysisSupport = 2; /* detect */
792     cps->analyzing = FALSE;
793     cps->initDone = FALSE;
794
795     /* New features added by Tord: */
796     cps->useFEN960 = FALSE;
797     cps->useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     cps->fenOverride  = appData.fenOverride[n];
800
801     /* [HGM] time odds: set factor for each machine */
802     cps->timeOdds  = appData.timeOdds[n];
803
804     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
805     cps->accumulateTC = appData.accumulateTC[n];
806     cps->maxNrOfSessions = 1;
807
808     /* [HGM] debug */
809     cps->debug = FALSE;
810
811     cps->supportsNPS = UNKNOWN;
812     cps->memSize = FALSE;
813     cps->maxCores = FALSE;
814     cps->egtFormats[0] = NULLCHAR;
815
816     /* [HGM] options */
817     cps->optionSettings  = appData.engOptions[n];
818
819     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
820     cps->isUCI = appData.isUCI[n]; /* [AS] */
821     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
822
823     if (appData.protocolVersion[n] > PROTOVER
824         || appData.protocolVersion[n] < 1)
825       {
826         char buf[MSG_SIZ];
827         int len;
828
829         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
830                        appData.protocolVersion[n]);
831         if( (len > MSG_SIZ) && appData.debugMode )
832           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
833
834         DisplayFatalError(buf, 0, 2);
835       }
836     else
837       {
838         cps->protocolVersion = appData.protocolVersion[n];
839       }
840
841     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
842 }
843
844 ChessProgramState *savCps;
845
846 void
847 LoadEngine()
848 {
849     int i;
850     if(WaitForEngine(savCps, LoadEngine)) return;
851     CommonEngineInit(); // recalculate time odds
852     if(gameInfo.variant != StringToVariant(appData.variant)) {
853         // we changed variant when loading the engine; this forces us to reset
854         Reset(TRUE, savCps != &first);
855         EditGameEvent(); // for consistency with other path, as Reset changes mode
856     }
857     InitChessProgram(savCps, FALSE);
858     SendToProgram("force\n", savCps);
859     DisplayMessage("", "");
860     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
862     ThawUI();
863     SetGNUMode();
864 }
865
866 void
867 ReplaceEngine(ChessProgramState *cps, int n)
868 {
869     EditGameEvent();
870     UnloadEngine(cps);
871     appData.noChessProgram = FALSE;
872     appData.clockMode = TRUE;
873     InitEngine(cps, n);
874     UpdateLogos(TRUE);
875     if(n) return; // only startup first engine immediately; second can wait
876     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877     LoadEngine();
878 }
879
880 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
881 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
882
883 static char resetOptions[] = 
884         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
885         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
886
887 void
888 Load(ChessProgramState *cps, int i)
889 {
890     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
891     if(engineLine[0]) { // an engine was selected from the combo box
892         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895         ParseArgsFromString(buf);
896         SwapEngines(i);
897         ReplaceEngine(cps, i);
898         return;
899     }
900     p = engineName;
901     while(q = strchr(p, SLASH)) p = q+1;
902     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903     if(engineDir[0] != NULLCHAR)
904         appData.directory[i] = engineDir;
905     else if(p != engineName) { // derive directory from engine path, when not given
906         p[-1] = 0;
907         appData.directory[i] = strdup(engineName);
908         p[-1] = SLASH;
909     } else appData.directory[i] = ".";
910     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911     if(params[0]) {
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
985
986     ClearProgramStats();
987     programStats.ok_to_send = 1;
988     programStats.seen_stat = 0;
989
990     /*
991      * Initialize game list
992      */
993     ListNew(&gameList);
994
995
996     /*
997      * Internet chess server status
998      */
999     if (appData.icsActive) {
1000         appData.matchMode = FALSE;
1001         appData.matchGames = 0;
1002 #if ZIPPY
1003         appData.noChessProgram = !appData.zippyPlay;
1004 #else
1005         appData.zippyPlay = FALSE;
1006         appData.zippyTalk = FALSE;
1007         appData.noChessProgram = TRUE;
1008 #endif
1009         if (*appData.icsHelper != NULLCHAR) {
1010             appData.useTelnet = TRUE;
1011             appData.telnetProgram = appData.icsHelper;
1012         }
1013     } else {
1014         appData.zippyTalk = appData.zippyPlay = FALSE;
1015     }
1016
1017     /* [AS] Initialize pv info list [HGM] and game state */
1018     {
1019         int i, j;
1020
1021         for( i=0; i<=framePtr; i++ ) {
1022             pvInfoList[i].depth = -1;
1023             boards[i][EP_STATUS] = EP_NONE;
1024             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1025         }
1026     }
1027
1028     InitTimeControls();
1029
1030     /* [AS] Adjudication threshold */
1031     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1032
1033     InitEngine(&first, 0);
1034     InitEngine(&second, 1);
1035     CommonEngineInit();
1036
1037     pairing.which = "pairing"; // pairing engine
1038     pairing.pr = NoProc;
1039     pairing.isr = NULL;
1040     pairing.program = appData.pairingEngine;
1041     pairing.host = "localhost";
1042     pairing.dir = ".";
1043
1044     if (appData.icsActive) {
1045         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1046     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047         appData.clockMode = FALSE;
1048         first.sendTime = second.sendTime = 0;
1049     }
1050
1051 #if ZIPPY
1052     /* Override some settings from environment variables, for backward
1053        compatibility.  Unfortunately it's not feasible to have the env
1054        vars just set defaults, at least in xboard.  Ugh.
1055     */
1056     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1057       ZippyInit();
1058     }
1059 #endif
1060
1061     if (!appData.icsActive) {
1062       char buf[MSG_SIZ];
1063       int len;
1064
1065       /* Check for variants that are supported only in ICS mode,
1066          or not at all.  Some that are accepted here nevertheless
1067          have bugs; see comments below.
1068       */
1069       VariantClass variant = StringToVariant(appData.variant);
1070       switch (variant) {
1071       case VariantBughouse:     /* need four players and two boards */
1072       case VariantKriegspiel:   /* need to hide pieces and move details */
1073         /* case VariantFischeRandom: (Fabien: moved below) */
1074         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075         if( (len > MSG_SIZ) && appData.debugMode )
1076           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1077
1078         DisplayFatalError(buf, 0, 2);
1079         return;
1080
1081       case VariantUnknown:
1082       case VariantLoadable:
1083       case Variant29:
1084       case Variant30:
1085       case Variant31:
1086       case Variant32:
1087       case Variant33:
1088       case Variant34:
1089       case Variant35:
1090       case Variant36:
1091       default:
1092         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093         if( (len > MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1100       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1101       case VariantGothic:     /* [HGM] should work */
1102       case VariantCapablanca: /* [HGM] should work */
1103       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1104       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1105       case VariantKnightmate: /* [HGM] should work */
1106       case VariantCylinder:   /* [HGM] untested */
1107       case VariantFalcon:     /* [HGM] untested */
1108       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109                                  offboard interposition not understood */
1110       case VariantNormal:     /* definitely works! */
1111       case VariantWildCastle: /* pieces not automatically shuffled */
1112       case VariantNoCastle:   /* pieces not automatically shuffled */
1113       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114       case VariantLosers:     /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantSuicide:    /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantGiveaway:   /* should work except for win condition,
1119                                  and doesn't know captures are mandatory */
1120       case VariantTwoKings:   /* should work */
1121       case VariantAtomic:     /* should work except for win condition */
1122       case Variant3Check:     /* should work except for win condition */
1123       case VariantShatranj:   /* should work except for all win conditions */
1124       case VariantMakruk:     /* should work except for draw countdown */
1125       case VariantBerolina:   /* might work if TestLegality is off */
1126       case VariantCapaRandom: /* should work */
1127       case VariantJanus:      /* should work */
1128       case VariantSuper:      /* experimental */
1129       case VariantGreat:      /* experimental, requires legality testing to be off */
1130       case VariantSChess:     /* S-Chess, should work */
1131       case VariantGrand:      /* should work */
1132       case VariantSpartan:    /* should work */
1133         break;
1134       }
1135     }
1136
1137 }
1138
1139 int NextIntegerFromString( char ** str, long * value )
1140 {
1141     int result = -1;
1142     char * s = *str;
1143
1144     while( *s == ' ' || *s == '\t' ) {
1145         s++;
1146     }
1147
1148     *value = 0;
1149
1150     if( *s >= '0' && *s <= '9' ) {
1151         while( *s >= '0' && *s <= '9' ) {
1152             *value = *value * 10 + (*s - '0');
1153             s++;
1154         }
1155
1156         result = 0;
1157     }
1158
1159     *str = s;
1160
1161     return result;
1162 }
1163
1164 int NextTimeControlFromString( char ** str, long * value )
1165 {
1166     long temp;
1167     int result = NextIntegerFromString( str, &temp );
1168
1169     if( result == 0 ) {
1170         *value = temp * 60; /* Minutes */
1171         if( **str == ':' ) {
1172             (*str)++;
1173             result = NextIntegerFromString( str, &temp );
1174             *value += temp; /* Seconds */
1175         }
1176     }
1177
1178     return result;
1179 }
1180
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183     int result = -1, type = 0; long temp, temp2;
1184
1185     if(**str != ':') return -1; // old params remain in force!
1186     (*str)++;
1187     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188     if( NextIntegerFromString( str, &temp ) ) return -1;
1189     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1190
1191     if(**str != '/') {
1192         /* time only: incremental or sudden-death time control */
1193         if(**str == '+') { /* increment follows; read it */
1194             (*str)++;
1195             if(**str == '!') type = *(*str)++; // Bronstein TC
1196             if(result = NextIntegerFromString( str, &temp2)) return -1;
1197             *inc = temp2 * 1000;
1198             if(**str == '.') { // read fraction of increment
1199                 char *start = ++(*str);
1200                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1201                 temp2 *= 1000;
1202                 while(start++ < *str) temp2 /= 10;
1203                 *inc += temp2;
1204             }
1205         } else *inc = 0;
1206         *moves = 0; *tc = temp * 1000; *incType = type;
1207         return 0;
1208     }
1209
1210     (*str)++; /* classical time control */
1211     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1212
1213     if(result == 0) {
1214         *moves = temp;
1215         *tc    = temp2 * 1000;
1216         *inc   = 0;
1217         *incType = type;
1218     }
1219     return result;
1220 }
1221
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 {   /* [HGM] get time to add from the multi-session time-control string */
1224     int incType, moves=1; /* kludge to force reading of first session */
1225     long time, increment;
1226     char *s = tcString;
1227
1228     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1230     do {
1231         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234         if(movenr == -1) return time;    /* last move before new session     */
1235         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237         if(!moves) return increment;     /* current session is incremental   */
1238         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239     } while(movenr >= -1);               /* try again for next session       */
1240
1241     return 0; // no new time quota on this move
1242 }
1243
1244 int
1245 ParseTimeControl(tc, ti, mps)
1246      char *tc;
1247      float ti;
1248      int mps;
1249 {
1250   long tc1;
1251   long tc2;
1252   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1253   int min, sec=0;
1254
1255   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1258   if(ti > 0) {
1259
1260     if(mps)
1261       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1262     else 
1263       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1264   } else {
1265     if(mps)
1266       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1267     else 
1268       snprintf(buf, MSG_SIZ, ":%s", mytc);
1269   }
1270   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1271   
1272   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1273     return FALSE;
1274   }
1275
1276   if( *tc == '/' ) {
1277     /* Parse second time control */
1278     tc++;
1279
1280     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1281       return FALSE;
1282     }
1283
1284     if( tc2 == 0 ) {
1285       return FALSE;
1286     }
1287
1288     timeControl_2 = tc2 * 1000;
1289   }
1290   else {
1291     timeControl_2 = 0;
1292   }
1293
1294   if( tc1 == 0 ) {
1295     return FALSE;
1296   }
1297
1298   timeControl = tc1 * 1000;
1299
1300   if (ti >= 0) {
1301     timeIncrement = ti * 1000;  /* convert to ms */
1302     movesPerSession = 0;
1303   } else {
1304     timeIncrement = 0;
1305     movesPerSession = mps;
1306   }
1307   return TRUE;
1308 }
1309
1310 void
1311 InitBackEnd2()
1312 {
1313     if (appData.debugMode) {
1314         fprintf(debugFP, "%s\n", programVersion);
1315     }
1316
1317     set_cont_sequence(appData.wrapContSeq);
1318     if (appData.matchGames > 0) {
1319         appData.matchMode = TRUE;
1320     } else if (appData.matchMode) {
1321         appData.matchGames = 1;
1322     }
1323     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324         appData.matchGames = appData.sameColorGames;
1325     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1328     }
1329     Reset(TRUE, FALSE);
1330     if (appData.noChessProgram || first.protocolVersion == 1) {
1331       InitBackEnd3();
1332     } else {
1333       /* kludge: allow timeout for initial "feature" commands */
1334       FreezeUI();
1335       DisplayMessage("", _("Starting chess program"));
1336       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1337     }
1338 }
1339
1340 int
1341 CalculateIndex(int index, int gameNr)
1342 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1343     int res;
1344     if(index > 0) return index; // fixed nmber
1345     if(index == 0) return 1;
1346     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1348     return res;
1349 }
1350
1351 int
1352 LoadGameOrPosition(int gameNr)
1353 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354     if (*appData.loadGameFile != NULLCHAR) {
1355         if (!LoadGameFromFile(appData.loadGameFile,
1356                 CalculateIndex(appData.loadGameIndex, gameNr),
1357                               appData.loadGameFile, FALSE)) {
1358             DisplayFatalError(_("Bad game file"), 0, 1);
1359             return 0;
1360         }
1361     } else if (*appData.loadPositionFile != NULLCHAR) {
1362         if (!LoadPositionFromFile(appData.loadPositionFile,
1363                 CalculateIndex(appData.loadPositionIndex, gameNr),
1364                                   appData.loadPositionFile)) {
1365             DisplayFatalError(_("Bad position file"), 0, 1);
1366             return 0;
1367         }
1368     }
1369     return 1;
1370 }
1371
1372 void
1373 ReserveGame(int gameNr, char resChar)
1374 {
1375     FILE *tf = fopen(appData.tourneyFile, "r+");
1376     char *p, *q, c, buf[MSG_SIZ];
1377     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378     safeStrCpy(buf, lastMsg, MSG_SIZ);
1379     DisplayMessage(_("Pick new game"), "");
1380     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381     ParseArgsFromFile(tf);
1382     p = q = appData.results;
1383     if(appData.debugMode) {
1384       char *r = appData.participants;
1385       fprintf(debugFP, "results = '%s'\n", p);
1386       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387       fprintf(debugFP, "\n");
1388     }
1389     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1390     nextGame = q - p;
1391     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392     safeStrCpy(q, p, strlen(p) + 2);
1393     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1397         q[nextGame] = '*';
1398     }
1399     fseek(tf, -(strlen(p)+4), SEEK_END);
1400     c = fgetc(tf);
1401     if(c != '"') // depending on DOS or Unix line endings we can be one off
1402          fseek(tf, -(strlen(p)+2), SEEK_END);
1403     else fseek(tf, -(strlen(p)+3), SEEK_END);
1404     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405     DisplayMessage(buf, "");
1406     free(p); appData.results = q;
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409         UnloadEngine(&first);  // next game belongs to other pairing;
1410         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1411     }
1412 }
1413
1414 void
1415 MatchEvent(int mode)
1416 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1417         int dummy;
1418         if(matchMode) { // already in match mode: switch it off
1419             abortMatch = TRUE;
1420             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1421             return;
1422         }
1423 //      if(gameMode != BeginningOfGame) {
1424 //          DisplayError(_("You can only start a match from the initial position."), 0);
1425 //          return;
1426 //      }
1427         abortMatch = FALSE;
1428         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429         /* Set up machine vs. machine match */
1430         nextGame = 0;
1431         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432         if(appData.tourneyFile[0]) {
1433             ReserveGame(-1, 0);
1434             if(nextGame > appData.matchGames) {
1435                 char buf[MSG_SIZ];
1436                 if(strchr(appData.results, '*') == NULL) {
1437                     FILE *f;
1438                     appData.tourneyCycles++;
1439                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1440                         fclose(f);
1441                         NextTourneyGame(-1, &dummy);
1442                         ReserveGame(-1, 0);
1443                         if(nextGame <= appData.matchGames) {
1444                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1445                             matchMode = mode;
1446                             ScheduleDelayedEvent(NextMatchGame, 10000);
1447                             return;
1448                         }
1449                     }
1450                 }
1451                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452                 DisplayError(buf, 0);
1453                 appData.tourneyFile[0] = 0;
1454                 return;
1455             }
1456         } else
1457         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1458             DisplayFatalError(_("Can't have a match with no chess programs"),
1459                               0, 2);
1460             return;
1461         }
1462         matchMode = mode;
1463         matchGame = roundNr = 1;
1464         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1465         NextMatchGame();
1466 }
1467
1468 void
1469 InitBackEnd3 P((void))
1470 {
1471     GameMode initialMode;
1472     char buf[MSG_SIZ];
1473     int err, len;
1474
1475     InitChessProgram(&first, startedFromSetupPosition);
1476
1477     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1478         free(programVersion);
1479         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1481     }
1482
1483     if (appData.icsActive) {
1484 #ifdef WIN32
1485         /* [DM] Make a console window if needed [HGM] merged ifs */
1486         ConsoleCreate();
1487 #endif
1488         err = establish();
1489         if (err != 0)
1490           {
1491             if (*appData.icsCommPort != NULLCHAR)
1492               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493                              appData.icsCommPort);
1494             else
1495               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496                         appData.icsHost, appData.icsPort);
1497
1498             if( (len > MSG_SIZ) && appData.debugMode )
1499               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1500
1501             DisplayFatalError(buf, err, 1);
1502             return;
1503         }
1504         SetICSMode();
1505         telnetISR =
1506           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1507         fromUserISR =
1508           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511     } else if (appData.noChessProgram) {
1512         SetNCPMode();
1513     } else {
1514         SetGNUMode();
1515     }
1516
1517     if (*appData.cmailGameName != NULLCHAR) {
1518         SetCmailMode();
1519         OpenLoopback(&cmailPR);
1520         cmailISR =
1521           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1522     }
1523
1524     ThawUI();
1525     DisplayMessage("", "");
1526     if (StrCaseCmp(appData.initialMode, "") == 0) {
1527       initialMode = BeginningOfGame;
1528       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1532         ModeHighlight();
1533       }
1534     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535       initialMode = TwoMachinesPlay;
1536     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537       initialMode = AnalyzeFile;
1538     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539       initialMode = AnalyzeMode;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541       initialMode = MachinePlaysWhite;
1542     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543       initialMode = MachinePlaysBlack;
1544     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545       initialMode = EditGame;
1546     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547       initialMode = EditPosition;
1548     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549       initialMode = Training;
1550     } else {
1551       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552       if( (len > MSG_SIZ) && appData.debugMode )
1553         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1554
1555       DisplayFatalError(buf, 0, 2);
1556       return;
1557     }
1558
1559     if (appData.matchMode) {
1560         if(appData.tourneyFile[0]) { // start tourney from command line
1561             FILE *f;
1562             if(f = fopen(appData.tourneyFile, "r")) {
1563                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1564                 fclose(f);
1565                 appData.clockMode = TRUE;
1566                 SetGNUMode();
1567             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1568         }
1569         MatchEvent(TRUE);
1570     } else if (*appData.cmailGameName != NULLCHAR) {
1571         /* Set up cmail mode */
1572         ReloadCmailMsgEvent(TRUE);
1573     } else {
1574         /* Set up other modes */
1575         if (initialMode == AnalyzeFile) {
1576           if (*appData.loadGameFile == NULLCHAR) {
1577             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1578             return;
1579           }
1580         }
1581         if (*appData.loadGameFile != NULLCHAR) {
1582             (void) LoadGameFromFile(appData.loadGameFile,
1583                                     appData.loadGameIndex,
1584                                     appData.loadGameFile, TRUE);
1585         } else if (*appData.loadPositionFile != NULLCHAR) {
1586             (void) LoadPositionFromFile(appData.loadPositionFile,
1587                                         appData.loadPositionIndex,
1588                                         appData.loadPositionFile);
1589             /* [HGM] try to make self-starting even after FEN load */
1590             /* to allow automatic setup of fairy variants with wtm */
1591             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592                 gameMode = BeginningOfGame;
1593                 setboardSpoiledMachineBlack = 1;
1594             }
1595             /* [HGM] loadPos: make that every new game uses the setup */
1596             /* from file as long as we do not switch variant          */
1597             if(!blackPlaysFirst) {
1598                 startedFromPositionFile = TRUE;
1599                 CopyBoard(filePosition, boards[0]);
1600             }
1601         }
1602         if (initialMode == AnalyzeMode) {
1603           if (appData.noChessProgram) {
1604             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1609             return;
1610           }
1611           AnalyzeModeEvent();
1612         } else if (initialMode == AnalyzeFile) {
1613           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614           ShowThinkingEvent();
1615           AnalyzeFileEvent();
1616           AnalysisPeriodicEvent(1);
1617         } else if (initialMode == MachinePlaysWhite) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineWhiteEvent();
1629         } else if (initialMode == MachinePlaysBlack) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           MachineBlackEvent();
1641         } else if (initialMode == TwoMachinesPlay) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           TwoMachinesEvent();
1653         } else if (initialMode == EditGame) {
1654           EditGameEvent();
1655         } else if (initialMode == EditPosition) {
1656           EditPositionEvent();
1657         } else if (initialMode == Training) {
1658           if (*appData.loadGameFile == NULLCHAR) {
1659             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1660             return;
1661           }
1662           TrainingEvent();
1663         }
1664     }
1665 }
1666
1667 /*
1668  * Establish will establish a contact to a remote host.port.
1669  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1670  *  used to talk to the host.
1671  * Returns 0 if okay, error code if not.
1672  */
1673 int
1674 establish()
1675 {
1676     char buf[MSG_SIZ];
1677
1678     if (*appData.icsCommPort != NULLCHAR) {
1679         /* Talk to the host through a serial comm port */
1680         return OpenCommPort(appData.icsCommPort, &icsPR);
1681
1682     } else if (*appData.gateway != NULLCHAR) {
1683         if (*appData.remoteShell == NULLCHAR) {
1684             /* Use the rcmd protocol to run telnet program on a gateway host */
1685             snprintf(buf, sizeof(buf), "%s %s %s",
1686                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1687             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1688
1689         } else {
1690             /* Use the rsh program to run telnet program on a gateway host */
1691             if (*appData.remoteUser == NULLCHAR) {
1692                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1693                         appData.gateway, appData.telnetProgram,
1694                         appData.icsHost, appData.icsPort);
1695             } else {
1696                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1697                         appData.remoteShell, appData.gateway,
1698                         appData.remoteUser, appData.telnetProgram,
1699                         appData.icsHost, appData.icsPort);
1700             }
1701             return StartChildProcess(buf, "", &icsPR);
1702
1703         }
1704     } else if (appData.useTelnet) {
1705         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1706
1707     } else {
1708         /* TCP socket interface differs somewhat between
1709            Unix and NT; handle details in the front end.
1710            */
1711         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1712     }
1713 }
1714
1715 void EscapeExpand(char *p, char *q)
1716 {       // [HGM] initstring: routine to shape up string arguments
1717         while(*p++ = *q++) if(p[-1] == '\\')
1718             switch(*q++) {
1719                 case 'n': p[-1] = '\n'; break;
1720                 case 'r': p[-1] = '\r'; break;
1721                 case 't': p[-1] = '\t'; break;
1722                 case '\\': p[-1] = '\\'; break;
1723                 case 0: *p = 0; return;
1724                 default: p[-1] = q[-1]; break;
1725             }
1726 }
1727
1728 void
1729 show_bytes(fp, buf, count)
1730      FILE *fp;
1731      char *buf;
1732      int count;
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet(pr, message, count, outError)
1748      ProcRef pr;
1749      char *message;
1750      int count;
1751      int *outError;
1752 {
1753     char buf[8192], *p, *q, *buflim;
1754     int left, newcount, outcount;
1755
1756     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1757         *appData.gateway != NULLCHAR) {
1758         if (appData.debugMode) {
1759             fprintf(debugFP, ">ICS: ");
1760             show_bytes(debugFP, message, count);
1761             fprintf(debugFP, "\n");
1762         }
1763         return OutputToProcess(pr, message, count, outError);
1764     }
1765
1766     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1767     p = message;
1768     q = buf;
1769     left = count;
1770     newcount = 0;
1771     while (left) {
1772         if (q >= buflim) {
1773             if (appData.debugMode) {
1774                 fprintf(debugFP, ">ICS: ");
1775                 show_bytes(debugFP, buf, newcount);
1776                 fprintf(debugFP, "\n");
1777             }
1778             outcount = OutputToProcess(pr, buf, newcount, outError);
1779             if (outcount < newcount) return -1; /* to be sure */
1780             q = buf;
1781             newcount = 0;
1782         }
1783         if (*p == '\n') {
1784             *q++ = '\r';
1785             newcount++;
1786         } else if (((unsigned char) *p) == TN_IAC) {
1787             *q++ = (char) TN_IAC;
1788             newcount ++;
1789         }
1790         *q++ = *p++;
1791         newcount++;
1792         left--;
1793     }
1794     if (appData.debugMode) {
1795         fprintf(debugFP, ">ICS: ");
1796         show_bytes(debugFP, buf, newcount);
1797         fprintf(debugFP, "\n");
1798     }
1799     outcount = OutputToProcess(pr, buf, newcount, outError);
1800     if (outcount < newcount) return -1; /* to be sure */
1801     return count;
1802 }
1803
1804 void
1805 read_from_player(isr, closure, message, count, error)
1806      InputSourceRef isr;
1807      VOIDSTAR closure;
1808      char *message;
1809      int count;
1810      int error;
1811 {
1812     int outError, outCount;
1813     static int gotEof = 0;
1814
1815     /* Pass data read from player on to ICS */
1816     if (count > 0) {
1817         gotEof = 0;
1818         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1819         if (outCount < count) {
1820             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821         }
1822     } else if (count < 0) {
1823         RemoveInputSource(isr);
1824         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1825     } else if (gotEof++ > 0) {
1826         RemoveInputSource(isr);
1827         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1828     }
1829 }
1830
1831 void
1832 KeepAlive()
1833 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1834     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1835     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1836     SendToICS("date\n");
1837     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1838 }
1839
1840 /* added routine for printf style output to ics */
1841 void ics_printf(char *format, ...)
1842 {
1843     char buffer[MSG_SIZ];
1844     va_list args;
1845
1846     va_start(args, format);
1847     vsnprintf(buffer, sizeof(buffer), format, args);
1848     buffer[sizeof(buffer)-1] = '\0';
1849     SendToICS(buffer);
1850     va_end(args);
1851 }
1852
1853 void
1854 SendToICS(s)
1855      char *s;
1856 {
1857     int count, outCount, outError;
1858
1859     if (icsPR == NULL) return;
1860
1861     count = strlen(s);
1862     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1863     if (outCount < count) {
1864         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865     }
1866 }
1867
1868 /* This is used for sending logon scripts to the ICS. Sending
1869    without a delay causes problems when using timestamp on ICC
1870    (at least on my machine). */
1871 void
1872 SendToICSDelayed(s,msdelay)
1873      char *s;
1874      long msdelay;
1875 {
1876     int count, outCount, outError;
1877
1878     if (icsPR == NULL) return;
1879
1880     count = strlen(s);
1881     if (appData.debugMode) {
1882         fprintf(debugFP, ">ICS: ");
1883         show_bytes(debugFP, s, count);
1884         fprintf(debugFP, "\n");
1885     }
1886     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1887                                       msdelay);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893
1894 /* Remove all highlighting escape sequences in s
1895    Also deletes any suffix starting with '('
1896    */
1897 char *
1898 StripHighlightAndTitle(s)
1899      char *s;
1900 {
1901     static char retbuf[MSG_SIZ];
1902     char *p = retbuf;
1903
1904     while (*s != NULLCHAR) {
1905         while (*s == '\033') {
1906             while (*s != NULLCHAR && !isalpha(*s)) s++;
1907             if (*s != NULLCHAR) s++;
1908         }
1909         while (*s != NULLCHAR && *s != '\033') {
1910             if (*s == '(' || *s == '[') {
1911                 *p = NULLCHAR;
1912                 return retbuf;
1913             }
1914             *p++ = *s++;
1915         }
1916     }
1917     *p = NULLCHAR;
1918     return retbuf;
1919 }
1920
1921 /* Remove all highlighting escape sequences in s */
1922 char *
1923 StripHighlight(s)
1924      char *s;
1925 {
1926     static char retbuf[MSG_SIZ];
1927     char *p = retbuf;
1928
1929     while (*s != NULLCHAR) {
1930         while (*s == '\033') {
1931             while (*s != NULLCHAR && !isalpha(*s)) s++;
1932             if (*s != NULLCHAR) s++;
1933         }
1934         while (*s != NULLCHAR && *s != '\033') {
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 char *variantNames[] = VARIANT_NAMES;
1943 char *
1944 VariantName(v)
1945      VariantClass v;
1946 {
1947     return variantNames[v];
1948 }
1949
1950
1951 /* Identify a variant from the strings the chess servers use or the
1952    PGN Variant tag names we use. */
1953 VariantClass
1954 StringToVariant(e)
1955      char *e;
1956 {
1957     char *p;
1958     int wnum = -1;
1959     VariantClass v = VariantNormal;
1960     int i, found = FALSE;
1961     char buf[MSG_SIZ];
1962     int len;
1963
1964     if (!e) return v;
1965
1966     /* [HGM] skip over optional board-size prefixes */
1967     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1968         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1969         while( *e++ != '_');
1970     }
1971
1972     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1973         v = VariantNormal;
1974         found = TRUE;
1975     } else
1976     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1977       if (StrCaseStr(e, variantNames[i])) {
1978         v = (VariantClass) i;
1979         found = TRUE;
1980         break;
1981       }
1982     }
1983
1984     if (!found) {
1985       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1986           || StrCaseStr(e, "wild/fr")
1987           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1988         v = VariantFischeRandom;
1989       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1990                  (i = 1, p = StrCaseStr(e, "w"))) {
1991         p += i;
1992         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1993         if (isdigit(*p)) {
1994           wnum = atoi(p);
1995         } else {
1996           wnum = -1;
1997         }
1998         switch (wnum) {
1999         case 0: /* FICS only, actually */
2000         case 1:
2001           /* Castling legal even if K starts on d-file */
2002           v = VariantWildCastle;
2003           break;
2004         case 2:
2005         case 3:
2006         case 4:
2007           /* Castling illegal even if K & R happen to start in
2008              normal positions. */
2009           v = VariantNoCastle;
2010           break;
2011         case 5:
2012         case 7:
2013         case 8:
2014         case 10:
2015         case 11:
2016         case 12:
2017         case 13:
2018         case 14:
2019         case 15:
2020         case 18:
2021         case 19:
2022           /* Castling legal iff K & R start in normal positions */
2023           v = VariantNormal;
2024           break;
2025         case 6:
2026         case 20:
2027         case 21:
2028           /* Special wilds for position setup; unclear what to do here */
2029           v = VariantLoadable;
2030           break;
2031         case 9:
2032           /* Bizarre ICC game */
2033           v = VariantTwoKings;
2034           break;
2035         case 16:
2036           v = VariantKriegspiel;
2037           break;
2038         case 17:
2039           v = VariantLosers;
2040           break;
2041         case 22:
2042           v = VariantFischeRandom;
2043           break;
2044         case 23:
2045           v = VariantCrazyhouse;
2046           break;
2047         case 24:
2048           v = VariantBughouse;
2049           break;
2050         case 25:
2051           v = Variant3Check;
2052           break;
2053         case 26:
2054           /* Not quite the same as FICS suicide! */
2055           v = VariantGiveaway;
2056           break;
2057         case 27:
2058           v = VariantAtomic;
2059           break;
2060         case 28:
2061           v = VariantShatranj;
2062           break;
2063
2064         /* Temporary names for future ICC types.  The name *will* change in
2065            the next xboard/WinBoard release after ICC defines it. */
2066         case 29:
2067           v = Variant29;
2068           break;
2069         case 30:
2070           v = Variant30;
2071           break;
2072         case 31:
2073           v = Variant31;
2074           break;
2075         case 32:
2076           v = Variant32;
2077           break;
2078         case 33:
2079           v = Variant33;
2080           break;
2081         case 34:
2082           v = Variant34;
2083           break;
2084         case 35:
2085           v = Variant35;
2086           break;
2087         case 36:
2088           v = Variant36;
2089           break;
2090         case 37:
2091           v = VariantShogi;
2092           break;
2093         case 38:
2094           v = VariantXiangqi;
2095           break;
2096         case 39:
2097           v = VariantCourier;
2098           break;
2099         case 40:
2100           v = VariantGothic;
2101           break;
2102         case 41:
2103           v = VariantCapablanca;
2104           break;
2105         case 42:
2106           v = VariantKnightmate;
2107           break;
2108         case 43:
2109           v = VariantFairy;
2110           break;
2111         case 44:
2112           v = VariantCylinder;
2113           break;
2114         case 45:
2115           v = VariantFalcon;
2116           break;
2117         case 46:
2118           v = VariantCapaRandom;
2119           break;
2120         case 47:
2121           v = VariantBerolina;
2122           break;
2123         case 48:
2124           v = VariantJanus;
2125           break;
2126         case 49:
2127           v = VariantSuper;
2128           break;
2129         case 50:
2130           v = VariantGreat;
2131           break;
2132         case -1:
2133           /* Found "wild" or "w" in the string but no number;
2134              must assume it's normal chess. */
2135           v = VariantNormal;
2136           break;
2137         default:
2138           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2139           if( (len > MSG_SIZ) && appData.debugMode )
2140             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2141
2142           DisplayError(buf, 0);
2143           v = VariantUnknown;
2144           break;
2145         }
2146       }
2147     }
2148     if (appData.debugMode) {
2149       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2150               e, wnum, VariantName(v));
2151     }
2152     return v;
2153 }
2154
2155 static int leftover_start = 0, leftover_len = 0;
2156 char star_match[STAR_MATCH_N][MSG_SIZ];
2157
2158 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2159    advance *index beyond it, and set leftover_start to the new value of
2160    *index; else return FALSE.  If pattern contains the character '*', it
2161    matches any sequence of characters not containing '\r', '\n', or the
2162    character following the '*' (if any), and the matched sequence(s) are
2163    copied into star_match.
2164    */
2165 int
2166 looking_at(buf, index, pattern)
2167      char *buf;
2168      int *index;
2169      char *pattern;
2170 {
2171     char *bufp = &buf[*index], *patternp = pattern;
2172     int star_count = 0;
2173     char *matchp = star_match[0];
2174
2175     for (;;) {
2176         if (*patternp == NULLCHAR) {
2177             *index = leftover_start = bufp - buf;
2178             *matchp = NULLCHAR;
2179             return TRUE;
2180         }
2181         if (*bufp == NULLCHAR) return FALSE;
2182         if (*patternp == '*') {
2183             if (*bufp == *(patternp + 1)) {
2184                 *matchp = NULLCHAR;
2185                 matchp = star_match[++star_count];
2186                 patternp += 2;
2187                 bufp++;
2188                 continue;
2189             } else if (*bufp == '\n' || *bufp == '\r') {
2190                 patternp++;
2191                 if (*patternp == NULLCHAR)
2192                   continue;
2193                 else
2194                   return FALSE;
2195             } else {
2196                 *matchp++ = *bufp++;
2197                 continue;
2198             }
2199         }
2200         if (*patternp != *bufp) return FALSE;
2201         patternp++;
2202         bufp++;
2203     }
2204 }
2205
2206 void
2207 SendToPlayer(data, length)
2208      char *data;
2209      int length;
2210 {
2211     int error, outCount;
2212     outCount = OutputToProcess(NoProc, data, length, &error);
2213     if (outCount < length) {
2214         DisplayFatalError(_("Error writing to display"), error, 1);
2215     }
2216 }
2217
2218 void
2219 PackHolding(packed, holding)
2220      char packed[];
2221      char *holding;
2222 {
2223     char *p = holding;
2224     char *q = packed;
2225     int runlength = 0;
2226     int curr = 9999;
2227     do {
2228         if (*p == curr) {
2229             runlength++;
2230         } else {
2231             switch (runlength) {
2232               case 0:
2233                 break;
2234               case 1:
2235                 *q++ = curr;
2236                 break;
2237               case 2:
2238                 *q++ = curr;
2239                 *q++ = curr;
2240                 break;
2241               default:
2242                 sprintf(q, "%d", runlength);
2243                 while (*q) q++;
2244                 *q++ = curr;
2245                 break;
2246             }
2247             runlength = 1;
2248             curr = *p;
2249         }
2250     } while (*p++);
2251     *q = NULLCHAR;
2252 }
2253
2254 /* Telnet protocol requests from the front end */
2255 void
2256 TelnetRequest(ddww, option)
2257      unsigned char ddww, option;
2258 {
2259     unsigned char msg[3];
2260     int outCount, outError;
2261
2262     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2263
2264     if (appData.debugMode) {
2265         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2266         switch (ddww) {
2267           case TN_DO:
2268             ddwwStr = "DO";
2269             break;
2270           case TN_DONT:
2271             ddwwStr = "DONT";
2272             break;
2273           case TN_WILL:
2274             ddwwStr = "WILL";
2275             break;
2276           case TN_WONT:
2277             ddwwStr = "WONT";
2278             break;
2279           default:
2280             ddwwStr = buf1;
2281             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2282             break;
2283         }
2284         switch (option) {
2285           case TN_ECHO:
2286             optionStr = "ECHO";
2287             break;
2288           default:
2289             optionStr = buf2;
2290             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2291             break;
2292         }
2293         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2294     }
2295     msg[0] = TN_IAC;
2296     msg[1] = ddww;
2297     msg[2] = option;
2298     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2299     if (outCount < 3) {
2300         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2301     }
2302 }
2303
2304 void
2305 DoEcho()
2306 {
2307     if (!appData.icsActive) return;
2308     TelnetRequest(TN_DO, TN_ECHO);
2309 }
2310
2311 void
2312 DontEcho()
2313 {
2314     if (!appData.icsActive) return;
2315     TelnetRequest(TN_DONT, TN_ECHO);
2316 }
2317
2318 void
2319 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2320 {
2321     /* put the holdings sent to us by the server on the board holdings area */
2322     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2323     char p;
2324     ChessSquare piece;
2325
2326     if(gameInfo.holdingsWidth < 2)  return;
2327     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2328         return; // prevent overwriting by pre-board holdings
2329
2330     if( (int)lowestPiece >= BlackPawn ) {
2331         holdingsColumn = 0;
2332         countsColumn = 1;
2333         holdingsStartRow = BOARD_HEIGHT-1;
2334         direction = -1;
2335     } else {
2336         holdingsColumn = BOARD_WIDTH-1;
2337         countsColumn = BOARD_WIDTH-2;
2338         holdingsStartRow = 0;
2339         direction = 1;
2340     }
2341
2342     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2343         board[i][holdingsColumn] = EmptySquare;
2344         board[i][countsColumn]   = (ChessSquare) 0;
2345     }
2346     while( (p=*holdings++) != NULLCHAR ) {
2347         piece = CharToPiece( ToUpper(p) );
2348         if(piece == EmptySquare) continue;
2349         /*j = (int) piece - (int) WhitePawn;*/
2350         j = PieceToNumber(piece);
2351         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2352         if(j < 0) continue;               /* should not happen */
2353         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2354         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2355         board[holdingsStartRow+j*direction][countsColumn]++;
2356     }
2357 }
2358
2359
2360 void
2361 VariantSwitch(Board board, VariantClass newVariant)
2362 {
2363    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2364    static Board oldBoard;
2365
2366    startedFromPositionFile = FALSE;
2367    if(gameInfo.variant == newVariant) return;
2368
2369    /* [HGM] This routine is called each time an assignment is made to
2370     * gameInfo.variant during a game, to make sure the board sizes
2371     * are set to match the new variant. If that means adding or deleting
2372     * holdings, we shift the playing board accordingly
2373     * This kludge is needed because in ICS observe mode, we get boards
2374     * of an ongoing game without knowing the variant, and learn about the
2375     * latter only later. This can be because of the move list we requested,
2376     * in which case the game history is refilled from the beginning anyway,
2377     * but also when receiving holdings of a crazyhouse game. In the latter
2378     * case we want to add those holdings to the already received position.
2379     */
2380
2381
2382    if (appData.debugMode) {
2383      fprintf(debugFP, "Switch board from %s to %s\n",
2384              VariantName(gameInfo.variant), VariantName(newVariant));
2385      setbuf(debugFP, NULL);
2386    }
2387    shuffleOpenings = 0;       /* [HGM] shuffle */
2388    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2389    switch(newVariant)
2390      {
2391      case VariantShogi:
2392        newWidth = 9;  newHeight = 9;
2393        gameInfo.holdingsSize = 7;
2394      case VariantBughouse:
2395      case VariantCrazyhouse:
2396        newHoldingsWidth = 2; break;
2397      case VariantGreat:
2398        newWidth = 10;
2399      case VariantSuper:
2400        newHoldingsWidth = 2;
2401        gameInfo.holdingsSize = 8;
2402        break;
2403      case VariantGothic:
2404      case VariantCapablanca:
2405      case VariantCapaRandom:
2406        newWidth = 10;
2407      default:
2408        newHoldingsWidth = gameInfo.holdingsSize = 0;
2409      };
2410
2411    if(newWidth  != gameInfo.boardWidth  ||
2412       newHeight != gameInfo.boardHeight ||
2413       newHoldingsWidth != gameInfo.holdingsWidth ) {
2414
2415      /* shift position to new playing area, if needed */
2416      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2417        for(i=0; i<BOARD_HEIGHT; i++)
2418          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2419            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420              board[i][j];
2421        for(i=0; i<newHeight; i++) {
2422          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2423          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2424        }
2425      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2426        for(i=0; i<BOARD_HEIGHT; i++)
2427          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2428            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429              board[i][j];
2430      }
2431      gameInfo.boardWidth  = newWidth;
2432      gameInfo.boardHeight = newHeight;
2433      gameInfo.holdingsWidth = newHoldingsWidth;
2434      gameInfo.variant = newVariant;
2435      InitDrawingSizes(-2, 0);
2436    } else gameInfo.variant = newVariant;
2437    CopyBoard(oldBoard, board);   // remember correctly formatted board
2438      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2439    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2440 }
2441
2442 static int loggedOn = FALSE;
2443
2444 /*-- Game start info cache: --*/
2445 int gs_gamenum;
2446 char gs_kind[MSG_SIZ];
2447 static char player1Name[128] = "";
2448 static char player2Name[128] = "";
2449 static char cont_seq[] = "\n\\   ";
2450 static int player1Rating = -1;
2451 static int player2Rating = -1;
2452 /*----------------------------*/
2453
2454 ColorClass curColor = ColorNormal;
2455 int suppressKibitz = 0;
2456
2457 // [HGM] seekgraph
2458 Boolean soughtPending = FALSE;
2459 Boolean seekGraphUp;
2460 #define MAX_SEEK_ADS 200
2461 #define SQUARE 0x80
2462 char *seekAdList[MAX_SEEK_ADS];
2463 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2464 float tcList[MAX_SEEK_ADS];
2465 char colorList[MAX_SEEK_ADS];
2466 int nrOfSeekAds = 0;
2467 int minRating = 1010, maxRating = 2800;
2468 int hMargin = 10, vMargin = 20, h, w;
2469 extern int squareSize, lineGap;
2470
2471 void
2472 PlotSeekAd(int i)
2473 {
2474         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2475         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2476         if(r < minRating+100 && r >=0 ) r = minRating+100;
2477         if(r > maxRating) r = maxRating;
2478         if(tc < 1.) tc = 1.;
2479         if(tc > 95.) tc = 95.;
2480         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2481         y = ((double)r - minRating)/(maxRating - minRating)
2482             * (h-vMargin-squareSize/8-1) + vMargin;
2483         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2484         if(strstr(seekAdList[i], " u ")) color = 1;
2485         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2486            !strstr(seekAdList[i], "bullet") &&
2487            !strstr(seekAdList[i], "blitz") &&
2488            !strstr(seekAdList[i], "standard") ) color = 2;
2489         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2490         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2491 }
2492
2493 void
2494 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2495 {
2496         char buf[MSG_SIZ], *ext = "";
2497         VariantClass v = StringToVariant(type);
2498         if(strstr(type, "wild")) {
2499             ext = type + 4; // append wild number
2500             if(v == VariantFischeRandom) type = "chess960"; else
2501             if(v == VariantLoadable) type = "setup"; else
2502             type = VariantName(v);
2503         }
2504         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2505         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2506             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2507             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2508             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2509             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2510             seekNrList[nrOfSeekAds] = nr;
2511             zList[nrOfSeekAds] = 0;
2512             seekAdList[nrOfSeekAds++] = StrSave(buf);
2513             if(plot) PlotSeekAd(nrOfSeekAds-1);
2514         }
2515 }
2516
2517 void
2518 EraseSeekDot(int i)
2519 {
2520     int x = xList[i], y = yList[i], d=squareSize/4, k;
2521     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2522     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2523     // now replot every dot that overlapped
2524     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2525         int xx = xList[k], yy = yList[k];
2526         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2527             DrawSeekDot(xx, yy, colorList[k]);
2528     }
2529 }
2530
2531 void
2532 RemoveSeekAd(int nr)
2533 {
2534         int i;
2535         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2536             EraseSeekDot(i);
2537             if(seekAdList[i]) free(seekAdList[i]);
2538             seekAdList[i] = seekAdList[--nrOfSeekAds];
2539             seekNrList[i] = seekNrList[nrOfSeekAds];
2540             ratingList[i] = ratingList[nrOfSeekAds];
2541             colorList[i]  = colorList[nrOfSeekAds];
2542             tcList[i] = tcList[nrOfSeekAds];
2543             xList[i]  = xList[nrOfSeekAds];
2544             yList[i]  = yList[nrOfSeekAds];
2545             zList[i]  = zList[nrOfSeekAds];
2546             seekAdList[nrOfSeekAds] = NULL;
2547             break;
2548         }
2549 }
2550
2551 Boolean
2552 MatchSoughtLine(char *line)
2553 {
2554     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2555     int nr, base, inc, u=0; char dummy;
2556
2557     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2558        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2559        (u=1) &&
2560        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2561         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2562         // match: compact and save the line
2563         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2564         return TRUE;
2565     }
2566     return FALSE;
2567 }
2568
2569 int
2570 DrawSeekGraph()
2571 {
2572     int i;
2573     if(!seekGraphUp) return FALSE;
2574     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2575     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2576
2577     DrawSeekBackground(0, 0, w, h);
2578     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2579     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2580     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2581         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2582         yy = h-1-yy;
2583         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2584         if(i%500 == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2588         }
2589     }
2590     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2591     for(i=1; i<100; i+=(i<10?1:5)) {
2592         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2593         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2594         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2598         }
2599     }
2600     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2601     return TRUE;
2602 }
2603
2604 int SeekGraphClick(ClickType click, int x, int y, int moving)
2605 {
2606     static int lastDown = 0, displayed = 0, lastSecond;
2607     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2608         if(click == Release || moving) return FALSE;
2609         nrOfSeekAds = 0;
2610         soughtPending = TRUE;
2611         SendToICS(ics_prefix);
2612         SendToICS("sought\n"); // should this be "sought all"?
2613     } else { // issue challenge based on clicked ad
2614         int dist = 10000; int i, closest = 0, second = 0;
2615         for(i=0; i<nrOfSeekAds; i++) {
2616             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2617             if(d < dist) { dist = d; closest = i; }
2618             second += (d - zList[i] < 120); // count in-range ads
2619             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2620         }
2621         if(dist < 120) {
2622             char buf[MSG_SIZ];
2623             second = (second > 1);
2624             if(displayed != closest || second != lastSecond) {
2625                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2626                 lastSecond = second; displayed = closest;
2627             }
2628             if(click == Press) {
2629                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2630                 lastDown = closest;
2631                 return TRUE;
2632             } // on press 'hit', only show info
2633             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2634             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2635             SendToICS(ics_prefix);
2636             SendToICS(buf);
2637             return TRUE; // let incoming board of started game pop down the graph
2638         } else if(click == Release) { // release 'miss' is ignored
2639             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2640             if(moving == 2) { // right up-click
2641                 nrOfSeekAds = 0; // refresh graph
2642                 soughtPending = TRUE;
2643                 SendToICS(ics_prefix);
2644                 SendToICS("sought\n"); // should this be "sought all"?
2645             }
2646             return TRUE;
2647         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2648         // press miss or release hit 'pop down' seek graph
2649         seekGraphUp = FALSE;
2650         DrawPosition(TRUE, NULL);
2651     }
2652     return TRUE;
2653 }
2654
2655 void
2656 read_from_ics(isr, closure, data, count, error)
2657      InputSourceRef isr;
2658      VOIDSTAR closure;
2659      char *data;
2660      int count;
2661      int error;
2662 {
2663 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2664 #define STARTED_NONE 0
2665 #define STARTED_MOVES 1
2666 #define STARTED_BOARD 2
2667 #define STARTED_OBSERVE 3
2668 #define STARTED_HOLDINGS 4
2669 #define STARTED_CHATTER 5
2670 #define STARTED_COMMENT 6
2671 #define STARTED_MOVES_NOHIDE 7
2672
2673     static int started = STARTED_NONE;
2674     static char parse[20000];
2675     static int parse_pos = 0;
2676     static char buf[BUF_SIZE + 1];
2677     static int firstTime = TRUE, intfSet = FALSE;
2678     static ColorClass prevColor = ColorNormal;
2679     static int savingComment = FALSE;
2680     static int cmatch = 0; // continuation sequence match
2681     char *bp;
2682     char str[MSG_SIZ];
2683     int i, oldi;
2684     int buf_len;
2685     int next_out;
2686     int tkind;
2687     int backup;    /* [DM] For zippy color lines */
2688     char *p;
2689     char talker[MSG_SIZ]; // [HGM] chat
2690     int channel;
2691
2692     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2693
2694     if (appData.debugMode) {
2695       if (!error) {
2696         fprintf(debugFP, "<ICS: ");
2697         show_bytes(debugFP, data, count);
2698         fprintf(debugFP, "\n");
2699       }
2700     }
2701
2702     if (appData.debugMode) { int f = forwardMostMove;
2703         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2704                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2705                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2706     }
2707     if (count > 0) {
2708         /* If last read ended with a partial line that we couldn't parse,
2709            prepend it to the new read and try again. */
2710         if (leftover_len > 0) {
2711             for (i=0; i<leftover_len; i++)
2712               buf[i] = buf[leftover_start + i];
2713         }
2714
2715     /* copy new characters into the buffer */
2716     bp = buf + leftover_len;
2717     buf_len=leftover_len;
2718     for (i=0; i<count; i++)
2719     {
2720         // ignore these
2721         if (data[i] == '\r')
2722             continue;
2723
2724         // join lines split by ICS?
2725         if (!appData.noJoin)
2726         {
2727             /*
2728                 Joining just consists of finding matches against the
2729                 continuation sequence, and discarding that sequence
2730                 if found instead of copying it.  So, until a match
2731                 fails, there's nothing to do since it might be the
2732                 complete sequence, and thus, something we don't want
2733                 copied.
2734             */
2735             if (data[i] == cont_seq[cmatch])
2736             {
2737                 cmatch++;
2738                 if (cmatch == strlen(cont_seq))
2739                 {
2740                     cmatch = 0; // complete match.  just reset the counter
2741
2742                     /*
2743                         it's possible for the ICS to not include the space
2744                         at the end of the last word, making our [correct]
2745                         join operation fuse two separate words.  the server
2746                         does this when the space occurs at the width setting.
2747                     */
2748                     if (!buf_len || buf[buf_len-1] != ' ')
2749                     {
2750                         *bp++ = ' ';
2751                         buf_len++;
2752                     }
2753                 }
2754                 continue;
2755             }
2756             else if (cmatch)
2757             {
2758                 /*
2759                     match failed, so we have to copy what matched before
2760                     falling through and copying this character.  In reality,
2761                     this will only ever be just the newline character, but
2762                     it doesn't hurt to be precise.
2763                 */
2764                 strncpy(bp, cont_seq, cmatch);
2765                 bp += cmatch;
2766                 buf_len += cmatch;
2767                 cmatch = 0;
2768             }
2769         }
2770
2771         // copy this char
2772         *bp++ = data[i];
2773         buf_len++;
2774     }
2775
2776         buf[buf_len] = NULLCHAR;
2777 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2778         next_out = 0;
2779         leftover_start = 0;
2780
2781         i = 0;
2782         while (i < buf_len) {
2783             /* Deal with part of the TELNET option negotiation
2784                protocol.  We refuse to do anything beyond the
2785                defaults, except that we allow the WILL ECHO option,
2786                which ICS uses to turn off password echoing when we are
2787                directly connected to it.  We reject this option
2788                if localLineEditing mode is on (always on in xboard)
2789                and we are talking to port 23, which might be a real
2790                telnet server that will try to keep WILL ECHO on permanently.
2791              */
2792             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2793                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2794                 unsigned char option;
2795                 oldi = i;
2796                 switch ((unsigned char) buf[++i]) {
2797                   case TN_WILL:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WILL ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (remoteEchoOption) break;
2807                         if (appData.localLineEditing &&
2808                             atoi(appData.icsPort) == TN_PORT) {
2809                             TelnetRequest(TN_DONT, TN_ECHO);
2810                         } else {
2811                             EchoOff();
2812                             TelnetRequest(TN_DO, TN_ECHO);
2813                             remoteEchoOption = TRUE;
2814                         }
2815                         break;
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we don't want it. */
2820                         TelnetRequest(TN_DONT, option);
2821                         break;
2822                     }
2823                     break;
2824                   case TN_WONT:
2825                     if (appData.debugMode)
2826                       fprintf(debugFP, "\n<WONT ");
2827                     switch (option = (unsigned char) buf[++i]) {
2828                       case TN_ECHO:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "ECHO ");
2831                         /* Reply only if this is a change, according
2832                            to the protocol rules. */
2833                         if (!remoteEchoOption) break;
2834                         EchoOn();
2835                         TelnetRequest(TN_DONT, TN_ECHO);
2836                         remoteEchoOption = FALSE;
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", (unsigned char) option);
2841                         /* Whatever this is, it must already be turned
2842                            off, because we never agree to turn on
2843                            anything non-default, so according to the
2844                            protocol rules, we don't reply. */
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DO:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DO ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         /* Whatever this is, we refuse to do it. */
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         TelnetRequest(TN_WONT, option);
2857                         break;
2858                     }
2859                     break;
2860                   case TN_DONT:
2861                     if (appData.debugMode)
2862                       fprintf(debugFP, "\n<DONT ");
2863                     switch (option = (unsigned char) buf[++i]) {
2864                       default:
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         /* Whatever this is, we are already not doing
2868                            it, because we never agree to do anything
2869                            non-default, so according to the protocol
2870                            rules, we don't reply. */
2871                         break;
2872                     }
2873                     break;
2874                   case TN_IAC:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<IAC ");
2877                     /* Doubled IAC; pass it through */
2878                     i--;
2879                     break;
2880                   default:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2883                     /* Drop all other telnet commands on the floor */
2884                     break;
2885                 }
2886                 if (oldi > next_out)
2887                   SendToPlayer(&buf[next_out], oldi - next_out);
2888                 if (++i > next_out)
2889                   next_out = i;
2890                 continue;
2891             }
2892
2893             /* OK, this at least will *usually* work */
2894             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2895                 loggedOn = TRUE;
2896             }
2897
2898             if (loggedOn && !intfSet) {
2899                 if (ics_type == ICS_ICC) {
2900                   snprintf(str, MSG_SIZ,
2901                           "/set-quietly interface %s\n/set-quietly style 12\n",
2902                           programVersion);
2903                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2904                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2905                 } else if (ics_type == ICS_CHESSNET) {
2906                   snprintf(str, MSG_SIZ, "/style 12\n");
2907                 } else {
2908                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2909                   strcat(str, programVersion);
2910                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2913 #ifdef WIN32
2914                   strcat(str, "$iset nohighlight 1\n");
2915 #endif
2916                   strcat(str, "$iset lock 1\n$style 12\n");
2917                 }
2918                 SendToICS(str);
2919                 NotifyFrontendLogin();
2920                 intfSet = TRUE;
2921             }
2922
2923             if (started == STARTED_COMMENT) {
2924                 /* Accumulate characters in comment */
2925                 parse[parse_pos++] = buf[i];
2926                 if (buf[i] == '\n') {
2927                     parse[parse_pos] = NULLCHAR;
2928                     if(chattingPartner>=0) {
2929                         char mess[MSG_SIZ];
2930                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2931                         OutputChatMessage(chattingPartner, mess);
2932                         chattingPartner = -1;
2933                         next_out = i+1; // [HGM] suppress printing in ICS window
2934                     } else
2935                     if(!suppressKibitz) // [HGM] kibitz
2936                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2937                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2938                         int nrDigit = 0, nrAlph = 0, j;
2939                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2940                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2941                         parse[parse_pos] = NULLCHAR;
2942                         // try to be smart: if it does not look like search info, it should go to
2943                         // ICS interaction window after all, not to engine-output window.
2944                         for(j=0; j<parse_pos; j++) { // count letters and digits
2945                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2946                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2947                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2948                         }
2949                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2950                             int depth=0; float score;
2951                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2952                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2953                                 pvInfoList[forwardMostMove-1].depth = depth;
2954                                 pvInfoList[forwardMostMove-1].score = 100*score;
2955                             }
2956                             OutputKibitz(suppressKibitz, parse);
2957                         } else {
2958                             char tmp[MSG_SIZ];
2959                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2960                             SendToPlayer(tmp, strlen(tmp));
2961                         }
2962                         next_out = i+1; // [HGM] suppress printing in ICS window
2963                     }
2964                     started = STARTED_NONE;
2965                 } else {
2966                     /* Don't match patterns against characters in comment */
2967                     i++;
2968                     continue;
2969                 }
2970             }
2971             if (started == STARTED_CHATTER) {
2972                 if (buf[i] != '\n') {
2973                     /* Don't match patterns against characters in chatter */
2974                     i++;
2975                     continue;
2976                 }
2977                 started = STARTED_NONE;
2978                 if(suppressKibitz) next_out = i+1;
2979             }
2980
2981             /* Kludge to deal with rcmd protocol */
2982             if (firstTime && looking_at(buf, &i, "\001*")) {
2983                 DisplayFatalError(&buf[1], 0, 1);
2984                 continue;
2985             } else {
2986                 firstTime = FALSE;
2987             }
2988
2989             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2990                 ics_type = ICS_ICC;
2991                 ics_prefix = "/";
2992                 if (appData.debugMode)
2993                   fprintf(debugFP, "ics_type %d\n", ics_type);
2994                 continue;
2995             }
2996             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2997                 ics_type = ICS_FICS;
2998                 ics_prefix = "$";
2999                 if (appData.debugMode)
3000                   fprintf(debugFP, "ics_type %d\n", ics_type);
3001                 continue;
3002             }
3003             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3004                 ics_type = ICS_CHESSNET;
3005                 ics_prefix = "/";
3006                 if (appData.debugMode)
3007                   fprintf(debugFP, "ics_type %d\n", ics_type);
3008                 continue;
3009             }
3010
3011             if (!loggedOn &&
3012                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3013                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3014                  looking_at(buf, &i, "will be \"*\""))) {
3015               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3016               continue;
3017             }
3018
3019             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3020               char buf[MSG_SIZ];
3021               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3022               DisplayIcsInteractionTitle(buf);
3023               have_set_title = TRUE;
3024             }
3025
3026             /* skip finger notes */
3027             if (started == STARTED_NONE &&
3028                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3029                  (buf[i] == '1' && buf[i+1] == '0')) &&
3030                 buf[i+2] == ':' && buf[i+3] == ' ') {
3031               started = STARTED_CHATTER;
3032               i += 3;
3033               continue;
3034             }
3035
3036             oldi = i;
3037             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3038             if(appData.seekGraph) {
3039                 if(soughtPending && MatchSoughtLine(buf+i)) {
3040                     i = strstr(buf+i, "rated") - buf;
3041                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3042                     next_out = leftover_start = i;
3043                     started = STARTED_CHATTER;
3044                     suppressKibitz = TRUE;
3045                     continue;
3046                 }
3047                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3048                         && looking_at(buf, &i, "* ads displayed")) {
3049                     soughtPending = FALSE;
3050                     seekGraphUp = TRUE;
3051                     DrawSeekGraph();
3052                     continue;
3053                 }
3054                 if(appData.autoRefresh) {
3055                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3056                         int s = (ics_type == ICS_ICC); // ICC format differs
3057                         if(seekGraphUp)
3058                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3059                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i; // suppress
3064                         continue;
3065                     }
3066                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3067                         char *p = star_match[0];
3068                         while(*p) {
3069                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3070                             while(*p && *p++ != ' '); // next
3071                         }
3072                         looking_at(buf, &i, "*% "); // eat prompt
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i;
3075                         continue;
3076                     }
3077                 }
3078             }
3079
3080             /* skip formula vars */
3081             if (started == STARTED_NONE &&
3082                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3083               started = STARTED_CHATTER;
3084               i += 3;
3085               continue;
3086             }
3087
3088             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3089             if (appData.autoKibitz && started == STARTED_NONE &&
3090                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3091                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3092                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3093                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3094                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3095                         suppressKibitz = TRUE;
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i;
3098                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3099                                 && (gameMode == IcsPlayingWhite)) ||
3100                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3101                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3102                             started = STARTED_CHATTER; // own kibitz we simply discard
3103                         else {
3104                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3105                             parse_pos = 0; parse[0] = NULLCHAR;
3106                             savingComment = TRUE;
3107                             suppressKibitz = gameMode != IcsObserving ? 2 :
3108                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3109                         }
3110                         continue;
3111                 } else
3112                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3113                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3114                          && atoi(star_match[0])) {
3115                     // suppress the acknowledgements of our own autoKibitz
3116                     char *p;
3117                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3119                     SendToPlayer(star_match[0], strlen(star_match[0]));
3120                     if(looking_at(buf, &i, "*% ")) // eat prompt
3121                         suppressKibitz = FALSE;
3122                     next_out = i;
3123                     continue;
3124                 }
3125             } // [HGM] kibitz: end of patch
3126
3127             // [HGM] chat: intercept tells by users for which we have an open chat window
3128             channel = -1;
3129             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3130                                            looking_at(buf, &i, "* whispers:") ||
3131                                            looking_at(buf, &i, "* kibitzes:") ||
3132                                            looking_at(buf, &i, "* shouts:") ||
3133                                            looking_at(buf, &i, "* c-shouts:") ||
3134                                            looking_at(buf, &i, "--> * ") ||
3135                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3139                 int p;
3140                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3141                 chattingPartner = -1;
3142
3143                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3146                     talker[0] = '['; strcat(talker, "] ");
3147                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3148                     chattingPartner = p; break;
3149                     }
3150                 } else
3151                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(!strcmp("kibitzes", chatPartner[p])) {
3154                         talker[0] = '['; strcat(talker, "] ");
3155                         chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("whispers", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3166                   if(buf[i-8] == '-' && buf[i-3] == 't')
3167                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3168                     if(!strcmp("c-shouts", chatPartner[p])) {
3169                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3170                         chattingPartner = p; break;
3171                     }
3172                   }
3173                   if(chattingPartner < 0)
3174                   for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("shouts", chatPartner[p])) {
3176                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3177                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3178                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                 }
3183                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3184                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3185                     talker[0] = 0; Colorize(ColorTell, FALSE);
3186                     chattingPartner = p; break;
3187                 }
3188                 if(chattingPartner<0) i = oldi; else {
3189                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3190                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     started = STARTED_COMMENT;
3193                     parse_pos = 0; parse[0] = NULLCHAR;
3194                     savingComment = 3 + chattingPartner; // counts as TRUE
3195                     suppressKibitz = TRUE;
3196                     continue;
3197                 }
3198             } // [HGM] chat: end of patch
3199
3200           backup = i;
3201             if (appData.zippyTalk || appData.zippyPlay) {
3202                 /* [DM] Backup address for color zippy lines */
3203 #if ZIPPY
3204                if (loggedOn == TRUE)
3205                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3206                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3207 #endif
3208             } // [DM] 'else { ' deleted
3209                 if (
3210                     /* Regular tells and says */
3211                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3212                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3213                     looking_at(buf, &i, "* says: ") ||
3214                     /* Don't color "message" or "messages" output */
3215                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3216                     looking_at(buf, &i, "*. * at *:*: ") ||
3217                     looking_at(buf, &i, "--* (*:*): ") ||
3218                     /* Message notifications (same color as tells) */
3219                     looking_at(buf, &i, "* has left a message ") ||
3220                     looking_at(buf, &i, "* just sent you a message:\n") ||
3221                     /* Whispers and kibitzes */
3222                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3223                     looking_at(buf, &i, "* kibitzes: ") ||
3224                     /* Channel tells */
3225                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3226
3227                   if (tkind == 1 && strchr(star_match[0], ':')) {
3228                       /* Avoid "tells you:" spoofs in channels */
3229                      tkind = 3;
3230                   }
3231                   if (star_match[0][0] == NULLCHAR ||
3232                       strchr(star_match[0], ' ') ||
3233                       (tkind == 3 && strchr(star_match[1], ' '))) {
3234                     /* Reject bogus matches */
3235                     i = oldi;
3236                   } else {
3237                     if (appData.colorize) {
3238                       if (oldi > next_out) {
3239                         SendToPlayer(&buf[next_out], oldi - next_out);
3240                         next_out = oldi;
3241                       }
3242                       switch (tkind) {
3243                       case 1:
3244                         Colorize(ColorTell, FALSE);
3245                         curColor = ColorTell;
3246                         break;
3247                       case 2:
3248                         Colorize(ColorKibitz, FALSE);
3249                         curColor = ColorKibitz;
3250                         break;
3251                       case 3:
3252                         p = strrchr(star_match[1], '(');
3253                         if (p == NULL) {
3254                           p = star_match[1];
3255                         } else {
3256                           p++;
3257                         }
3258                         if (atoi(p) == 1) {
3259                           Colorize(ColorChannel1, FALSE);
3260                           curColor = ColorChannel1;
3261                         } else {
3262                           Colorize(ColorChannel, FALSE);
3263                           curColor = ColorChannel;
3264                         }
3265                         break;
3266                       case 5:
3267                         curColor = ColorNormal;
3268                         break;
3269                       }
3270                     }
3271                     if (started == STARTED_NONE && appData.autoComment &&
3272                         (gameMode == IcsObserving ||
3273                          gameMode == IcsPlayingWhite ||
3274                          gameMode == IcsPlayingBlack)) {
3275                       parse_pos = i - oldi;
3276                       memcpy(parse, &buf[oldi], parse_pos);
3277                       parse[parse_pos] = NULLCHAR;
3278                       started = STARTED_COMMENT;
3279                       savingComment = TRUE;
3280                     } else {
3281                       started = STARTED_CHATTER;
3282                       savingComment = FALSE;
3283                     }
3284                     loggedOn = TRUE;
3285                     continue;
3286                   }
3287                 }
3288
3289                 if (looking_at(buf, &i, "* s-shouts: ") ||
3290                     looking_at(buf, &i, "* c-shouts: ")) {
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorSShout, FALSE);
3297                         curColor = ColorSShout;
3298                     }
3299                     loggedOn = TRUE;
3300                     started = STARTED_CHATTER;
3301                     continue;
3302                 }
3303
3304                 if (looking_at(buf, &i, "--->")) {
3305                     loggedOn = TRUE;
3306                     continue;
3307                 }
3308
3309                 if (looking_at(buf, &i, "* shouts: ") ||
3310                     looking_at(buf, &i, "--> ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorShout, FALSE);
3317                         curColor = ColorShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at( buf, &i, "Challenge:")) {
3325                     if (appData.colorize) {
3326                         if (oldi > next_out) {
3327                             SendToPlayer(&buf[next_out], oldi - next_out);
3328                             next_out = oldi;
3329                         }
3330                         Colorize(ColorChallenge, FALSE);
3331                         curColor = ColorChallenge;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* offers you") ||
3338                     looking_at(buf, &i, "* offers to be") ||
3339                     looking_at(buf, &i, "* would like to") ||
3340                     looking_at(buf, &i, "* requests to") ||
3341                     looking_at(buf, &i, "Your opponent offers") ||
3342                     looking_at(buf, &i, "Your opponent requests")) {
3343
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorRequest, FALSE);
3350                         curColor = ColorRequest;
3351                     }
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "* (*) seeking")) {
3356                     if (appData.colorize) {
3357                         if (oldi > next_out) {
3358                             SendToPlayer(&buf[next_out], oldi - next_out);
3359                             next_out = oldi;
3360                         }
3361                         Colorize(ColorSeek, FALSE);
3362                         curColor = ColorSeek;
3363                     }
3364                     continue;
3365             }
3366
3367           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3368
3369             if (looking_at(buf, &i, "\\   ")) {
3370                 if (prevColor != ColorNormal) {
3371                     if (oldi > next_out) {
3372                         SendToPlayer(&buf[next_out], oldi - next_out);
3373                         next_out = oldi;
3374                     }
3375                     Colorize(prevColor, TRUE);
3376                     curColor = prevColor;
3377                 }
3378                 if (savingComment) {
3379                     parse_pos = i - oldi;
3380                     memcpy(parse, &buf[oldi], parse_pos);
3381                     parse[parse_pos] = NULLCHAR;
3382                     started = STARTED_COMMENT;
3383                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3384                         chattingPartner = savingComment - 3; // kludge to remember the box
3385                 } else {
3386                     started = STARTED_CHATTER;
3387                 }
3388                 continue;
3389             }
3390
3391             if (looking_at(buf, &i, "Black Strength :") ||
3392                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3393                 looking_at(buf, &i, "<10>") ||
3394                 looking_at(buf, &i, "#@#")) {
3395                 /* Wrong board style */
3396                 loggedOn = TRUE;
3397                 SendToICS(ics_prefix);
3398                 SendToICS("set style 12\n");
3399                 SendToICS(ics_prefix);
3400                 SendToICS("refresh\n");
3401                 continue;
3402             }
3403
3404             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3405                 ICSInitScript();
3406                 have_sent_ICS_logon = 1;
3407                 continue;
3408             }
3409
3410             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3411                 (looking_at(buf, &i, "\n<12> ") ||
3412                  looking_at(buf, &i, "<12> "))) {
3413                 loggedOn = TRUE;
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_BOARD;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3424                 looking_at(buf, &i, "<b1> ")) {
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_HOLDINGS;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3435                 loggedOn = TRUE;
3436                 /* Header for a move list -- first line */
3437
3438                 switch (ics_getting_history) {
3439                   case H_FALSE:
3440                     switch (gameMode) {
3441                       case IcsIdle:
3442                       case BeginningOfGame:
3443                         /* User typed "moves" or "oldmoves" while we
3444                            were idle.  Pretend we asked for these
3445                            moves and soak them up so user can step
3446                            through them and/or save them.
3447                            */
3448                         Reset(FALSE, TRUE);
3449                         gameMode = IcsObserving;
3450                         ModeHighlight();
3451                         ics_gamenum = -1;
3452                         ics_getting_history = H_GOT_UNREQ_HEADER;
3453                         break;
3454                       case EditGame: /*?*/
3455                       case EditPosition: /*?*/
3456                         /* Should above feature work in these modes too? */
3457                         /* For now it doesn't */
3458                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3459                         break;
3460                       default:
3461                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3462                         break;
3463                     }
3464                     break;
3465                   case H_REQUESTED:
3466                     /* Is this the right one? */
3467                     if (gameInfo.white && gameInfo.black &&
3468                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3469                         strcmp(gameInfo.black, star_match[2]) == 0) {
3470                         /* All is well */
3471                         ics_getting_history = H_GOT_REQ_HEADER;
3472                     }
3473                     break;
3474                   case H_GOT_REQ_HEADER:
3475                   case H_GOT_UNREQ_HEADER:
3476                   case H_GOT_UNWANTED_HEADER:
3477                   case H_GETTING_MOVES:
3478                     /* Should not happen */
3479                     DisplayError(_("Error gathering move list: two headers"), 0);
3480                     ics_getting_history = H_FALSE;
3481                     break;
3482                 }
3483
3484                 /* Save player ratings into gameInfo if needed */
3485                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3486                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3487                     (gameInfo.whiteRating == -1 ||
3488                      gameInfo.blackRating == -1)) {
3489
3490                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3491                     gameInfo.blackRating = string_to_rating(star_match[3]);
3492                     if (appData.debugMode)
3493                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3494                               gameInfo.whiteRating, gameInfo.blackRating);
3495                 }
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i,
3500               "* * match, initial time: * minute*, increment: * second")) {
3501                 /* Header for a move list -- second line */
3502                 /* Initial board will follow if this is a wild game */
3503                 if (gameInfo.event != NULL) free(gameInfo.event);
3504                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3505                 gameInfo.event = StrSave(str);
3506                 /* [HGM] we switched variant. Translate boards if needed. */
3507                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3508                 continue;
3509             }
3510
3511             if (looking_at(buf, &i, "Move  ")) {
3512                 /* Beginning of a move list */
3513                 switch (ics_getting_history) {
3514                   case H_FALSE:
3515                     /* Normally should not happen */
3516                     /* Maybe user hit reset while we were parsing */
3517                     break;
3518                   case H_REQUESTED:
3519                     /* Happens if we are ignoring a move list that is not
3520                      * the one we just requested.  Common if the user
3521                      * tries to observe two games without turning off
3522                      * getMoveList */
3523                     break;
3524                   case H_GETTING_MOVES:
3525                     /* Should not happen */
3526                     DisplayError(_("Error gathering move list: nested"), 0);
3527                     ics_getting_history = H_FALSE;
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                     ics_getting_history = H_GETTING_MOVES;
3531                     started = STARTED_MOVES;
3532                     parse_pos = 0;
3533                     if (oldi > next_out) {
3534                         SendToPlayer(&buf[next_out], oldi - next_out);
3535                     }
3536                     break;
3537                   case H_GOT_UNREQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES_NOHIDE;
3540                     parse_pos = 0;
3541                     break;
3542                   case H_GOT_UNWANTED_HEADER:
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "% ") ||
3550                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3551                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3552                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3553                     soughtPending = FALSE;
3554                     seekGraphUp = TRUE;
3555                     DrawSeekGraph();
3556                 }
3557                 if(suppressKibitz) next_out = i;
3558                 savingComment = FALSE;
3559                 suppressKibitz = 0;
3560                 switch (started) {
3561                   case STARTED_MOVES:
3562                   case STARTED_MOVES_NOHIDE:
3563                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3564                     parse[parse_pos + i - oldi] = NULLCHAR;
3565                     ParseGameHistory(parse);
3566 #if ZIPPY
3567                     if (appData.zippyPlay && first.initDone) {
3568                         FeedMovesToProgram(&first, forwardMostMove);
3569                         if (gameMode == IcsPlayingWhite) {
3570                             if (WhiteOnMove(forwardMostMove)) {
3571                                 if (first.sendTime) {
3572                                   if (first.useColors) {
3573                                     SendToProgram("black\n", &first);
3574                                   }
3575                                   SendTimeRemaining(&first, TRUE);
3576                                 }
3577                                 if (first.useColors) {
3578                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3579                                 }
3580                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3581                                 first.maybeThinking = TRUE;
3582                             } else {
3583                                 if (first.usePlayother) {
3584                                   if (first.sendTime) {
3585                                     SendTimeRemaining(&first, TRUE);
3586                                   }
3587                                   SendToProgram("playother\n", &first);
3588                                   firstMove = FALSE;
3589                                 } else {
3590                                   firstMove = TRUE;
3591                                 }
3592                             }
3593                         } else if (gameMode == IcsPlayingBlack) {
3594                             if (!WhiteOnMove(forwardMostMove)) {
3595                                 if (first.sendTime) {
3596                                   if (first.useColors) {
3597                                     SendToProgram("white\n", &first);
3598                                   }
3599                                   SendTimeRemaining(&first, FALSE);
3600                                 }
3601                                 if (first.useColors) {
3602                                   SendToProgram("black\n", &first);
3603                                 }
3604                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3605                                 first.maybeThinking = TRUE;
3606                             } else {
3607                                 if (first.usePlayother) {
3608                                   if (first.sendTime) {
3609                                     SendTimeRemaining(&first, FALSE);
3610                                   }
3611                                   SendToProgram("playother\n", &first);
3612                                   firstMove = FALSE;
3613                                 } else {
3614                                   firstMove = TRUE;
3615                                 }
3616                             }
3617                         }
3618                     }
3619 #endif
3620                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3621                         /* Moves came from oldmoves or moves command
3622                            while we weren't doing anything else.
3623                            */
3624                         currentMove = forwardMostMove;
3625                         ClearHighlights();/*!!could figure this out*/
3626                         flipView = appData.flipView;
3627                         DrawPosition(TRUE, boards[currentMove]);
3628                         DisplayBothClocks();
3629                         snprintf(str, MSG_SIZ, "%s vs. %s",
3630                                 gameInfo.white, gameInfo.black);
3631                         DisplayTitle(str);
3632                         gameMode = IcsIdle;
3633                     } else {
3634                         /* Moves were history of an active game */
3635                         if (gameInfo.resultDetails != NULL) {
3636                             free(gameInfo.resultDetails);
3637                             gameInfo.resultDetails = NULL;
3638                         }
3639                     }
3640                     HistorySet(parseList, backwardMostMove,
3641                                forwardMostMove, currentMove-1);
3642                     DisplayMove(currentMove - 1);
3643                     if (started == STARTED_MOVES) next_out = i;
3644                     started = STARTED_NONE;
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647
3648                   case STARTED_OBSERVE:
3649                     started = STARTED_NONE;
3650                     SendToICS(ics_prefix);
3651                     SendToICS("refresh\n");
3652                     break;
3653
3654                   default:
3655                     break;
3656                 }
3657                 if(bookHit) { // [HGM] book: simulate book reply
3658                     static char bookMove[MSG_SIZ]; // a bit generous?
3659
3660                     programStats.nodes = programStats.depth = programStats.time =
3661                     programStats.score = programStats.got_only_move = 0;
3662                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3663
3664                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3665                     strcat(bookMove, bookHit);
3666                     HandleMachineMove(bookMove, &first);
3667                 }
3668                 continue;
3669             }
3670
3671             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3672                  started == STARTED_HOLDINGS ||
3673                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3674                 /* Accumulate characters in move list or board */
3675                 parse[parse_pos++] = buf[i];
3676             }
3677
3678             /* Start of game messages.  Mostly we detect start of game
3679                when the first board image arrives.  On some versions
3680                of the ICS, though, we need to do a "refresh" after starting
3681                to observe in order to get the current board right away. */
3682             if (looking_at(buf, &i, "Adding game * to observation list")) {
3683                 started = STARTED_OBSERVE;
3684                 continue;
3685             }
3686
3687             /* Handle auto-observe */
3688             if (appData.autoObserve &&
3689                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3690                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3691                 char *player;
3692                 /* Choose the player that was highlighted, if any. */
3693                 if (star_match[0][0] == '\033' ||
3694                     star_match[1][0] != '\033') {
3695                     player = star_match[0];
3696                 } else {
3697                     player = star_match[2];
3698                 }
3699                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3700                         ics_prefix, StripHighlightAndTitle(player));
3701                 SendToICS(str);
3702
3703                 /* Save ratings from notify string */
3704                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3705                 player1Rating = string_to_rating(star_match[1]);
3706                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3707                 player2Rating = string_to_rating(star_match[3]);
3708
3709                 if (appData.debugMode)
3710                   fprintf(debugFP,
3711                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3712                           player1Name, player1Rating,
3713                           player2Name, player2Rating);
3714
3715                 continue;
3716             }
3717
3718             /* Deal with automatic examine mode after a game,
3719                and with IcsObserving -> IcsExamining transition */
3720             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3721                 looking_at(buf, &i, "has made you an examiner of game *")) {
3722
3723                 int gamenum = atoi(star_match[0]);
3724                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3725                     gamenum == ics_gamenum) {
3726                     /* We were already playing or observing this game;
3727                        no need to refetch history */
3728                     gameMode = IcsExamining;
3729                     if (pausing) {
3730                         pauseExamForwardMostMove = forwardMostMove;
3731                     } else if (currentMove < forwardMostMove) {
3732                         ForwardInner(forwardMostMove);
3733                     }
3734                 } else {
3735                     /* I don't think this case really can happen */
3736                     SendToICS(ics_prefix);
3737                     SendToICS("refresh\n");
3738                 }
3739                 continue;
3740             }
3741
3742             /* Error messages */
3743 //          if (ics_user_moved) {
3744             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3745                 if (looking_at(buf, &i, "Illegal move") ||
3746                     looking_at(buf, &i, "Not a legal move") ||
3747                     looking_at(buf, &i, "Your king is in check") ||
3748                     looking_at(buf, &i, "It isn't your turn") ||
3749                     looking_at(buf, &i, "It is not your move")) {
3750                     /* Illegal move */
3751                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3752                         currentMove = forwardMostMove-1;
3753                         DisplayMove(currentMove - 1); /* before DMError */
3754                         DrawPosition(FALSE, boards[currentMove]);
3755                         SwitchClocks(forwardMostMove-1); // [HGM] race
3756                         DisplayBothClocks();
3757                     }
3758                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3759                     ics_user_moved = 0;
3760                     continue;
3761                 }
3762             }
3763
3764             if (looking_at(buf, &i, "still have time") ||
3765                 looking_at(buf, &i, "not out of time") ||
3766                 looking_at(buf, &i, "either player is out of time") ||
3767                 looking_at(buf, &i, "has timeseal; checking")) {
3768                 /* We must have called his flag a little too soon */
3769                 whiteFlag = blackFlag = FALSE;
3770                 continue;
3771             }
3772
3773             if (looking_at(buf, &i, "added * seconds to") ||
3774                 looking_at(buf, &i, "seconds were added to")) {
3775                 /* Update the clocks */
3776                 SendToICS(ics_prefix);
3777                 SendToICS("refresh\n");
3778                 continue;
3779             }
3780
3781             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3782                 ics_clock_paused = TRUE;
3783                 StopClocks();
3784                 continue;
3785             }
3786
3787             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3788                 ics_clock_paused = FALSE;
3789                 StartClocks();
3790                 continue;
3791             }
3792
3793             /* Grab player ratings from the Creating: message.
3794                Note we have to check for the special case when
3795                the ICS inserts things like [white] or [black]. */
3796             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3797                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3798                 /* star_matches:
3799                    0    player 1 name (not necessarily white)
3800                    1    player 1 rating
3801                    2    empty, white, or black (IGNORED)
3802                    3    player 2 name (not necessarily black)
3803                    4    player 2 rating
3804
3805                    The names/ratings are sorted out when the game
3806                    actually starts (below).
3807                 */
3808                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3809                 player1Rating = string_to_rating(star_match[1]);
3810                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3811                 player2Rating = string_to_rating(star_match[4]);
3812
3813                 if (appData.debugMode)
3814                   fprintf(debugFP,
3815                           "Ratings from 'Creating:' %s %d, %s %d\n",
3816                           player1Name, player1Rating,
3817                           player2Name, player2Rating);
3818
3819                 continue;
3820             }
3821
3822             /* Improved generic start/end-of-game messages */
3823             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3824                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3825                 /* If tkind == 0: */
3826                 /* star_match[0] is the game number */
3827                 /*           [1] is the white player's name */
3828                 /*           [2] is the black player's name */
3829                 /* For end-of-game: */
3830                 /*           [3] is the reason for the game end */
3831                 /*           [4] is a PGN end game-token, preceded by " " */
3832                 /* For start-of-game: */
3833                 /*           [3] begins with "Creating" or "Continuing" */
3834                 /*           [4] is " *" or empty (don't care). */
3835                 int gamenum = atoi(star_match[0]);
3836                 char *whitename, *blackname, *why, *endtoken;
3837                 ChessMove endtype = EndOfFile;
3838
3839                 if (tkind == 0) {
3840                   whitename = star_match[1];
3841                   blackname = star_match[2];
3842                   why = star_match[3];
3843                   endtoken = star_match[4];
3844                 } else {
3845                   whitename = star_match[1];
3846                   blackname = star_match[3];
3847                   why = star_match[5];
3848                   endtoken = star_match[6];
3849                 }
3850
3851                 /* Game start messages */
3852                 if (strncmp(why, "Creating ", 9) == 0 ||
3853                     strncmp(why, "Continuing ", 11) == 0) {
3854                     gs_gamenum = gamenum;
3855                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3856                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3857 #if ZIPPY
3858                     if (appData.zippyPlay) {
3859                         ZippyGameStart(whitename, blackname);
3860                     }
3861 #endif /*ZIPPY*/
3862                     partnerBoardValid = FALSE; // [HGM] bughouse
3863                     continue;
3864                 }
3865
3866                 /* Game end messages */
3867                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3868                     ics_gamenum != gamenum) {
3869                     continue;
3870                 }
3871                 while (endtoken[0] == ' ') endtoken++;
3872                 switch (endtoken[0]) {
3873                   case '*':
3874                   default:
3875                     endtype = GameUnfinished;
3876                     break;
3877                   case '0':
3878                     endtype = BlackWins;
3879                     break;
3880                   case '1':
3881                     if (endtoken[1] == '/')
3882                       endtype = GameIsDrawn;
3883                     else
3884                       endtype = WhiteWins;
3885                     break;
3886                 }
3887                 GameEnds(endtype, why, GE_ICS);
3888 #if ZIPPY
3889                 if (appData.zippyPlay && first.initDone) {
3890                     ZippyGameEnd(endtype, why);
3891                     if (first.pr == NULL) {
3892                       /* Start the next process early so that we'll
3893                          be ready for the next challenge */
3894                       StartChessProgram(&first);
3895                     }
3896                     /* Send "new" early, in case this command takes
3897                        a long time to finish, so that we'll be ready
3898                        for the next challenge. */
3899                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3900                     Reset(TRUE, TRUE);
3901                 }
3902 #endif /*ZIPPY*/
3903                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3904                 continue;
3905             }
3906
3907             if (looking_at(buf, &i, "Removing game * from observation") ||
3908                 looking_at(buf, &i, "no longer observing game *") ||
3909                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3910                 if (gameMode == IcsObserving &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       /* icsEngineAnalyze */
3914                       if (appData.icsEngineAnalyze) {
3915                             ExitAnalyzeMode();
3916                             ModeHighlight();
3917                       }
3918                       StopClocks();
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             if (looking_at(buf, &i, "no longer examining game *")) {
3927                 if (gameMode == IcsExamining &&
3928                     atoi(star_match[0]) == ics_gamenum)
3929                   {
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             /* Advance leftover_start past any newlines we find,
3938                so only partial lines can get reparsed */
3939             if (looking_at(buf, &i, "\n")) {
3940                 prevColor = curColor;
3941                 if (curColor != ColorNormal) {
3942                     if (oldi > next_out) {
3943                         SendToPlayer(&buf[next_out], oldi - next_out);
3944                         next_out = oldi;
3945                     }
3946                     Colorize(ColorNormal, FALSE);
3947                     curColor = ColorNormal;
3948                 }
3949                 if (started == STARTED_BOARD) {
3950                     started = STARTED_NONE;
3951                     parse[parse_pos] = NULLCHAR;
3952                     ParseBoard12(parse);
3953                     ics_user_moved = 0;
3954
3955                     /* Send premove here */
3956                     if (appData.premove) {
3957                       char str[MSG_SIZ];
3958                       if (currentMove == 0 &&
3959                           gameMode == IcsPlayingWhite &&
3960                           appData.premoveWhite) {
3961                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3962                         if (appData.debugMode)
3963                           fprintf(debugFP, "Sending premove:\n");
3964                         SendToICS(str);
3965                       } else if (currentMove == 1 &&
3966                                  gameMode == IcsPlayingBlack &&
3967                                  appData.premoveBlack) {
3968                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3969                         if (appData.debugMode)
3970                           fprintf(debugFP, "Sending premove:\n");
3971                         SendToICS(str);
3972                       } else if (gotPremove) {
3973                         gotPremove = 0;
3974                         ClearPremoveHighlights();
3975                         if (appData.debugMode)
3976                           fprintf(debugFP, "Sending premove:\n");
3977                           UserMoveEvent(premoveFromX, premoveFromY,
3978                                         premoveToX, premoveToY,
3979                                         premovePromoChar);
3980                       }
3981                     }
3982
3983                     /* Usually suppress following prompt */
3984                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3985                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3986                         if (looking_at(buf, &i, "*% ")) {
3987                             savingComment = FALSE;
3988                             suppressKibitz = 0;
3989                         }
3990                     }
3991                     next_out = i;
3992                 } else if (started == STARTED_HOLDINGS) {
3993                     int gamenum;
3994                     char new_piece[MSG_SIZ];
3995                     started = STARTED_NONE;
3996                     parse[parse_pos] = NULLCHAR;
3997                     if (appData.debugMode)
3998                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3999                                                         parse, currentMove);
4000                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4001                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4002                         if (gameInfo.variant == VariantNormal) {
4003                           /* [HGM] We seem to switch variant during a game!
4004                            * Presumably no holdings were displayed, so we have
4005                            * to move the position two files to the right to
4006                            * create room for them!
4007                            */
4008                           VariantClass newVariant;
4009                           switch(gameInfo.boardWidth) { // base guess on board width
4010                                 case 9:  newVariant = VariantShogi; break;
4011                                 case 10: newVariant = VariantGreat; break;
4012                                 default: newVariant = VariantCrazyhouse; break;
4013                           }
4014                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4015                           /* Get a move list just to see the header, which
4016                              will tell us whether this is really bug or zh */
4017                           if (ics_getting_history == H_FALSE) {
4018                             ics_getting_history = H_REQUESTED;
4019                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4020                             SendToICS(str);
4021                           }
4022                         }
4023                         new_piece[0] = NULLCHAR;
4024                         sscanf(parse, "game %d white [%s black [%s <- %s",
4025                                &gamenum, white_holding, black_holding,
4026                                new_piece);
4027                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4028                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4029                         /* [HGM] copy holdings to board holdings area */
4030                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4031                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4032                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4033 #if ZIPPY
4034                         if (appData.zippyPlay && first.initDone) {
4035                             ZippyHoldings(white_holding, black_holding,
4036                                           new_piece);
4037                         }
4038 #endif /*ZIPPY*/
4039                         if (tinyLayout || smallLayout) {
4040                             char wh[16], bh[16];
4041                             PackHolding(wh, white_holding);
4042                             PackHolding(bh, black_holding);
4043                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4044                                     gameInfo.white, gameInfo.black);
4045                         } else {
4046                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4047                                     gameInfo.white, white_holding,
4048                                     gameInfo.black, black_holding);
4049                         }
4050                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4051                         DrawPosition(FALSE, boards[currentMove]);
4052                         DisplayTitle(str);
4053                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to partner-board holdings area */
4060                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4061                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4062                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4063                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4064                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4065                       }
4066                     }
4067                     /* Suppress following prompt */
4068                     if (looking_at(buf, &i, "*% ")) {
4069                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4070                         savingComment = FALSE;
4071                         suppressKibitz = 0;
4072                     }
4073                     next_out = i;
4074                 }
4075                 continue;
4076             }
4077
4078             i++;                /* skip unparsed character and loop back */
4079         }
4080
4081         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4082 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4083 //          SendToPlayer(&buf[next_out], i - next_out);
4084             started != STARTED_HOLDINGS && leftover_start > next_out) {
4085             SendToPlayer(&buf[next_out], leftover_start - next_out);
4086             next_out = i;
4087         }
4088
4089         leftover_len = buf_len - leftover_start;
4090         /* if buffer ends with something we couldn't parse,
4091            reparse it after appending the next read */
4092
4093     } else if (count == 0) {
4094         RemoveInputSource(isr);
4095         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4096     } else {
4097         DisplayFatalError(_("Error reading from ICS"), error, 1);
4098     }
4099 }
4100
4101
4102 /* Board style 12 looks like this:
4103
4104    <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
4105
4106  * The "<12> " is stripped before it gets to this routine.  The two
4107  * trailing 0's (flip state and clock ticking) are later addition, and
4108  * some chess servers may not have them, or may have only the first.
4109  * Additional trailing fields may be added in the future.
4110  */
4111
4112 #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"
4113
4114 #define RELATION_OBSERVING_PLAYED    0
4115 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4116 #define RELATION_PLAYING_MYMOVE      1
4117 #define RELATION_PLAYING_NOTMYMOVE  -1
4118 #define RELATION_EXAMINING           2
4119 #define RELATION_ISOLATED_BOARD     -3
4120 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4121
4122 void
4123 ParseBoard12(string)
4124      char *string;
4125 {
4126     GameMode newGameMode;
4127     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4128     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4129     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4130     char to_play, board_chars[200];
4131     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4132     char black[32], white[32];
4133     Board board;
4134     int prevMove = currentMove;
4135     int ticking = 2;
4136     ChessMove moveType;
4137     int fromX, fromY, toX, toY;
4138     char promoChar;
4139     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4140     char *bookHit = NULL; // [HGM] book
4141     Boolean weird = FALSE, reqFlag = FALSE;
4142
4143     fromX = fromY = toX = toY = -1;
4144
4145     newGame = FALSE;
4146
4147     if (appData.debugMode)
4148       fprintf(debugFP, _("Parsing board: %s\n"), string);
4149
4150     move_str[0] = NULLCHAR;
4151     elapsed_time[0] = NULLCHAR;
4152     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4153         int  i = 0, j;
4154         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4155             if(string[i] == ' ') { ranks++; files = 0; }
4156             else files++;
4157             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4158             i++;
4159         }
4160         for(j = 0; j <i; j++) board_chars[j] = string[j];
4161         board_chars[i] = '\0';
4162         string += i + 1;
4163     }
4164     n = sscanf(string, PATTERN, &to_play, &double_push,
4165                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4166                &gamenum, white, black, &relation, &basetime, &increment,
4167                &white_stren, &black_stren, &white_time, &black_time,
4168                &moveNum, str, elapsed_time, move_str, &ics_flip,
4169                &ticking);
4170
4171     if (n < 21) {
4172         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4173         DisplayError(str, 0);
4174         return;
4175     }
4176
4177     /* Convert the move number to internal form */
4178     moveNum = (moveNum - 1) * 2;
4179     if (to_play == 'B') moveNum++;
4180     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4182                         0, 1);
4183       return;
4184     }
4185
4186     switch (relation) {
4187       case RELATION_OBSERVING_PLAYED:
4188       case RELATION_OBSERVING_STATIC:
4189         if (gamenum == -1) {
4190             /* Old ICC buglet */
4191             relation = RELATION_OBSERVING_STATIC;
4192         }
4193         newGameMode = IcsObserving;
4194         break;
4195       case RELATION_PLAYING_MYMOVE:
4196       case RELATION_PLAYING_NOTMYMOVE:
4197         newGameMode =
4198           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4199             IcsPlayingWhite : IcsPlayingBlack;
4200         break;
4201       case RELATION_EXAMINING:
4202         newGameMode = IcsExamining;
4203         break;
4204       case RELATION_ISOLATED_BOARD:
4205       default:
4206         /* Just display this board.  If user was doing something else,
4207            we will forget about it until the next board comes. */
4208         newGameMode = IcsIdle;
4209         break;
4210       case RELATION_STARTING_POSITION:
4211         newGameMode = gameMode;
4212         break;
4213     }
4214
4215     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4216          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4217       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4218       char *toSqr;
4219       for (k = 0; k < ranks; k++) {
4220         for (j = 0; j < files; j++)
4221           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4222         if(gameInfo.holdingsWidth > 1) {
4223              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4224              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4225         }
4226       }
4227       CopyBoard(partnerBoard, board);
4228       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4229         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4230         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4231       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4232       if(toSqr = strchr(str, '-')) {
4233         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4234         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4235       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4236       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4237       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4238       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4239       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4240       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4241                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4242       DisplayMessage(partnerStatus, "");
4243         partnerBoardValid = TRUE;
4244       return;
4245     }
4246
4247     /* Modify behavior for initial board display on move listing
4248        of wild games.
4249        */
4250     switch (ics_getting_history) {
4251       case H_FALSE:
4252       case H_REQUESTED:
4253         break;
4254       case H_GOT_REQ_HEADER:
4255       case H_GOT_UNREQ_HEADER:
4256         /* This is the initial position of the current game */
4257         gamenum = ics_gamenum;
4258         moveNum = 0;            /* old ICS bug workaround */
4259         if (to_play == 'B') {
4260           startedFromSetupPosition = TRUE;
4261           blackPlaysFirst = TRUE;
4262           moveNum = 1;
4263           if (forwardMostMove == 0) forwardMostMove = 1;
4264           if (backwardMostMove == 0) backwardMostMove = 1;
4265           if (currentMove == 0) currentMove = 1;
4266         }
4267         newGameMode = gameMode;
4268         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4269         break;
4270       case H_GOT_UNWANTED_HEADER:
4271         /* This is an initial board that we don't want */
4272         return;
4273       case H_GETTING_MOVES:
4274         /* Should not happen */
4275         DisplayError(_("Error gathering move list: extra board"), 0);
4276         ics_getting_history = H_FALSE;
4277         return;
4278     }
4279
4280    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4281                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4282      /* [HGM] We seem to have switched variant unexpectedly
4283       * Try to guess new variant from board size
4284       */
4285           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4286           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4287           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4288           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4289           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4290           if(!weird) newVariant = VariantNormal;
4291           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4292           /* Get a move list just to see the header, which
4293              will tell us whether this is really bug or zh */
4294           if (ics_getting_history == H_FALSE) {
4295             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4296             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4297             SendToICS(str);
4298           }
4299     }
4300
4301     /* Take action if this is the first board of a new game, or of a
4302        different game than is currently being displayed.  */
4303     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4304         relation == RELATION_ISOLATED_BOARD) {
4305
4306         /* Forget the old game and get the history (if any) of the new one */
4307         if (gameMode != BeginningOfGame) {
4308           Reset(TRUE, TRUE);
4309         }
4310         newGame = TRUE;
4311         if (appData.autoRaiseBoard) BoardToTop();
4312         prevMove = -3;
4313         if (gamenum == -1) {
4314             newGameMode = IcsIdle;
4315         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4316                    appData.getMoveList && !reqFlag) {
4317             /* Need to get game history */
4318             ics_getting_history = H_REQUESTED;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321         }
4322
4323         /* Initially flip the board to have black on the bottom if playing
4324            black or if the ICS flip flag is set, but let the user change
4325            it with the Flip View button. */
4326         flipView = appData.autoFlipView ?
4327           (newGameMode == IcsPlayingBlack) || ics_flip :
4328           appData.flipView;
4329
4330         /* Done with values from previous mode; copy in new ones */
4331         gameMode = newGameMode;
4332         ModeHighlight();
4333         ics_gamenum = gamenum;
4334         if (gamenum == gs_gamenum) {
4335             int klen = strlen(gs_kind);
4336             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4337             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4338             gameInfo.event = StrSave(str);
4339         } else {
4340             gameInfo.event = StrSave("ICS game");
4341         }
4342         gameInfo.site = StrSave(appData.icsHost);
4343         gameInfo.date = PGNDate();
4344         gameInfo.round = StrSave("-");
4345         gameInfo.white = StrSave(white);
4346         gameInfo.black = StrSave(black);
4347         timeControl = basetime * 60 * 1000;
4348         timeControl_2 = 0;
4349         timeIncrement = increment * 1000;
4350         movesPerSession = 0;
4351         gameInfo.timeControl = TimeControlTagValue();
4352         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4353   if (appData.debugMode) {
4354     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4355     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4356     setbuf(debugFP, NULL);
4357   }
4358
4359         gameInfo.outOfBook = NULL;
4360
4361         /* Do we have the ratings? */
4362         if (strcmp(player1Name, white) == 0 &&
4363             strcmp(player2Name, black) == 0) {
4364             if (appData.debugMode)
4365               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4366                       player1Rating, player2Rating);
4367             gameInfo.whiteRating = player1Rating;
4368             gameInfo.blackRating = player2Rating;
4369         } else if (strcmp(player2Name, white) == 0 &&
4370                    strcmp(player1Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player2Rating, player1Rating);
4374             gameInfo.whiteRating = player2Rating;
4375             gameInfo.blackRating = player1Rating;
4376         }
4377         player1Name[0] = player2Name[0] = NULLCHAR;
4378
4379         /* Silence shouts if requested */
4380         if (appData.quietPlay &&
4381             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4382             SendToICS(ics_prefix);
4383             SendToICS("set shout 0\n");
4384         }
4385     }
4386
4387     /* Deal with midgame name changes */
4388     if (!newGame) {
4389         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4390             if (gameInfo.white) free(gameInfo.white);
4391             gameInfo.white = StrSave(white);
4392         }
4393         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4394             if (gameInfo.black) free(gameInfo.black);
4395             gameInfo.black = StrSave(black);
4396         }
4397     }
4398
4399     /* Throw away game result if anything actually changes in examine mode */
4400     if (gameMode == IcsExamining && !newGame) {
4401         gameInfo.result = GameUnfinished;
4402         if (gameInfo.resultDetails != NULL) {
4403             free(gameInfo.resultDetails);
4404             gameInfo.resultDetails = NULL;
4405         }
4406     }
4407
4408     /* In pausing && IcsExamining mode, we ignore boards coming
4409        in if they are in a different variation than we are. */
4410     if (pauseExamInvalid) return;
4411     if (pausing && gameMode == IcsExamining) {
4412         if (moveNum <= pauseExamForwardMostMove) {
4413             pauseExamInvalid = TRUE;
4414             forwardMostMove = pauseExamForwardMostMove;
4415             return;
4416         }
4417     }
4418
4419   if (appData.debugMode) {
4420     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4421   }
4422     /* Parse the board */
4423     for (k = 0; k < ranks; k++) {
4424       for (j = 0; j < files; j++)
4425         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4426       if(gameInfo.holdingsWidth > 1) {
4427            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4428            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4429       }
4430     }
4431     CopyBoard(boards[moveNum], board);
4432     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4433     if (moveNum == 0) {
4434         startedFromSetupPosition =
4435           !CompareBoards(board, initialPosition);
4436         if(startedFromSetupPosition)
4437             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4438     }
4439
4440     /* [HGM] Set castling rights. Take the outermost Rooks,
4441        to make it also work for FRC opening positions. Note that board12
4442        is really defective for later FRC positions, as it has no way to
4443        indicate which Rook can castle if they are on the same side of King.
4444        For the initial position we grant rights to the outermost Rooks,
4445        and remember thos rights, and we then copy them on positions
4446        later in an FRC game. This means WB might not recognize castlings with
4447        Rooks that have moved back to their original position as illegal,
4448        but in ICS mode that is not its job anyway.
4449     */
4450     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4451     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4452
4453         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4454             if(board[0][i] == WhiteRook) j = i;
4455         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4456         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4460             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4461         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465
4466         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4467         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4469         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4470             if(board[BOARD_HEIGHT-1][k] == bKing)
4471                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4472         if(gameInfo.variant == VariantTwoKings) {
4473             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4474             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4475             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4476         }
4477     } else { int r;
4478         r = boards[moveNum][CASTLING][0] = initialRights[0];
4479         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4480         r = boards[moveNum][CASTLING][1] = initialRights[1];
4481         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4482         r = boards[moveNum][CASTLING][3] = initialRights[3];
4483         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4484         r = boards[moveNum][CASTLING][4] = initialRights[4];
4485         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4486         /* wildcastle kludge: always assume King has rights */
4487         r = boards[moveNum][CASTLING][2] = initialRights[2];
4488         r = boards[moveNum][CASTLING][5] = initialRights[5];
4489     }
4490     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4491     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4492
4493
4494     if (ics_getting_history == H_GOT_REQ_HEADER ||
4495         ics_getting_history == H_GOT_UNREQ_HEADER) {
4496         /* This was an initial position from a move list, not
4497            the current position */
4498         return;
4499     }
4500
4501     /* Update currentMove and known move number limits */
4502     newMove = newGame || moveNum > forwardMostMove;
4503
4504     if (newGame) {
4505         forwardMostMove = backwardMostMove = currentMove = moveNum;
4506         if (gameMode == IcsExamining && moveNum == 0) {
4507           /* Workaround for ICS limitation: we are not told the wild
4508              type when starting to examine a game.  But if we ask for
4509              the move list, the move list header will tell us */
4510             ics_getting_history = H_REQUESTED;
4511             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4512             SendToICS(str);
4513         }
4514     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4515                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4516 #if ZIPPY
4517         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4518         /* [HGM] applied this also to an engine that is silently watching        */
4519         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4520             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4521             gameInfo.variant == currentlyInitializedVariant) {
4522           takeback = forwardMostMove - moveNum;
4523           for (i = 0; i < takeback; i++) {
4524             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4525             SendToProgram("undo\n", &first);
4526           }
4527         }
4528 #endif
4529
4530         forwardMostMove = moveNum;
4531         if (!pausing || currentMove > forwardMostMove)
4532           currentMove = forwardMostMove;
4533     } else {
4534         /* New part of history that is not contiguous with old part */
4535         if (pausing && gameMode == IcsExamining) {
4536             pauseExamInvalid = TRUE;
4537             forwardMostMove = pauseExamForwardMostMove;
4538             return;
4539         }
4540         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4541 #if ZIPPY
4542             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4543                 // [HGM] when we will receive the move list we now request, it will be
4544                 // fed to the engine from the first move on. So if the engine is not
4545                 // in the initial position now, bring it there.
4546                 InitChessProgram(&first, 0);
4547             }
4548 #endif
4549             ics_getting_history = H_REQUESTED;
4550             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4551             SendToICS(str);
4552         }
4553         forwardMostMove = backwardMostMove = currentMove = moveNum;
4554     }
4555
4556     /* Update the clocks */
4557     if (strchr(elapsed_time, '.')) {
4558       /* Time is in ms */
4559       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4560       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4561     } else {
4562       /* Time is in seconds */
4563       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4564       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4565     }
4566
4567
4568 #if ZIPPY
4569     if (appData.zippyPlay && newGame &&
4570         gameMode != IcsObserving && gameMode != IcsIdle &&
4571         gameMode != IcsExamining)
4572       ZippyFirstBoard(moveNum, basetime, increment);
4573 #endif
4574
4575     /* Put the move on the move list, first converting
4576        to canonical algebraic form. */
4577     if (moveNum > 0) {
4578   if (appData.debugMode) {
4579     if (appData.debugMode) { int f = forwardMostMove;
4580         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4581                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4582                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4583     }
4584     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4585     fprintf(debugFP, "moveNum = %d\n", moveNum);
4586     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4587     setbuf(debugFP, NULL);
4588   }
4589         if (moveNum <= backwardMostMove) {
4590             /* We don't know what the board looked like before
4591                this move.  Punt. */
4592           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4593             strcat(parseList[moveNum - 1], " ");
4594             strcat(parseList[moveNum - 1], elapsed_time);
4595             moveList[moveNum - 1][0] = NULLCHAR;
4596         } else if (strcmp(move_str, "none") == 0) {
4597             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4598             /* Again, we don't know what the board looked like;
4599                this is really the start of the game. */
4600             parseList[moveNum - 1][0] = NULLCHAR;
4601             moveList[moveNum - 1][0] = NULLCHAR;
4602             backwardMostMove = moveNum;
4603             startedFromSetupPosition = TRUE;
4604             fromX = fromY = toX = toY = -1;
4605         } else {
4606           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4607           //                 So we parse the long-algebraic move string in stead of the SAN move
4608           int valid; char buf[MSG_SIZ], *prom;
4609
4610           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4611                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4612           // str looks something like "Q/a1-a2"; kill the slash
4613           if(str[1] == '/')
4614             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4615           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4616           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4617                 strcat(buf, prom); // long move lacks promo specification!
4618           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4619                 if(appData.debugMode)
4620                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4621                 safeStrCpy(move_str, buf, MSG_SIZ);
4622           }
4623           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4624                                 &fromX, &fromY, &toX, &toY, &promoChar)
4625                || ParseOneMove(buf, moveNum - 1, &moveType,
4626                                 &fromX, &fromY, &toX, &toY, &promoChar);
4627           // end of long SAN patch
4628           if (valid) {
4629             (void) CoordsToAlgebraic(boards[moveNum - 1],
4630                                      PosFlags(moveNum - 1),
4631                                      fromY, fromX, toY, toX, promoChar,
4632                                      parseList[moveNum-1]);
4633             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4634               case MT_NONE:
4635               case MT_STALEMATE:
4636               default:
4637                 break;
4638               case MT_CHECK:
4639                 if(gameInfo.variant != VariantShogi)
4640                     strcat(parseList[moveNum - 1], "+");
4641                 break;
4642               case MT_CHECKMATE:
4643               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4644                 strcat(parseList[moveNum - 1], "#");
4645                 break;
4646             }
4647             strcat(parseList[moveNum - 1], " ");
4648             strcat(parseList[moveNum - 1], elapsed_time);
4649             /* currentMoveString is set as a side-effect of ParseOneMove */
4650             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4651             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4652             strcat(moveList[moveNum - 1], "\n");
4653
4654             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4655                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4656               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4657                 ChessSquare old, new = boards[moveNum][k][j];
4658                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4659                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4660                   if(old == new) continue;
4661                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4662                   else if(new == WhiteWazir || new == BlackWazir) {
4663                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4664                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4665                       else boards[moveNum][k][j] = old; // preserve type of Gold
4666                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4667                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4668               }
4669           } else {
4670             /* Move from ICS was illegal!?  Punt. */
4671             if (appData.debugMode) {
4672               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4673               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4674             }
4675             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4676             strcat(parseList[moveNum - 1], " ");
4677             strcat(parseList[moveNum - 1], elapsed_time);
4678             moveList[moveNum - 1][0] = NULLCHAR;
4679             fromX = fromY = toX = toY = -1;
4680           }
4681         }
4682   if (appData.debugMode) {
4683     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4684     setbuf(debugFP, NULL);
4685   }
4686
4687 #if ZIPPY
4688         /* Send move to chess program (BEFORE animating it). */
4689         if (appData.zippyPlay && !newGame && newMove &&
4690            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4691
4692             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4693                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4694                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4695                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4696                             move_str);
4697                     DisplayError(str, 0);
4698                 } else {
4699                     if (first.sendTime) {
4700                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4701                     }
4702                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4703                     if (firstMove && !bookHit) {
4704                         firstMove = FALSE;
4705                         if (first.useColors) {
4706                           SendToProgram(gameMode == IcsPlayingWhite ?
4707                                         "white\ngo\n" :
4708                                         "black\ngo\n", &first);
4709                         } else {
4710                           SendToProgram("go\n", &first);
4711                         }
4712                         first.maybeThinking = TRUE;
4713                     }
4714                 }
4715             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4716               if (moveList[moveNum - 1][0] == NULLCHAR) {
4717                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4718                 DisplayError(str, 0);
4719               } else {
4720                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4721                 SendMoveToProgram(moveNum - 1, &first);
4722               }
4723             }
4724         }
4725 #endif
4726     }
4727
4728     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4729         /* If move comes from a remote source, animate it.  If it
4730            isn't remote, it will have already been animated. */
4731         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4732             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4733         }
4734         if (!pausing && appData.highlightLastMove) {
4735             SetHighlights(fromX, fromY, toX, toY);
4736         }
4737     }
4738
4739     /* Start the clocks */
4740     whiteFlag = blackFlag = FALSE;
4741     appData.clockMode = !(basetime == 0 && increment == 0);
4742     if (ticking == 0) {
4743       ics_clock_paused = TRUE;
4744       StopClocks();
4745     } else if (ticking == 1) {
4746       ics_clock_paused = FALSE;
4747     }
4748     if (gameMode == IcsIdle ||
4749         relation == RELATION_OBSERVING_STATIC ||
4750         relation == RELATION_EXAMINING ||
4751         ics_clock_paused)
4752       DisplayBothClocks();
4753     else
4754       StartClocks();
4755
4756     /* Display opponents and material strengths */
4757     if (gameInfo.variant != VariantBughouse &&
4758         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4759         if (tinyLayout || smallLayout) {
4760             if(gameInfo.variant == VariantNormal)
4761               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4762                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4763                     basetime, increment);
4764             else
4765               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4766                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4767                     basetime, increment, (int) gameInfo.variant);
4768         } else {
4769             if(gameInfo.variant == VariantNormal)
4770               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4771                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4772                     basetime, increment);
4773             else
4774               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment, VariantName(gameInfo.variant));
4777         }
4778         DisplayTitle(str);
4779   if (appData.debugMode) {
4780     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4781   }
4782     }
4783
4784
4785     /* Display the board */
4786     if (!pausing && !appData.noGUI) {
4787
4788       if (appData.premove)
4789           if (!gotPremove ||
4790              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4791              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4792               ClearPremoveHighlights();
4793
4794       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4795         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4796       DrawPosition(j, boards[currentMove]);
4797
4798       DisplayMove(moveNum - 1);
4799       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4800             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4801               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4802         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4803       }
4804     }
4805
4806     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4807 #if ZIPPY
4808     if(bookHit) { // [HGM] book: simulate book reply
4809         static char bookMove[MSG_SIZ]; // a bit generous?
4810
4811         programStats.nodes = programStats.depth = programStats.time =
4812         programStats.score = programStats.got_only_move = 0;
4813         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4814
4815         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4816         strcat(bookMove, bookHit);
4817         HandleMachineMove(bookMove, &first);
4818     }
4819 #endif
4820 }
4821
4822 void
4823 GetMoveListEvent()
4824 {
4825     char buf[MSG_SIZ];
4826     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4827         ics_getting_history = H_REQUESTED;
4828         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4829         SendToICS(buf);
4830     }
4831 }
4832
4833 void
4834 AnalysisPeriodicEvent(force)
4835      int force;
4836 {
4837     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4838          && !force) || !appData.periodicUpdates)
4839       return;
4840
4841     /* Send . command to Crafty to collect stats */
4842     SendToProgram(".\n", &first);
4843
4844     /* Don't send another until we get a response (this makes
4845        us stop sending to old Crafty's which don't understand
4846        the "." command (sending illegal cmds resets node count & time,
4847        which looks bad)) */
4848     programStats.ok_to_send = 0;
4849 }
4850
4851 void ics_update_width(new_width)
4852         int new_width;
4853 {
4854         ics_printf("set width %d\n", new_width);
4855 }
4856
4857 void
4858 SendMoveToProgram(moveNum, cps)
4859      int moveNum;
4860      ChessProgramState *cps;
4861 {
4862     char buf[MSG_SIZ];
4863
4864     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4865         // null move in variant where engine does not understand it (for analysis purposes)
4866         SendBoard(cps, moveNum + 1); // send position after move in stead.
4867         return;
4868     }
4869     if (cps->useUsermove) {
4870       SendToProgram("usermove ", cps);
4871     }
4872     if (cps->useSAN) {
4873       char *space;
4874       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4875         int len = space - parseList[moveNum];
4876         memcpy(buf, parseList[moveNum], len);
4877         buf[len++] = '\n';
4878         buf[len] = NULLCHAR;
4879       } else {
4880         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4881       }
4882       SendToProgram(buf, cps);
4883     } else {
4884       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4885         AlphaRank(moveList[moveNum], 4);
4886         SendToProgram(moveList[moveNum], cps);
4887         AlphaRank(moveList[moveNum], 4); // and back
4888       } else
4889       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4890        * the engine. It would be nice to have a better way to identify castle
4891        * moves here. */
4892       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4893                                                                          && cps->useOOCastle) {
4894         int fromX = moveList[moveNum][0] - AAA;
4895         int fromY = moveList[moveNum][1] - ONE;
4896         int toX = moveList[moveNum][2] - AAA;
4897         int toY = moveList[moveNum][3] - ONE;
4898         if((boards[moveNum][fromY][fromX] == WhiteKing
4899             && boards[moveNum][toY][toX] == WhiteRook)
4900            || (boards[moveNum][fromY][fromX] == BlackKing
4901                && boards[moveNum][toY][toX] == BlackRook)) {
4902           if(toX > fromX) SendToProgram("O-O\n", cps);
4903           else SendToProgram("O-O-O\n", cps);
4904         }
4905         else SendToProgram(moveList[moveNum], cps);
4906       } else
4907       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4908         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4909           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4910           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4911                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4912         } else
4913           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4914                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         SendToProgram(buf, cps);
4916       }
4917       else SendToProgram(moveList[moveNum], cps);
4918       /* End of additions by Tord */
4919     }
4920
4921     /* [HGM] setting up the opening has brought engine in force mode! */
4922     /*       Send 'go' if we are in a mode where machine should play. */
4923     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4924         (gameMode == TwoMachinesPlay   ||
4925 #if ZIPPY
4926          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4927 #endif
4928          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4929         SendToProgram("go\n", cps);
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "(extra)\n");
4932   }
4933     }
4934     setboardSpoiledMachineBlack = 0;
4935 }
4936
4937 void
4938 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4939      ChessMove moveType;
4940      int fromX, fromY, toX, toY;
4941      char promoChar;
4942 {
4943     char user_move[MSG_SIZ];
4944
4945     switch (moveType) {
4946       default:
4947         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4948                 (int)moveType, fromX, fromY, toX, toY);
4949         DisplayError(user_move + strlen("say "), 0);
4950         break;
4951       case WhiteKingSideCastle:
4952       case BlackKingSideCastle:
4953       case WhiteQueenSideCastleWild:
4954       case BlackQueenSideCastleWild:
4955       /* PUSH Fabien */
4956       case WhiteHSideCastleFR:
4957       case BlackHSideCastleFR:
4958       /* POP Fabien */
4959         snprintf(user_move, MSG_SIZ, "o-o\n");
4960         break;
4961       case WhiteQueenSideCastle:
4962       case BlackQueenSideCastle:
4963       case WhiteKingSideCastleWild:
4964       case BlackKingSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteASideCastleFR:
4967       case BlackASideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4970         break;
4971       case WhiteNonPromotion:
4972       case BlackNonPromotion:
4973         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4974         break;
4975       case WhitePromotion:
4976       case BlackPromotion:
4977         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4978           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4979                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4980                 PieceToChar(WhiteFerz));
4981         else if(gameInfo.variant == VariantGreat)
4982           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4984                 PieceToChar(WhiteMan));
4985         else
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 promoChar);
4989         break;
4990       case WhiteDrop:
4991       case BlackDrop:
4992       drop:
4993         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4994                  ToUpper(PieceToChar((ChessSquare) fromX)),
4995                  AAA + toX, ONE + toY);
4996         break;
4997       case IllegalMove:  /* could be a variant we don't quite understand */
4998         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4999       case NormalMove:
5000       case WhiteCapturesEnPassant:
5001       case BlackCapturesEnPassant:
5002         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5004         break;
5005     }
5006     SendToICS(user_move);
5007     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5008         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5009 }
5010
5011 void
5012 UploadGameEvent()
5013 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5014     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5015     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5016     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5017         DisplayError("You cannot do this while you are playing or observing", 0);
5018         return;
5019     }
5020     if(gameMode != IcsExamining) { // is this ever not the case?
5021         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5022
5023         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5024           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5025         } else { // on FICS we must first go to general examine mode
5026           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5027         }
5028         if(gameInfo.variant != VariantNormal) {
5029             // try figure out wild number, as xboard names are not always valid on ICS
5030             for(i=1; i<=36; i++) {
5031               snprintf(buf, MSG_SIZ, "wild/%d", i);
5032                 if(StringToVariant(buf) == gameInfo.variant) break;
5033             }
5034             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5035             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5036             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5037         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5038         SendToICS(ics_prefix);
5039         SendToICS(buf);
5040         if(startedFromSetupPosition || backwardMostMove != 0) {
5041           fen = PositionToFEN(backwardMostMove, NULL);
5042           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5043             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5044             SendToICS(buf);
5045           } else { // FICS: everything has to set by separate bsetup commands
5046             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5047             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5048             SendToICS(buf);
5049             if(!WhiteOnMove(backwardMostMove)) {
5050                 SendToICS("bsetup tomove black\n");
5051             }
5052             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5053             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5054             SendToICS(buf);
5055             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5056             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5057             SendToICS(buf);
5058             i = boards[backwardMostMove][EP_STATUS];
5059             if(i >= 0) { // set e.p.
5060               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5061                 SendToICS(buf);
5062             }
5063             bsetup++;
5064           }
5065         }
5066       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5067             SendToICS("bsetup done\n"); // switch to normal examining.
5068     }
5069     for(i = backwardMostMove; i<last; i++) {
5070         char buf[20];
5071         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5072         SendToICS(buf);
5073     }
5074     SendToICS(ics_prefix);
5075     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5076 }
5077
5078 void
5079 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5080      int rf, ff, rt, ft;
5081      char promoChar;
5082      char move[7];
5083 {
5084     if (rf == DROP_RANK) {
5085       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5086       sprintf(move, "%c@%c%c\n",
5087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5088     } else {
5089         if (promoChar == 'x' || promoChar == NULLCHAR) {
5090           sprintf(move, "%c%c%c%c\n",
5091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5092         } else {
5093             sprintf(move, "%c%c%c%c%c\n",
5094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5095         }
5096     }
5097 }
5098
5099 void
5100 ProcessICSInitScript(f)
5101      FILE *f;
5102 {
5103     char buf[MSG_SIZ];
5104
5105     while (fgets(buf, MSG_SIZ, f)) {
5106         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5107     }
5108
5109     fclose(f);
5110 }
5111
5112
5113 static int lastX, lastY, selectFlag, dragging;
5114
5115 void
5116 Sweep(int step)
5117 {
5118     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5119     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5120     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5121     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5122     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5123     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5124     do {
5125         promoSweep -= step;
5126         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5127         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5128         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5129         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5130         if(!step) step = 1;
5131     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5132             appData.testLegality && (promoSweep == king ||
5133             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5134     ChangeDragPiece(promoSweep);
5135 }
5136
5137 int PromoScroll(int x, int y)
5138 {
5139   int step = 0;
5140
5141   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5142   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5143   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5144   if(!step) return FALSE;
5145   lastX = x; lastY = y;
5146   if((promoSweep < BlackPawn) == flipView) step = -step;
5147   if(step > 0) selectFlag = 1;
5148   if(!selectFlag) Sweep(step);
5149   return FALSE;
5150 }
5151
5152 void
5153 NextPiece(int step)
5154 {
5155     ChessSquare piece = boards[currentMove][toY][toX];
5156     do {
5157         pieceSweep -= step;
5158         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5159         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(pieceSweep) == '.');
5162     boards[currentMove][toY][toX] = pieceSweep;
5163     DrawPosition(FALSE, boards[currentMove]);
5164     boards[currentMove][toY][toX] = piece;
5165 }
5166 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5167 void
5168 AlphaRank(char *move, int n)
5169 {
5170 //    char *p = move, c; int x, y;
5171
5172     if (appData.debugMode) {
5173         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5174     }
5175
5176     if(move[1]=='*' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         move[1] = '@';
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[0]>='0' && move[0]<='9' &&
5184        move[1]>='a' && move[1]<='x' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         /* input move, Shogi -> normal */
5188         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5189         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[1]=='@' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196         move[1] = '*';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199     } else
5200     if(
5201        move[0]>='a' && move[0]<='x' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204          /* output move, normal -> Shogi */
5205         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5210     }
5211     if (appData.debugMode) {
5212         fprintf(debugFP, "   out = '%s'\n", move);
5213     }
5214 }
5215
5216 char yy_textstr[8000];
5217
5218 /* Parser for moves from gnuchess, ICS, or user typein box */
5219 Boolean
5220 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5221      char *move;
5222      int moveNum;
5223      ChessMove *moveType;
5224      int *fromX, *fromY, *toX, *toY;
5225      char *promoChar;
5226 {
5227     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5228
5229     switch (*moveType) {
5230       case WhitePromotion:
5231       case BlackPromotion:
5232       case WhiteNonPromotion:
5233       case BlackNonPromotion:
5234       case NormalMove:
5235       case WhiteCapturesEnPassant:
5236       case BlackCapturesEnPassant:
5237       case WhiteKingSideCastle:
5238       case WhiteQueenSideCastle:
5239       case BlackKingSideCastle:
5240       case BlackQueenSideCastle:
5241       case WhiteKingSideCastleWild:
5242       case WhiteQueenSideCastleWild:
5243       case BlackKingSideCastleWild:
5244       case BlackQueenSideCastleWild:
5245       /* Code added by Tord: */
5246       case WhiteHSideCastleFR:
5247       case WhiteASideCastleFR:
5248       case BlackHSideCastleFR:
5249       case BlackASideCastleFR:
5250       /* End of code added by Tord */
5251       case IllegalMove:         /* bug or odd chess variant */
5252         *fromX = currentMoveString[0] - AAA;
5253         *fromY = currentMoveString[1] - ONE;
5254         *toX = currentMoveString[2] - AAA;
5255         *toY = currentMoveString[3] - ONE;
5256         *promoChar = currentMoveString[4];
5257         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5258             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5259     if (appData.debugMode) {
5260         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5261     }
5262             *fromX = *fromY = *toX = *toY = 0;
5263             return FALSE;
5264         }
5265         if (appData.testLegality) {
5266           return (*moveType != IllegalMove);
5267         } else {
5268           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5269                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5270         }
5271
5272       case WhiteDrop:
5273       case BlackDrop:
5274         *fromX = *moveType == WhiteDrop ?
5275           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5276           (int) CharToPiece(ToLower(currentMoveString[0]));
5277         *fromY = DROP_RANK;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = NULLCHAR;
5281         return TRUE;
5282
5283       case AmbiguousMove:
5284       case ImpossibleMove:
5285       case EndOfFile:
5286       case ElapsedTime:
5287       case Comment:
5288       case PGNTag:
5289       case NAG:
5290       case WhiteWins:
5291       case BlackWins:
5292       case GameIsDrawn:
5293       default:
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5296     }
5297         /* bug? */
5298         *fromX = *fromY = *toX = *toY = 0;
5299         *promoChar = NULLCHAR;
5300         return FALSE;
5301     }
5302 }
5303
5304 Boolean pushed = FALSE;
5305 char *lastParseAttempt;
5306
5307 void
5308 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5309 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5310   int fromX, fromY, toX, toY; char promoChar;
5311   ChessMove moveType;
5312   Boolean valid;
5313   int nr = 0;
5314
5315   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5316     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5317     pushed = TRUE;
5318   }
5319   endPV = forwardMostMove;
5320   do {
5321     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5322     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5323     lastParseAttempt = pv;
5324     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5325 if(appData.debugMode){
5326 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);
5327 }
5328     if(!valid && nr == 0 &&
5329        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5330         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5331         // Hande case where played move is different from leading PV move
5332         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5333         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5334         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5335         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5336           endPV += 2; // if position different, keep this
5337           moveList[endPV-1][0] = fromX + AAA;
5338           moveList[endPV-1][1] = fromY + ONE;
5339           moveList[endPV-1][2] = toX + AAA;
5340           moveList[endPV-1][3] = toY + ONE;
5341           parseList[endPV-1][0] = NULLCHAR;
5342           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5343         }
5344       }
5345     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5346     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5347     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5348     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5349         valid++; // allow comments in PV
5350         continue;
5351     }
5352     nr++;
5353     if(endPV+1 > framePtr) break; // no space, truncate
5354     if(!valid) break;
5355     endPV++;
5356     CopyBoard(boards[endPV], boards[endPV-1]);
5357     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5358     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5359     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5360     CoordsToAlgebraic(boards[endPV - 1],
5361                              PosFlags(endPV - 1),
5362                              fromY, fromX, toY, toX, promoChar,
5363                              parseList[endPV - 1]);
5364   } while(valid);
5365   if(atEnd == 2) return; // used hidden, for PV conversion
5366   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5367   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5368   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5369                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5370   DrawPosition(TRUE, boards[currentMove]);
5371 }
5372
5373 int
5374 MultiPV(ChessProgramState *cps)
5375 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5376         int i;
5377         for(i=0; i<cps->nrOptions; i++)
5378             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5379                 return i;
5380         return -1;
5381 }
5382
5383 Boolean
5384 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5385 {
5386         int startPV, multi, lineStart, origIndex = index;
5387         char *p, buf2[MSG_SIZ];
5388
5389         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5390         lastX = x; lastY = y;
5391         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5392         lineStart = startPV = index;
5393         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5394         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5395         index = startPV;
5396         do{ while(buf[index] && buf[index] != '\n') index++;
5397         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5398         buf[index] = 0;
5399         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5400                 int n = first.option[multi].value;
5401                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5402                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5403                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5404                 first.option[multi].value = n;
5405                 *start = *end = 0;
5406                 return FALSE;
5407         }
5408         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5409         *start = startPV; *end = index-1;
5410         return TRUE;
5411 }
5412
5413 char *
5414 PvToSAN(char *pv)
5415 {
5416         static char buf[10*MSG_SIZ];
5417         int i, k=0, savedEnd=endPV;
5418         *buf = NULLCHAR;
5419         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5420         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5421         for(i = forwardMostMove; i<endPV; i++){
5422             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5423             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5424             k += strlen(buf+k);
5425         }
5426         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5427         if(forwardMostMove < savedEnd) PopInner(0);
5428         endPV = savedEnd;
5429         return buf;
5430 }
5431
5432 Boolean
5433 LoadPV(int x, int y)
5434 { // called on right mouse click to load PV
5435   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5436   lastX = x; lastY = y;
5437   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5438   return TRUE;
5439 }
5440
5441 void
5442 UnLoadPV()
5443 {
5444   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5445   if(endPV < 0) return;
5446   endPV = -1;
5447   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5448         Boolean saveAnimate = appData.animate;
5449         if(pushed) {
5450             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5451                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5452             } else storedGames--; // abandon shelved tail of original game
5453         }
5454         pushed = FALSE;
5455         forwardMostMove = currentMove;
5456         currentMove = oldFMM;
5457         appData.animate = FALSE;
5458         ToNrEvent(forwardMostMove);
5459         appData.animate = saveAnimate;
5460   }
5461   currentMove = forwardMostMove;
5462   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5463   ClearPremoveHighlights();
5464   DrawPosition(TRUE, boards[currentMove]);
5465 }
5466
5467 void
5468 MovePV(int x, int y, int h)
5469 { // step through PV based on mouse coordinates (called on mouse move)
5470   int margin = h>>3, step = 0;
5471
5472   // we must somehow check if right button is still down (might be released off board!)
5473   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5474   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5475   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5476   if(!step) return;
5477   lastX = x; lastY = y;
5478
5479   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5480   if(endPV < 0) return;
5481   if(y < margin) step = 1; else
5482   if(y > h - margin) step = -1;
5483   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5484   currentMove += step;
5485   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5486   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5487                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5488   DrawPosition(FALSE, boards[currentMove]);
5489 }
5490
5491
5492 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5493 // All positions will have equal probability, but the current method will not provide a unique
5494 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5495 #define DARK 1
5496 #define LITE 2
5497 #define ANY 3
5498
5499 int squaresLeft[4];
5500 int piecesLeft[(int)BlackPawn];
5501 int seed, nrOfShuffles;
5502
5503 void GetPositionNumber()
5504 {       // sets global variable seed
5505         int i;
5506
5507         seed = appData.defaultFrcPosition;
5508         if(seed < 0) { // randomize based on time for negative FRC position numbers
5509                 for(i=0; i<50; i++) seed += random();
5510                 seed = random() ^ random() >> 8 ^ random() << 8;
5511                 if(seed<0) seed = -seed;
5512         }
5513 }
5514
5515 int put(Board board, int pieceType, int rank, int n, int shade)
5516 // put the piece on the (n-1)-th empty squares of the given shade
5517 {
5518         int i;
5519
5520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5521                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5522                         board[rank][i] = (ChessSquare) pieceType;
5523                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5524                         squaresLeft[ANY]--;
5525                         piecesLeft[pieceType]--;
5526                         return i;
5527                 }
5528         }
5529         return -1;
5530 }
5531
5532
5533 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5534 // calculate where the next piece goes, (any empty square), and put it there
5535 {
5536         int i;
5537
5538         i = seed % squaresLeft[shade];
5539         nrOfShuffles *= squaresLeft[shade];
5540         seed /= squaresLeft[shade];
5541         put(board, pieceType, rank, i, shade);
5542 }
5543
5544 void AddTwoPieces(Board board, int pieceType, int rank)
5545 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5546 {
5547         int i, n=squaresLeft[ANY], j=n-1, k;
5548
5549         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5550         i = seed % k;  // pick one
5551         nrOfShuffles *= k;
5552         seed /= k;
5553         while(i >= j) i -= j--;
5554         j = n - 1 - j; i += j;
5555         put(board, pieceType, rank, j, ANY);
5556         put(board, pieceType, rank, i, ANY);
5557 }
5558
5559 void SetUpShuffle(Board board, int number)
5560 {
5561         int i, p, first=1;
5562
5563         GetPositionNumber(); nrOfShuffles = 1;
5564
5565         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5566         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5567         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5568
5569         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5570
5571         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5572             p = (int) board[0][i];
5573             if(p < (int) BlackPawn) piecesLeft[p] ++;
5574             board[0][i] = EmptySquare;
5575         }
5576
5577         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5578             // shuffles restricted to allow normal castling put KRR first
5579             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5580                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5581             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5582                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5583             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5584                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5585             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5586                 put(board, WhiteRook, 0, 0, ANY);
5587             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5588         }
5589
5590         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5591             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5592             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5593                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5594                 while(piecesLeft[p] >= 2) {
5595                     AddOnePiece(board, p, 0, LITE);
5596                     AddOnePiece(board, p, 0, DARK);
5597                 }
5598                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5599             }
5600
5601         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5602             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5603             // but we leave King and Rooks for last, to possibly obey FRC restriction
5604             if(p == (int)WhiteRook) continue;
5605             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5606             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5607         }
5608
5609         // now everything is placed, except perhaps King (Unicorn) and Rooks
5610
5611         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5612             // Last King gets castling rights
5613             while(piecesLeft[(int)WhiteUnicorn]) {
5614                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5615                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5616             }
5617
5618             while(piecesLeft[(int)WhiteKing]) {
5619                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5620                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5621             }
5622
5623
5624         } else {
5625             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5626             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5627         }
5628
5629         // Only Rooks can be left; simply place them all
5630         while(piecesLeft[(int)WhiteRook]) {
5631                 i = put(board, WhiteRook, 0, 0, ANY);
5632                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5633                         if(first) {
5634                                 first=0;
5635                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5636                         }
5637                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5638                 }
5639         }
5640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5641             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5642         }
5643
5644         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5645 }
5646
5647 int SetCharTable( char *table, const char * map )
5648 /* [HGM] moved here from winboard.c because of its general usefulness */
5649 /*       Basically a safe strcpy that uses the last character as King */
5650 {
5651     int result = FALSE; int NrPieces;
5652
5653     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5654                     && NrPieces >= 12 && !(NrPieces&1)) {
5655         int i; /* [HGM] Accept even length from 12 to 34 */
5656
5657         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5658         for( i=0; i<NrPieces/2-1; i++ ) {
5659             table[i] = map[i];
5660             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5661         }
5662         table[(int) WhiteKing]  = map[NrPieces/2-1];
5663         table[(int) BlackKing]  = map[NrPieces-1];
5664
5665         result = TRUE;
5666     }
5667
5668     return result;
5669 }
5670
5671 void Prelude(Board board)
5672 {       // [HGM] superchess: random selection of exo-pieces
5673         int i, j, k; ChessSquare p;
5674         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5675
5676         GetPositionNumber(); // use FRC position number
5677
5678         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5679             SetCharTable(pieceToChar, appData.pieceToCharTable);
5680             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5681                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5682         }
5683
5684         j = seed%4;                 seed /= 4;
5685         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%3 + (seed%3 >= j); seed /= 3;
5689         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%3;                 seed /= 3;
5693         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%2 + (seed%2 >= j); seed /= 2;
5697         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5701         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5702         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5703         put(board, exoPieces[0],    0, 0, ANY);
5704         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5705 }
5706
5707 void
5708 InitPosition(redraw)
5709      int redraw;
5710 {
5711     ChessSquare (* pieces)[BOARD_FILES];
5712     int i, j, pawnRow, overrule,
5713     oldx = gameInfo.boardWidth,
5714     oldy = gameInfo.boardHeight,
5715     oldh = gameInfo.holdingsWidth;
5716     static int oldv;
5717
5718     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5719
5720     /* [AS] Initialize pv info list [HGM] and game status */
5721     {
5722         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5723             pvInfoList[i].depth = 0;
5724             boards[i][EP_STATUS] = EP_NONE;
5725             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5726         }
5727
5728         initialRulePlies = 0; /* 50-move counter start */
5729
5730         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5731         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5732     }
5733
5734
5735     /* [HGM] logic here is completely changed. In stead of full positions */
5736     /* the initialized data only consist of the two backranks. The switch */
5737     /* selects which one we will use, which is than copied to the Board   */
5738     /* initialPosition, which for the rest is initialized by Pawns and    */
5739     /* empty squares. This initial position is then copied to boards[0],  */
5740     /* possibly after shuffling, so that it remains available.            */
5741
5742     gameInfo.holdingsWidth = 0; /* default board sizes */
5743     gameInfo.boardWidth    = 8;
5744     gameInfo.boardHeight   = 8;
5745     gameInfo.holdingsSize  = 0;
5746     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5747     for(i=0; i<BOARD_FILES-2; i++)
5748       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5749     initialPosition[EP_STATUS] = EP_NONE;
5750     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5751     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5752          SetCharTable(pieceNickName, appData.pieceNickNames);
5753     else SetCharTable(pieceNickName, "............");
5754     pieces = FIDEArray;
5755
5756     switch (gameInfo.variant) {
5757     case VariantFischeRandom:
5758       shuffleOpenings = TRUE;
5759     default:
5760       break;
5761     case VariantShatranj:
5762       pieces = ShatranjArray;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5765       break;
5766     case VariantMakruk:
5767       pieces = makrukArray;
5768       nrCastlingRights = 0;
5769       startedFromSetupPosition = TRUE;
5770       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5771       break;
5772     case VariantTwoKings:
5773       pieces = twoKingsArray;
5774       break;
5775     case VariantGrand:
5776       pieces = GrandArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5779       gameInfo.boardWidth = 10;
5780       gameInfo.boardHeight = 10;
5781       gameInfo.holdingsSize = 7;
5782       break;
5783     case VariantCapaRandom:
5784       shuffleOpenings = TRUE;
5785     case VariantCapablanca:
5786       pieces = CapablancaArray;
5787       gameInfo.boardWidth = 10;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       break;
5790     case VariantGothic:
5791       pieces = GothicArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       break;
5795     case VariantSChess:
5796       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantJanus:
5800       pieces = JanusArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5803       nrCastlingRights = 6;
5804         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5805         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5806         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5807         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5808         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5809         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5810       break;
5811     case VariantFalcon:
5812       pieces = FalconArray;
5813       gameInfo.boardWidth = 10;
5814       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5815       break;
5816     case VariantXiangqi:
5817       pieces = XiangqiArray;
5818       gameInfo.boardWidth  = 9;
5819       gameInfo.boardHeight = 10;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5822       break;
5823     case VariantShogi:
5824       pieces = ShogiArray;
5825       gameInfo.boardWidth  = 9;
5826       gameInfo.boardHeight = 9;
5827       gameInfo.holdingsSize = 7;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5830       break;
5831     case VariantCourier:
5832       pieces = CourierArray;
5833       gameInfo.boardWidth  = 12;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5836       break;
5837     case VariantKnightmate:
5838       pieces = KnightmateArray;
5839       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5840       break;
5841     case VariantSpartan:
5842       pieces = SpartanArray;
5843       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5844       break;
5845     case VariantFairy:
5846       pieces = fairyArray;
5847       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5848       break;
5849     case VariantGreat:
5850       pieces = GreatArray;
5851       gameInfo.boardWidth = 10;
5852       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5853       gameInfo.holdingsSize = 8;
5854       break;
5855     case VariantSuper:
5856       pieces = FIDEArray;
5857       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5858       gameInfo.holdingsSize = 8;
5859       startedFromSetupPosition = TRUE;
5860       break;
5861     case VariantCrazyhouse:
5862     case VariantBughouse:
5863       pieces = FIDEArray;
5864       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5865       gameInfo.holdingsSize = 5;
5866       break;
5867     case VariantWildCastle:
5868       pieces = FIDEArray;
5869       /* !!?shuffle with kings guaranteed to be on d or e file */
5870       shuffleOpenings = 1;
5871       break;
5872     case VariantNoCastle:
5873       pieces = FIDEArray;
5874       nrCastlingRights = 0;
5875       /* !!?unconstrained back-rank shuffle */
5876       shuffleOpenings = 1;
5877       break;
5878     }
5879
5880     overrule = 0;
5881     if(appData.NrFiles >= 0) {
5882         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5883         gameInfo.boardWidth = appData.NrFiles;
5884     }
5885     if(appData.NrRanks >= 0) {
5886         gameInfo.boardHeight = appData.NrRanks;
5887     }
5888     if(appData.holdingsSize >= 0) {
5889         i = appData.holdingsSize;
5890         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5891         gameInfo.holdingsSize = i;
5892     }
5893     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5894     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5895         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5896
5897     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5898     if(pawnRow < 1) pawnRow = 1;
5899     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5900
5901     /* User pieceToChar list overrules defaults */
5902     if(appData.pieceToCharTable != NULL)
5903         SetCharTable(pieceToChar, appData.pieceToCharTable);
5904
5905     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5906
5907         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5908             s = (ChessSquare) 0; /* account holding counts in guard band */
5909         for( i=0; i<BOARD_HEIGHT; i++ )
5910             initialPosition[i][j] = s;
5911
5912         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5913         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5914         initialPosition[pawnRow][j] = WhitePawn;
5915         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5916         if(gameInfo.variant == VariantXiangqi) {
5917             if(j&1) {
5918                 initialPosition[pawnRow][j] =
5919                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5920                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5921                    initialPosition[2][j] = WhiteCannon;
5922                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5923                 }
5924             }
5925         }
5926         if(gameInfo.variant == VariantGrand) {
5927             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5928                initialPosition[0][j] = WhiteRook;
5929                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5930             }
5931         }
5932         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5933     }
5934     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5935
5936             j=BOARD_LEFT+1;
5937             initialPosition[1][j] = WhiteBishop;
5938             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5939             j=BOARD_RGHT-2;
5940             initialPosition[1][j] = WhiteRook;
5941             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5942     }
5943
5944     if( nrCastlingRights == -1) {
5945         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5946         /*       This sets default castling rights from none to normal corners   */
5947         /* Variants with other castling rights must set them themselves above    */
5948         nrCastlingRights = 6;
5949
5950         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5951         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5952         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5953         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5954         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5955         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5956      }
5957
5958      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5959      if(gameInfo.variant == VariantGreat) { // promotion commoners
5960         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5962         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5964      }
5965      if( gameInfo.variant == VariantSChess ) {
5966       initialPosition[1][0] = BlackMarshall;
5967       initialPosition[2][0] = BlackAngel;
5968       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5969       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5970       initialPosition[1][1] = initialPosition[2][1] = 
5971       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5972      }
5973   if (appData.debugMode) {
5974     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5975   }
5976     if(shuffleOpenings) {
5977         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5978         startedFromSetupPosition = TRUE;
5979     }
5980     if(startedFromPositionFile) {
5981       /* [HGM] loadPos: use PositionFile for every new game */
5982       CopyBoard(initialPosition, filePosition);
5983       for(i=0; i<nrCastlingRights; i++)
5984           initialRights[i] = filePosition[CASTLING][i];
5985       startedFromSetupPosition = TRUE;
5986     }
5987
5988     CopyBoard(boards[0], initialPosition);
5989
5990     if(oldx != gameInfo.boardWidth ||
5991        oldy != gameInfo.boardHeight ||
5992        oldv != gameInfo.variant ||
5993        oldh != gameInfo.holdingsWidth
5994                                          )
5995             InitDrawingSizes(-2 ,0);
5996
5997     oldv = gameInfo.variant;
5998     if (redraw)
5999       DrawPosition(TRUE, boards[currentMove]);
6000 }
6001
6002 void
6003 SendBoard(cps, moveNum)
6004      ChessProgramState *cps;
6005      int moveNum;
6006 {
6007     char message[MSG_SIZ];
6008
6009     if (cps->useSetboard) {
6010       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6011       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6012       SendToProgram(message, cps);
6013       free(fen);
6014
6015     } else {
6016       ChessSquare *bp;
6017       int i, j;
6018       /* Kludge to set black to move, avoiding the troublesome and now
6019        * deprecated "black" command.
6020        */
6021       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6022         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6023
6024       SendToProgram("edit\n", cps);
6025       SendToProgram("#\n", cps);
6026       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6027         bp = &boards[moveNum][i][BOARD_LEFT];
6028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6029           if ((int) *bp < (int) BlackPawn) {
6030             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6031                     AAA + j, ONE + i);
6032             if(message[0] == '+' || message[0] == '~') {
6033               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6034                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6035                         AAA + j, ONE + i);
6036             }
6037             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6038                 message[1] = BOARD_RGHT   - 1 - j + '1';
6039                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6040             }
6041             SendToProgram(message, cps);
6042           }
6043         }
6044       }
6045
6046       SendToProgram("c\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][BOARD_LEFT];
6049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6050           if (((int) *bp != (int) EmptySquare)
6051               && ((int) *bp >= (int) BlackPawn)) {
6052             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6053                     AAA + j, ONE + i);
6054             if(message[0] == '+' || message[0] == '~') {
6055               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6056                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6057                         AAA + j, ONE + i);
6058             }
6059             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6060                 message[1] = BOARD_RGHT   - 1 - j + '1';
6061                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6062             }
6063             SendToProgram(message, cps);
6064           }
6065         }
6066       }
6067
6068       SendToProgram(".\n", cps);
6069     }
6070     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6071 }
6072
6073 ChessSquare
6074 DefaultPromoChoice(int white)
6075 {
6076     ChessSquare result;
6077     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6078         result = WhiteFerz; // no choice
6079     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6080         result= WhiteKing; // in Suicide Q is the last thing we want
6081     else if(gameInfo.variant == VariantSpartan)
6082         result = white ? WhiteQueen : WhiteAngel;
6083     else result = WhiteQueen;
6084     if(!white) result = WHITE_TO_BLACK result;
6085     return result;
6086 }
6087
6088 static int autoQueen; // [HGM] oneclick
6089
6090 int
6091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6092 {
6093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6094     /* [HGM] add Shogi promotions */
6095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6096     ChessSquare piece;
6097     ChessMove moveType;
6098     Boolean premove;
6099
6100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6102
6103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6105         return FALSE;
6106
6107     piece = boards[currentMove][fromY][fromX];
6108     if(gameInfo.variant == VariantShogi) {
6109         promotionZoneSize = BOARD_HEIGHT/3;
6110         highestPromotingPiece = (int)WhiteFerz;
6111     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6112         promotionZoneSize = 3;
6113     }
6114
6115     // Treat Lance as Pawn when it is not representing Amazon
6116     if(gameInfo.variant != VariantSuper) {
6117         if(piece == WhiteLance) piece = WhitePawn; else
6118         if(piece == BlackLance) piece = BlackPawn;
6119     }
6120
6121     // next weed out all moves that do not touch the promotion zone at all
6122     if((int)piece >= BlackPawn) {
6123         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6124              return FALSE;
6125         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6126     } else {
6127         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6128            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6129     }
6130
6131     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6132
6133     // weed out mandatory Shogi promotions
6134     if(gameInfo.variant == VariantShogi) {
6135         if(piece >= BlackPawn) {
6136             if(toY == 0 && piece == BlackPawn ||
6137                toY == 0 && piece == BlackQueen ||
6138                toY <= 1 && piece == BlackKnight) {
6139                 *promoChoice = '+';
6140                 return FALSE;
6141             }
6142         } else {
6143             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6144                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6145                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6146                 *promoChoice = '+';
6147                 return FALSE;
6148             }
6149         }
6150     }
6151
6152     // weed out obviously illegal Pawn moves
6153     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6154         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6155         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6156         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6157         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6158         // note we are not allowed to test for valid (non-)capture, due to premove
6159     }
6160
6161     // we either have a choice what to promote to, or (in Shogi) whether to promote
6162     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6163         *promoChoice = PieceToChar(BlackFerz);  // no choice
6164         return FALSE;
6165     }
6166     // no sense asking what we must promote to if it is going to explode...
6167     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6168         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6169         return FALSE;
6170     }
6171     // give caller the default choice even if we will not make it
6172     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6173     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6174     if(        sweepSelect && gameInfo.variant != VariantGreat
6175                            && gameInfo.variant != VariantGrand
6176                            && gameInfo.variant != VariantSuper) return FALSE;
6177     if(autoQueen) return FALSE; // predetermined
6178
6179     // suppress promotion popup on illegal moves that are not premoves
6180     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6181               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6182     if(appData.testLegality && !premove) {
6183         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6184                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6185         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6186             return FALSE;
6187     }
6188
6189     return TRUE;
6190 }
6191
6192 int
6193 InPalace(row, column)
6194      int row, column;
6195 {   /* [HGM] for Xiangqi */
6196     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6197          column < (BOARD_WIDTH + 4)/2 &&
6198          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6199     return FALSE;
6200 }
6201
6202 int
6203 PieceForSquare (x, y)
6204      int x;
6205      int y;
6206 {
6207   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6208      return -1;
6209   else
6210      return boards[currentMove][y][x];
6211 }
6212
6213 int
6214 OKToStartUserMove(x, y)
6215      int x, y;
6216 {
6217     ChessSquare from_piece;
6218     int white_piece;
6219
6220     if (matchMode) return FALSE;
6221     if (gameMode == EditPosition) return TRUE;
6222
6223     if (x >= 0 && y >= 0)
6224       from_piece = boards[currentMove][y][x];
6225     else
6226       from_piece = EmptySquare;
6227
6228     if (from_piece == EmptySquare) return FALSE;
6229
6230     white_piece = (int)from_piece >= (int)WhitePawn &&
6231       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6232
6233     switch (gameMode) {
6234       case PlayFromGameFile:
6235       case AnalyzeFile:
6236       case TwoMachinesPlay:
6237       case EndOfGame:
6238         return FALSE;
6239
6240       case IcsObserving:
6241       case IcsIdle:
6242         return FALSE;
6243
6244       case MachinePlaysWhite:
6245       case IcsPlayingBlack:
6246         if (appData.zippyPlay) return FALSE;
6247         if (white_piece) {
6248             DisplayMoveError(_("You are playing Black"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255         if (appData.zippyPlay) return FALSE;
6256         if (!white_piece) {
6257             DisplayMoveError(_("You are playing White"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case EditGame:
6263         if (!white_piece && WhiteOnMove(currentMove)) {
6264             DisplayMoveError(_("It is White's turn"));
6265             return FALSE;
6266         }
6267         if (white_piece && !WhiteOnMove(currentMove)) {
6268             DisplayMoveError(_("It is Black's turn"));
6269             return FALSE;
6270         }
6271         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6272             /* Editing correspondence game history */
6273             /* Could disallow this or prompt for confirmation */
6274             cmailOldMove = -1;
6275         }
6276         break;
6277
6278       case BeginningOfGame:
6279         if (appData.icsActive) return FALSE;
6280         if (!appData.noChessProgram) {
6281             if (!white_piece) {
6282                 DisplayMoveError(_("You are playing White"));
6283                 return FALSE;
6284             }
6285         }
6286         break;
6287
6288       case Training:
6289         if (!white_piece && WhiteOnMove(currentMove)) {
6290             DisplayMoveError(_("It is White's turn"));
6291             return FALSE;
6292         }
6293         if (white_piece && !WhiteOnMove(currentMove)) {
6294             DisplayMoveError(_("It is Black's turn"));
6295             return FALSE;
6296         }
6297         break;
6298
6299       default:
6300       case IcsExamining:
6301         break;
6302     }
6303     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6304         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6305         && gameMode != AnalyzeFile && gameMode != Training) {
6306         DisplayMoveError(_("Displayed position is not current"));
6307         return FALSE;
6308     }
6309     return TRUE;
6310 }
6311
6312 Boolean
6313 OnlyMove(int *x, int *y, Boolean captures) {
6314     DisambiguateClosure cl;
6315     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6316     switch(gameMode) {
6317       case MachinePlaysBlack:
6318       case IcsPlayingWhite:
6319       case BeginningOfGame:
6320         if(!WhiteOnMove(currentMove)) return FALSE;
6321         break;
6322       case MachinePlaysWhite:
6323       case IcsPlayingBlack:
6324         if(WhiteOnMove(currentMove)) return FALSE;
6325         break;
6326       case EditGame:
6327         break;
6328       default:
6329         return FALSE;
6330     }
6331     cl.pieceIn = EmptySquare;
6332     cl.rfIn = *y;
6333     cl.ffIn = *x;
6334     cl.rtIn = -1;
6335     cl.ftIn = -1;
6336     cl.promoCharIn = NULLCHAR;
6337     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6338     if( cl.kind == NormalMove ||
6339         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6340         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6341         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6342       fromX = cl.ff;
6343       fromY = cl.rf;
6344       *x = cl.ft;
6345       *y = cl.rt;
6346       return TRUE;
6347     }
6348     if(cl.kind != ImpossibleMove) return FALSE;
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = -1;
6351     cl.ffIn = -1;
6352     cl.rtIn = *y;
6353     cl.ftIn = *x;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6365       return TRUE;
6366     }
6367     return FALSE;
6368 }
6369
6370 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6371 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6372 int lastLoadGameUseList = FALSE;
6373 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6374 ChessMove lastLoadGameStart = EndOfFile;
6375
6376 void
6377 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6378      int fromX, fromY, toX, toY;
6379      int promoChar;
6380 {
6381     ChessMove moveType;
6382     ChessSquare pdown, pup;
6383
6384     /* Check if the user is playing in turn.  This is complicated because we
6385        let the user "pick up" a piece before it is his turn.  So the piece he
6386        tried to pick up may have been captured by the time he puts it down!
6387        Therefore we use the color the user is supposed to be playing in this
6388        test, not the color of the piece that is currently on the starting
6389        square---except in EditGame mode, where the user is playing both
6390        sides; fortunately there the capture race can't happen.  (It can
6391        now happen in IcsExamining mode, but that's just too bad.  The user
6392        will get a somewhat confusing message in that case.)
6393        */
6394
6395     switch (gameMode) {
6396       case PlayFromGameFile:
6397       case AnalyzeFile:
6398       case TwoMachinesPlay:
6399       case EndOfGame:
6400       case IcsObserving:
6401       case IcsIdle:
6402         /* We switched into a game mode where moves are not accepted,
6403            perhaps while the mouse button was down. */
6404         return;
6405
6406       case MachinePlaysWhite:
6407         /* User is moving for Black */
6408         if (WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is White's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case MachinePlaysBlack:
6415         /* User is moving for White */
6416         if (!WhiteOnMove(currentMove)) {
6417             DisplayMoveError(_("It is Black's turn"));
6418             return;
6419         }
6420         break;
6421
6422       case EditGame:
6423       case IcsExamining:
6424       case BeginningOfGame:
6425       case AnalyzeMode:
6426       case Training:
6427         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6428         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6429             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6430             /* User is moving for Black */
6431             if (WhiteOnMove(currentMove)) {
6432                 DisplayMoveError(_("It is White's turn"));
6433                 return;
6434             }
6435         } else {
6436             /* User is moving for White */
6437             if (!WhiteOnMove(currentMove)) {
6438                 DisplayMoveError(_("It is Black's turn"));
6439                 return;
6440             }
6441         }
6442         break;
6443
6444       case IcsPlayingBlack:
6445         /* User is moving for Black */
6446         if (WhiteOnMove(currentMove)) {
6447             if (!appData.premove) {
6448                 DisplayMoveError(_("It is White's turn"));
6449             } else if (toX >= 0 && toY >= 0) {
6450                 premoveToX = toX;
6451                 premoveToY = toY;
6452                 premoveFromX = fromX;
6453                 premoveFromY = fromY;
6454                 premovePromoChar = promoChar;
6455                 gotPremove = 1;
6456                 if (appData.debugMode)
6457                     fprintf(debugFP, "Got premove: fromX %d,"
6458                             "fromY %d, toX %d, toY %d\n",
6459                             fromX, fromY, toX, toY);
6460             }
6461             return;
6462         }
6463         break;
6464
6465       case IcsPlayingWhite:
6466         /* User is moving for White */
6467         if (!WhiteOnMove(currentMove)) {
6468             if (!appData.premove) {
6469                 DisplayMoveError(_("It is Black's turn"));
6470             } else if (toX >= 0 && toY >= 0) {
6471                 premoveToX = toX;
6472                 premoveToY = toY;
6473                 premoveFromX = fromX;
6474                 premoveFromY = fromY;
6475                 premovePromoChar = promoChar;
6476                 gotPremove = 1;
6477                 if (appData.debugMode)
6478                     fprintf(debugFP, "Got premove: fromX %d,"
6479                             "fromY %d, toX %d, toY %d\n",
6480                             fromX, fromY, toX, toY);
6481             }
6482             return;
6483         }
6484         break;
6485
6486       default:
6487         break;
6488
6489       case EditPosition:
6490         /* EditPosition, empty square, or different color piece;
6491            click-click move is possible */
6492         if (toX == -2 || toY == -2) {
6493             boards[0][fromY][fromX] = EmptySquare;
6494             DrawPosition(FALSE, boards[currentMove]);
6495             return;
6496         } else if (toX >= 0 && toY >= 0) {
6497             boards[0][toY][toX] = boards[0][fromY][fromX];
6498             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6499                 if(boards[0][fromY][0] != EmptySquare) {
6500                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6501                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6502                 }
6503             } else
6504             if(fromX == BOARD_RGHT+1) {
6505                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6506                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6507                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6508                 }
6509             } else
6510             boards[0][fromY][fromX] = EmptySquare;
6511             DrawPosition(FALSE, boards[currentMove]);
6512             return;
6513         }
6514         return;
6515     }
6516
6517     if(toX < 0 || toY < 0) return;
6518     pdown = boards[currentMove][fromY][fromX];
6519     pup = boards[currentMove][toY][toX];
6520
6521     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6522     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6523          if( pup != EmptySquare ) return;
6524          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6525            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6526                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6527            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6528            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6529            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6530            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6531          fromY = DROP_RANK;
6532     }
6533
6534     /* [HGM] always test for legality, to get promotion info */
6535     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6536                                          fromY, fromX, toY, toX, promoChar);
6537
6538     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6539
6540     /* [HGM] but possibly ignore an IllegalMove result */
6541     if (appData.testLegality) {
6542         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6543             DisplayMoveError(_("Illegal move"));
6544             return;
6545         }
6546     }
6547
6548     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6549 }
6550
6551 /* Common tail of UserMoveEvent and DropMenuEvent */
6552 int
6553 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6554      ChessMove moveType;
6555      int fromX, fromY, toX, toY;
6556      /*char*/int promoChar;
6557 {
6558     char *bookHit = 0;
6559
6560     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6561         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6562         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6563         if(WhiteOnMove(currentMove)) {
6564             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6565         } else {
6566             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6567         }
6568     }
6569
6570     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6571        move type in caller when we know the move is a legal promotion */
6572     if(moveType == NormalMove && promoChar)
6573         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6574
6575     /* [HGM] <popupFix> The following if has been moved here from
6576        UserMoveEvent(). Because it seemed to belong here (why not allow
6577        piece drops in training games?), and because it can only be
6578        performed after it is known to what we promote. */
6579     if (gameMode == Training) {
6580       /* compare the move played on the board to the next move in the
6581        * game. If they match, display the move and the opponent's response.
6582        * If they don't match, display an error message.
6583        */
6584       int saveAnimate;
6585       Board testBoard;
6586       CopyBoard(testBoard, boards[currentMove]);
6587       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6588
6589       if (CompareBoards(testBoard, boards[currentMove+1])) {
6590         ForwardInner(currentMove+1);
6591
6592         /* Autoplay the opponent's response.
6593          * if appData.animate was TRUE when Training mode was entered,
6594          * the response will be animated.
6595          */
6596         saveAnimate = appData.animate;
6597         appData.animate = animateTraining;
6598         ForwardInner(currentMove+1);
6599         appData.animate = saveAnimate;
6600
6601         /* check for the end of the game */
6602         if (currentMove >= forwardMostMove) {
6603           gameMode = PlayFromGameFile;
6604           ModeHighlight();
6605           SetTrainingModeOff();
6606           DisplayInformation(_("End of game"));
6607         }
6608       } else {
6609         DisplayError(_("Incorrect move"), 0);
6610       }
6611       return 1;
6612     }
6613
6614   /* Ok, now we know that the move is good, so we can kill
6615      the previous line in Analysis Mode */
6616   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6617                                 && currentMove < forwardMostMove) {
6618     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6619     else forwardMostMove = currentMove;
6620   }
6621
6622   /* If we need the chess program but it's dead, restart it */
6623   ResurrectChessProgram();
6624
6625   /* A user move restarts a paused game*/
6626   if (pausing)
6627     PauseEvent();
6628
6629   thinkOutput[0] = NULLCHAR;
6630
6631   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6632
6633   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6634     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6635     return 1;
6636   }
6637
6638   if (gameMode == BeginningOfGame) {
6639     if (appData.noChessProgram) {
6640       gameMode = EditGame;
6641       SetGameInfo();
6642     } else {
6643       char buf[MSG_SIZ];
6644       gameMode = MachinePlaysBlack;
6645       StartClocks();
6646       SetGameInfo();
6647       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6648       DisplayTitle(buf);
6649       if (first.sendName) {
6650         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6651         SendToProgram(buf, &first);
6652       }
6653       StartClocks();
6654     }
6655     ModeHighlight();
6656   }
6657
6658   /* Relay move to ICS or chess engine */
6659   if (appData.icsActive) {
6660     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6661         gameMode == IcsExamining) {
6662       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6663         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6664         SendToICS("draw ");
6665         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6666       }
6667       // also send plain move, in case ICS does not understand atomic claims
6668       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6669       ics_user_moved = 1;
6670     }
6671   } else {
6672     if (first.sendTime && (gameMode == BeginningOfGame ||
6673                            gameMode == MachinePlaysWhite ||
6674                            gameMode == MachinePlaysBlack)) {
6675       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6676     }
6677     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6678          // [HGM] book: if program might be playing, let it use book
6679         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6680         first.maybeThinking = TRUE;
6681     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6682         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6683         SendBoard(&first, currentMove+1);
6684     } else SendMoveToProgram(forwardMostMove-1, &first);
6685     if (currentMove == cmailOldMove + 1) {
6686       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6687     }
6688   }
6689
6690   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6691
6692   switch (gameMode) {
6693   case EditGame:
6694     if(appData.testLegality)
6695     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6696     case MT_NONE:
6697     case MT_CHECK:
6698       break;
6699     case MT_CHECKMATE:
6700     case MT_STAINMATE:
6701       if (WhiteOnMove(currentMove)) {
6702         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6703       } else {
6704         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6705       }
6706       break;
6707     case MT_STALEMATE:
6708       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6709       break;
6710     }
6711     break;
6712
6713   case MachinePlaysBlack:
6714   case MachinePlaysWhite:
6715     /* disable certain menu options while machine is thinking */
6716     SetMachineThinkingEnables();
6717     break;
6718
6719   default:
6720     break;
6721   }
6722
6723   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6724   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6725
6726   if(bookHit) { // [HGM] book: simulate book reply
6727         static char bookMove[MSG_SIZ]; // a bit generous?
6728
6729         programStats.nodes = programStats.depth = programStats.time =
6730         programStats.score = programStats.got_only_move = 0;
6731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6732
6733         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6734         strcat(bookMove, bookHit);
6735         HandleMachineMove(bookMove, &first);
6736   }
6737   return 1;
6738 }
6739
6740 void
6741 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6742      Board board;
6743      int flags;
6744      ChessMove kind;
6745      int rf, ff, rt, ft;
6746      VOIDSTAR closure;
6747 {
6748     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6749     Markers *m = (Markers *) closure;
6750     if(rf == fromY && ff == fromX)
6751         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6752                          || kind == WhiteCapturesEnPassant
6753                          || kind == BlackCapturesEnPassant);
6754     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6755 }
6756
6757 void
6758 MarkTargetSquares(int clear)
6759 {
6760   int x, y;
6761   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6762      !appData.testLegality || gameMode == EditPosition) return;
6763   if(clear) {
6764     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6765   } else {
6766     int capt = 0;
6767     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6768     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6769       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6770       if(capt)
6771       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6772     }
6773   }
6774   DrawPosition(TRUE, NULL);
6775 }
6776
6777 int
6778 Explode(Board board, int fromX, int fromY, int toX, int toY)
6779 {
6780     if(gameInfo.variant == VariantAtomic &&
6781        (board[toY][toX] != EmptySquare ||                     // capture?
6782         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6783                          board[fromY][fromX] == BlackPawn   )
6784       )) {
6785         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6786         return TRUE;
6787     }
6788     return FALSE;
6789 }
6790
6791 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6792
6793 int CanPromote(ChessSquare piece, int y)
6794 {
6795         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6796         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6797         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6798            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6799            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6800                                                   gameInfo.variant == VariantMakruk) return FALSE;
6801         return (piece == BlackPawn && y == 1 ||
6802                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6803                 piece == BlackLance && y == 1 ||
6804                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6805 }
6806
6807 void LeftClick(ClickType clickType, int xPix, int yPix)
6808 {
6809     int x, y;
6810     Boolean saveAnimate;
6811     static int second = 0, promotionChoice = 0, clearFlag = 0;
6812     char promoChoice = NULLCHAR;
6813     ChessSquare piece;
6814
6815     if(appData.seekGraph && appData.icsActive && loggedOn &&
6816         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6817         SeekGraphClick(clickType, xPix, yPix, 0);
6818         return;
6819     }
6820
6821     if (clickType == Press) ErrorPopDown();
6822
6823     x = EventToSquare(xPix, BOARD_WIDTH);
6824     y = EventToSquare(yPix, BOARD_HEIGHT);
6825     if (!flipView && y >= 0) {
6826         y = BOARD_HEIGHT - 1 - y;
6827     }
6828     if (flipView && x >= 0) {
6829         x = BOARD_WIDTH - 1 - x;
6830     }
6831
6832     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6833         defaultPromoChoice = promoSweep;
6834         promoSweep = EmptySquare;   // terminate sweep
6835         promoDefaultAltered = TRUE;
6836         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6837     }
6838
6839     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6840         if(clickType == Release) return; // ignore upclick of click-click destination
6841         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6842         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6843         if(gameInfo.holdingsWidth &&
6844                 (WhiteOnMove(currentMove)
6845                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6846                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6847             // click in right holdings, for determining promotion piece
6848             ChessSquare p = boards[currentMove][y][x];
6849             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6850             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6851             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6852                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6853                 fromX = fromY = -1;
6854                 return;
6855             }
6856         }
6857         DrawPosition(FALSE, boards[currentMove]);
6858         return;
6859     }
6860
6861     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6862     if(clickType == Press
6863             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6864               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6865               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6866         return;
6867
6868     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6869         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6870
6871     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6872         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6873                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6874         defaultPromoChoice = DefaultPromoChoice(side);
6875     }
6876
6877     autoQueen = appData.alwaysPromoteToQueen;
6878
6879     if (fromX == -1) {
6880       int originalY = y;
6881       gatingPiece = EmptySquare;
6882       if (clickType != Press) {
6883         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6884             DragPieceEnd(xPix, yPix); dragging = 0;
6885             DrawPosition(FALSE, NULL);
6886         }
6887         return;
6888       }
6889       fromX = x; fromY = y; toX = toY = -1;
6890       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6891          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6892          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6893             /* First square */
6894             if (OKToStartUserMove(fromX, fromY)) {
6895                 second = 0;
6896                 MarkTargetSquares(0);
6897                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6898                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6899                     promoSweep = defaultPromoChoice;
6900                     selectFlag = 0; lastX = xPix; lastY = yPix;
6901                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6902                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6903                 }
6904                 if (appData.highlightDragging) {
6905                     SetHighlights(fromX, fromY, -1, -1);
6906                 }
6907             } else fromX = fromY = -1;
6908             return;
6909         }
6910     }
6911
6912     /* fromX != -1 */
6913     if (clickType == Press && gameMode != EditPosition) {
6914         ChessSquare fromP;
6915         ChessSquare toP;
6916         int frc;
6917
6918         // ignore off-board to clicks
6919         if(y < 0 || x < 0) return;
6920
6921         /* Check if clicking again on the same color piece */
6922         fromP = boards[currentMove][fromY][fromX];
6923         toP = boards[currentMove][y][x];
6924         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6925         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6926              WhitePawn <= toP && toP <= WhiteKing &&
6927              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6928              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6929             (BlackPawn <= fromP && fromP <= BlackKing &&
6930              BlackPawn <= toP && toP <= BlackKing &&
6931              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6932              !(fromP == BlackKing && toP == BlackRook && frc))) {
6933             /* Clicked again on same color piece -- changed his mind */
6934             second = (x == fromX && y == fromY);
6935             promoDefaultAltered = FALSE;
6936             MarkTargetSquares(1);
6937            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6938             if (appData.highlightDragging) {
6939                 SetHighlights(x, y, -1, -1);
6940             } else {
6941                 ClearHighlights();
6942             }
6943             if (OKToStartUserMove(x, y)) {
6944                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6945                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6946                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6947                  gatingPiece = boards[currentMove][fromY][fromX];
6948                 else gatingPiece = EmptySquare;
6949                 fromX = x;
6950                 fromY = y; dragging = 1;
6951                 MarkTargetSquares(0);
6952                 DragPieceBegin(xPix, yPix, FALSE);
6953                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6954                     promoSweep = defaultPromoChoice;
6955                     selectFlag = 0; lastX = xPix; lastY = yPix;
6956                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6957                 }
6958             }
6959            }
6960            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6961            second = FALSE; 
6962         }
6963         // ignore clicks on holdings
6964         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6965     }
6966
6967     if (clickType == Release && x == fromX && y == fromY) {
6968         DragPieceEnd(xPix, yPix); dragging = 0;
6969         if(clearFlag) {
6970             // a deferred attempt to click-click move an empty square on top of a piece
6971             boards[currentMove][y][x] = EmptySquare;
6972             ClearHighlights();
6973             DrawPosition(FALSE, boards[currentMove]);
6974             fromX = fromY = -1; clearFlag = 0;
6975             return;
6976         }
6977         if (appData.animateDragging) {
6978             /* Undo animation damage if any */
6979             DrawPosition(FALSE, NULL);
6980         }
6981         if (second) {
6982             /* Second up/down in same square; just abort move */
6983             second = 0;
6984             fromX = fromY = -1;
6985             gatingPiece = EmptySquare;
6986             ClearHighlights();
6987             gotPremove = 0;
6988             ClearPremoveHighlights();
6989         } else {
6990             /* First upclick in same square; start click-click mode */
6991             SetHighlights(x, y, -1, -1);
6992         }
6993         return;
6994     }
6995
6996     clearFlag = 0;
6997
6998     /* we now have a different from- and (possibly off-board) to-square */
6999     /* Completed move */
7000     toX = x;
7001     toY = y;
7002     saveAnimate = appData.animate;
7003     MarkTargetSquares(1);
7004     if (clickType == Press) {
7005         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7006             // must be Edit Position mode with empty-square selected
7007             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7008             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7009             return;
7010         }
7011         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7012             ChessSquare piece = boards[currentMove][fromY][fromX];
7013             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7014             promoSweep = defaultPromoChoice;
7015             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7016             selectFlag = 0; lastX = xPix; lastY = yPix;
7017             Sweep(0); // Pawn that is going to promote: preview promotion piece
7018             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7019             DrawPosition(FALSE, boards[currentMove]);
7020             return;
7021         }
7022         /* Finish clickclick move */
7023         if (appData.animate || appData.highlightLastMove) {
7024             SetHighlights(fromX, fromY, toX, toY);
7025         } else {
7026             ClearHighlights();
7027         }
7028     } else {
7029         /* Finish drag move */
7030         if (appData.highlightLastMove) {
7031             SetHighlights(fromX, fromY, toX, toY);
7032         } else {
7033             ClearHighlights();
7034         }
7035         DragPieceEnd(xPix, yPix); dragging = 0;
7036         /* Don't animate move and drag both */
7037         appData.animate = FALSE;
7038     }
7039
7040     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7041     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7042         ChessSquare piece = boards[currentMove][fromY][fromX];
7043         if(gameMode == EditPosition && piece != EmptySquare &&
7044            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7045             int n;
7046
7047             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7048                 n = PieceToNumber(piece - (int)BlackPawn);
7049                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7050                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7051                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7052             } else
7053             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7054                 n = PieceToNumber(piece);
7055                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7056                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7057                 boards[currentMove][n][BOARD_WIDTH-2]++;
7058             }
7059             boards[currentMove][fromY][fromX] = EmptySquare;
7060         }
7061         ClearHighlights();
7062         fromX = fromY = -1;
7063         DrawPosition(TRUE, boards[currentMove]);
7064         return;
7065     }
7066
7067     // off-board moves should not be highlighted
7068     if(x < 0 || y < 0) ClearHighlights();
7069
7070     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7071
7072     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7073         SetHighlights(fromX, fromY, toX, toY);
7074         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7075             // [HGM] super: promotion to captured piece selected from holdings
7076             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7077             promotionChoice = TRUE;
7078             // kludge follows to temporarily execute move on display, without promoting yet
7079             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7080             boards[currentMove][toY][toX] = p;
7081             DrawPosition(FALSE, boards[currentMove]);
7082             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7083             boards[currentMove][toY][toX] = q;
7084             DisplayMessage("Click in holdings to choose piece", "");
7085             return;
7086         }
7087         PromotionPopUp();
7088     } else {
7089         int oldMove = currentMove;
7090         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7091         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7092         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7093         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7094            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7095             DrawPosition(TRUE, boards[currentMove]);
7096         fromX = fromY = -1;
7097     }
7098     appData.animate = saveAnimate;
7099     if (appData.animate || appData.animateDragging) {
7100         /* Undo animation damage if needed */
7101         DrawPosition(FALSE, NULL);
7102     }
7103 }
7104
7105 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7106 {   // front-end-free part taken out of PieceMenuPopup
7107     int whichMenu; int xSqr, ySqr;
7108
7109     if(seekGraphUp) { // [HGM] seekgraph
7110         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7111         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7112         return -2;
7113     }
7114
7115     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7116          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7117         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7118         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7119         if(action == Press)   {
7120             originalFlip = flipView;
7121             flipView = !flipView; // temporarily flip board to see game from partners perspective
7122             DrawPosition(TRUE, partnerBoard);
7123             DisplayMessage(partnerStatus, "");
7124             partnerUp = TRUE;
7125         } else if(action == Release) {
7126             flipView = originalFlip;
7127             DrawPosition(TRUE, boards[currentMove]);
7128             partnerUp = FALSE;
7129         }
7130         return -2;
7131     }
7132
7133     xSqr = EventToSquare(x, BOARD_WIDTH);
7134     ySqr = EventToSquare(y, BOARD_HEIGHT);
7135     if (action == Release) {
7136         if(pieceSweep != EmptySquare) {
7137             EditPositionMenuEvent(pieceSweep, toX, toY);
7138             pieceSweep = EmptySquare;
7139         } else UnLoadPV(); // [HGM] pv
7140     }
7141     if (action != Press) return -2; // return code to be ignored
7142     switch (gameMode) {
7143       case IcsExamining:
7144         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7145       case EditPosition:
7146         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7147         if (xSqr < 0 || ySqr < 0) return -1;
7148         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7149         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7150         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7151         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7152         NextPiece(0);
7153         return 2; // grab
7154       case IcsObserving:
7155         if(!appData.icsEngineAnalyze) return -1;
7156       case IcsPlayingWhite:
7157       case IcsPlayingBlack:
7158         if(!appData.zippyPlay) goto noZip;
7159       case AnalyzeMode:
7160       case AnalyzeFile:
7161       case MachinePlaysWhite:
7162       case MachinePlaysBlack:
7163       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7164         if (!appData.dropMenu) {
7165           LoadPV(x, y);
7166           return 2; // flag front-end to grab mouse events
7167         }
7168         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7169            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7170       case EditGame:
7171       noZip:
7172         if (xSqr < 0 || ySqr < 0) return -1;
7173         if (!appData.dropMenu || appData.testLegality &&
7174             gameInfo.variant != VariantBughouse &&
7175             gameInfo.variant != VariantCrazyhouse) return -1;
7176         whichMenu = 1; // drop menu
7177         break;
7178       default:
7179         return -1;
7180     }
7181
7182     if (((*fromX = xSqr) < 0) ||
7183         ((*fromY = ySqr) < 0)) {
7184         *fromX = *fromY = -1;
7185         return -1;
7186     }
7187     if (flipView)
7188       *fromX = BOARD_WIDTH - 1 - *fromX;
7189     else
7190       *fromY = BOARD_HEIGHT - 1 - *fromY;
7191
7192     return whichMenu;
7193 }
7194
7195 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7196 {
7197 //    char * hint = lastHint;
7198     FrontEndProgramStats stats;
7199
7200     stats.which = cps == &first ? 0 : 1;
7201     stats.depth = cpstats->depth;
7202     stats.nodes = cpstats->nodes;
7203     stats.score = cpstats->score;
7204     stats.time = cpstats->time;
7205     stats.pv = cpstats->movelist;
7206     stats.hint = lastHint;
7207     stats.an_move_index = 0;
7208     stats.an_move_count = 0;
7209
7210     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7211         stats.hint = cpstats->move_name;
7212         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7213         stats.an_move_count = cpstats->nr_moves;
7214     }
7215
7216     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
7217
7218     SetProgramStats( &stats );
7219 }
7220
7221 void
7222 ClearEngineOutputPane(int which)
7223 {
7224     static FrontEndProgramStats dummyStats;
7225     dummyStats.which = which;
7226     dummyStats.pv = "#";
7227     SetProgramStats( &dummyStats );
7228 }
7229
7230 #define MAXPLAYERS 500
7231
7232 char *
7233 TourneyStandings(int display)
7234 {
7235     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7236     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7237     char result, *p, *names[MAXPLAYERS];
7238
7239     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7240         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7241     names[0] = p = strdup(appData.participants);
7242     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7243
7244     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7245
7246     while(result = appData.results[nr]) {
7247         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7248         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7249         wScore = bScore = 0;
7250         switch(result) {
7251           case '+': wScore = 2; break;
7252           case '-': bScore = 2; break;
7253           case '=': wScore = bScore = 1; break;
7254           case ' ':
7255           case '*': return strdup("busy"); // tourney not finished
7256         }
7257         score[w] += wScore;
7258         score[b] += bScore;
7259         games[w]++;
7260         games[b]++;
7261         nr++;
7262     }
7263     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7264     for(w=0; w<nPlayers; w++) {
7265         bScore = -1;
7266         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7267         ranking[w] = b; points[w] = bScore; score[b] = -2;
7268     }
7269     p = malloc(nPlayers*34+1);
7270     for(w=0; w<nPlayers && w<display; w++)
7271         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7272     free(names[0]);
7273     return p;
7274 }
7275
7276 void
7277 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7278 {       // count all piece types
7279         int p, f, r;
7280         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7281         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7282         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7283                 p = board[r][f];
7284                 pCnt[p]++;
7285                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7286                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7287                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7288                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7289                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7290                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7291         }
7292 }
7293
7294 int
7295 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7296 {
7297         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7298         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7299
7300         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7301         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7302         if(myPawns == 2 && nMine == 3) // KPP
7303             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7304         if(myPawns == 1 && nMine == 2) // KP
7305             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7306         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7307             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7308         if(myPawns) return FALSE;
7309         if(pCnt[WhiteRook+side])
7310             return pCnt[BlackRook-side] ||
7311                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7312                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7313                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7314         if(pCnt[WhiteCannon+side]) {
7315             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7316             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7317         }
7318         if(pCnt[WhiteKnight+side])
7319             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7320         return FALSE;
7321 }
7322
7323 int
7324 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7325 {
7326         VariantClass v = gameInfo.variant;
7327
7328         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7329         if(v == VariantShatranj) return TRUE; // always winnable through baring
7330         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7331         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7332
7333         if(v == VariantXiangqi) {
7334                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7335
7336                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7337                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7338                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7339                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7340                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7341                 if(stale) // we have at least one last-rank P plus perhaps C
7342                     return majors // KPKX
7343                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7344                 else // KCA*E*
7345                     return pCnt[WhiteFerz+side] // KCAK
7346                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7347                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7348                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7349
7350         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7351                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7352
7353                 if(nMine == 1) return FALSE; // bare King
7354                 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
7355                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7356                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7357                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7358                 if(pCnt[WhiteKnight+side])
7359                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7360                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7361                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7362                 if(nBishops)
7363                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7364                 if(pCnt[WhiteAlfil+side])
7365                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7366                 if(pCnt[WhiteWazir+side])
7367                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7368         }
7369
7370         return TRUE;
7371 }
7372
7373 int
7374 Adjudicate(ChessProgramState *cps)
7375 {       // [HGM] some adjudications useful with buggy engines
7376         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7377         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7378         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7379         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7380         int k, count = 0; static int bare = 1;
7381         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7382         Boolean canAdjudicate = !appData.icsActive;
7383
7384         // most tests only when we understand the game, i.e. legality-checking on
7385             if( appData.testLegality )
7386             {   /* [HGM] Some more adjudications for obstinate engines */
7387                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7388                 static int moveCount = 6;
7389                 ChessMove result;
7390                 char *reason = NULL;
7391
7392                 /* Count what is on board. */
7393                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7394
7395                 /* Some material-based adjudications that have to be made before stalemate test */
7396                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7397                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7398                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7399                      if(canAdjudicate && appData.checkMates) {
7400                          if(engineOpponent)
7401                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7402                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7403                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7404                          return 1;
7405                      }
7406                 }
7407
7408                 /* Bare King in Shatranj (loses) or Losers (wins) */
7409                 if( nrW == 1 || nrB == 1) {
7410                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7411                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7412                      if(canAdjudicate && appData.checkMates) {
7413                          if(engineOpponent)
7414                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7415                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7416                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7417                          return 1;
7418                      }
7419                   } else
7420                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7421                   {    /* bare King */
7422                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7423                         if(canAdjudicate && appData.checkMates) {
7424                             /* but only adjudicate if adjudication enabled */
7425                             if(engineOpponent)
7426                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7427                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7428                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7429                             return 1;
7430                         }
7431                   }
7432                 } else bare = 1;
7433
7434
7435             // don't wait for engine to announce game end if we can judge ourselves
7436             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7437               case MT_CHECK:
7438                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7439                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7440                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7441                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7442                             checkCnt++;
7443                         if(checkCnt >= 2) {
7444                             reason = "Xboard adjudication: 3rd check";
7445                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7446                             break;
7447                         }
7448                     }
7449                 }
7450               case MT_NONE:
7451               default:
7452                 break;
7453               case MT_STALEMATE:
7454               case MT_STAINMATE:
7455                 reason = "Xboard adjudication: Stalemate";
7456                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7457                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7458                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7459                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7460                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7461                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7462                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7463                                                                         EP_CHECKMATE : EP_WINS);
7464                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7465                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7466                 }
7467                 break;
7468               case MT_CHECKMATE:
7469                 reason = "Xboard adjudication: Checkmate";
7470                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7471                 break;
7472             }
7473
7474                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7475                     case EP_STALEMATE:
7476                         result = GameIsDrawn; break;
7477                     case EP_CHECKMATE:
7478                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7479                     case EP_WINS:
7480                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7481                     default:
7482                         result = EndOfFile;
7483                 }
7484                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7485                     if(engineOpponent)
7486                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7487                     GameEnds( result, reason, GE_XBOARD );
7488                     return 1;
7489                 }
7490
7491                 /* Next absolutely insufficient mating material. */
7492                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7493                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7494                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7495
7496                      /* always flag draws, for judging claims */
7497                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7498
7499                      if(canAdjudicate && appData.materialDraws) {
7500                          /* but only adjudicate them if adjudication enabled */
7501                          if(engineOpponent) {
7502                            SendToProgram("force\n", engineOpponent); // suppress reply
7503                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7504                          }
7505                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7506                          return 1;
7507                      }
7508                 }
7509
7510                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7511                 if(gameInfo.variant == VariantXiangqi ?
7512                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7513                  : nrW + nrB == 4 &&
7514                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7515                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7516                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7517                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7518                    ) ) {
7519                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7520                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7521                           if(engineOpponent) {
7522                             SendToProgram("force\n", engineOpponent); // suppress reply
7523                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7524                           }
7525                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7526                           return 1;
7527                      }
7528                 } else moveCount = 6;
7529             }
7530         if (appData.debugMode) { int i;
7531             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7532                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7533                     appData.drawRepeats);
7534             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7535               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7536
7537         }
7538
7539         // Repetition draws and 50-move rule can be applied independently of legality testing
7540
7541                 /* Check for rep-draws */
7542                 count = 0;
7543                 for(k = forwardMostMove-2;
7544                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7545                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7546                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7547                     k-=2)
7548                 {   int rights=0;
7549                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7550                         /* compare castling rights */
7551                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7552                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7553                                 rights++; /* King lost rights, while rook still had them */
7554                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7555                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7556                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7557                                    rights++; /* but at least one rook lost them */
7558                         }
7559                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7560                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7561                                 rights++;
7562                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7563                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7564                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7565                                    rights++;
7566                         }
7567                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7568                             && appData.drawRepeats > 1) {
7569                              /* adjudicate after user-specified nr of repeats */
7570                              int result = GameIsDrawn;
7571                              char *details = "XBoard adjudication: repetition draw";
7572                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7573                                 // [HGM] xiangqi: check for forbidden perpetuals
7574                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7575                                 for(m=forwardMostMove; m>k; m-=2) {
7576                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7577                                         ourPerpetual = 0; // the current mover did not always check
7578                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7579                                         hisPerpetual = 0; // the opponent did not always check
7580                                 }
7581                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7582                                                                         ourPerpetual, hisPerpetual);
7583                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7584                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7585                                     details = "Xboard adjudication: perpetual checking";
7586                                 } else
7587                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7588                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7589                                 } else
7590                                 // Now check for perpetual chases
7591                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7592                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7593                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7594                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7595                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7596                                         details = "Xboard adjudication: perpetual chasing";
7597                                     } else
7598                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7599                                         break; // Abort repetition-checking loop.
7600                                 }
7601                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7602                              }
7603                              if(engineOpponent) {
7604                                SendToProgram("force\n", engineOpponent); // suppress reply
7605                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7606                              }
7607                              GameEnds( result, details, GE_XBOARD );
7608                              return 1;
7609                         }
7610                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7611                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7612                     }
7613                 }
7614
7615                 /* Now we test for 50-move draws. Determine ply count */
7616                 count = forwardMostMove;
7617                 /* look for last irreversble move */
7618                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7619                     count--;
7620                 /* if we hit starting position, add initial plies */
7621                 if( count == backwardMostMove )
7622                     count -= initialRulePlies;
7623                 count = forwardMostMove - count;
7624                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7625                         // adjust reversible move counter for checks in Xiangqi
7626                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7627                         if(i < backwardMostMove) i = backwardMostMove;
7628                         while(i <= forwardMostMove) {
7629                                 lastCheck = inCheck; // check evasion does not count
7630                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7631                                 if(inCheck || lastCheck) count--; // check does not count
7632                                 i++;
7633                         }
7634                 }
7635                 if( count >= 100)
7636                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7637                          /* this is used to judge if draw claims are legal */
7638                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7639                          if(engineOpponent) {
7640                            SendToProgram("force\n", engineOpponent); // suppress reply
7641                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                          }
7643                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7644                          return 1;
7645                 }
7646
7647                 /* if draw offer is pending, treat it as a draw claim
7648                  * when draw condition present, to allow engines a way to
7649                  * claim draws before making their move to avoid a race
7650                  * condition occurring after their move
7651                  */
7652                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7653                          char *p = NULL;
7654                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7655                              p = "Draw claim: 50-move rule";
7656                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7657                              p = "Draw claim: 3-fold repetition";
7658                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7659                              p = "Draw claim: insufficient mating material";
7660                          if( p != NULL && canAdjudicate) {
7661                              if(engineOpponent) {
7662                                SendToProgram("force\n", engineOpponent); // suppress reply
7663                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7664                              }
7665                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7666                              return 1;
7667                          }
7668                 }
7669
7670                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7671                     if(engineOpponent) {
7672                       SendToProgram("force\n", engineOpponent); // suppress reply
7673                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                     }
7675                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7676                     return 1;
7677                 }
7678         return 0;
7679 }
7680
7681 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7682 {   // [HGM] book: this routine intercepts moves to simulate book replies
7683     char *bookHit = NULL;
7684
7685     //first determine if the incoming move brings opponent into his book
7686     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7687         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7688     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7689     if(bookHit != NULL && !cps->bookSuspend) {
7690         // make sure opponent is not going to reply after receiving move to book position
7691         SendToProgram("force\n", cps);
7692         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7693     }
7694     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7695     // now arrange restart after book miss
7696     if(bookHit) {
7697         // after a book hit we never send 'go', and the code after the call to this routine
7698         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7699         char buf[MSG_SIZ], *move = bookHit;
7700         if(cps->useSAN) {
7701             int fromX, fromY, toX, toY;
7702             char promoChar;
7703             ChessMove moveType;
7704             move = buf + 30;
7705             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7706                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7707                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7708                                     PosFlags(forwardMostMove),
7709                                     fromY, fromX, toY, toX, promoChar, move);
7710             } else {
7711                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7712                 bookHit = NULL;
7713             }
7714         }
7715         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7716         SendToProgram(buf, cps);
7717         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7718     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7719         SendToProgram("go\n", cps);
7720         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7721     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7722         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7723             SendToProgram("go\n", cps);
7724         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7725     }
7726     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7727 }
7728
7729 char *savedMessage;
7730 ChessProgramState *savedState;
7731 void DeferredBookMove(void)
7732 {
7733         if(savedState->lastPing != savedState->lastPong)
7734                     ScheduleDelayedEvent(DeferredBookMove, 10);
7735         else
7736         HandleMachineMove(savedMessage, savedState);
7737 }
7738
7739 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7740
7741 void
7742 HandleMachineMove(message, cps)
7743      char *message;
7744      ChessProgramState *cps;
7745 {
7746     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7747     char realname[MSG_SIZ];
7748     int fromX, fromY, toX, toY;
7749     ChessMove moveType;
7750     char promoChar;
7751     char *p, *pv=buf1;
7752     int machineWhite;
7753     char *bookHit;
7754
7755     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7756         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7757         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7758             DisplayError(_("Invalid pairing from pairing engine"), 0);
7759             return;
7760         }
7761         pairingReceived = 1;
7762         NextMatchGame();
7763         return; // Skim the pairing messages here.
7764     }
7765
7766     cps->userError = 0;
7767
7768 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7769     /*
7770      * Kludge to ignore BEL characters
7771      */
7772     while (*message == '\007') message++;
7773
7774     /*
7775      * [HGM] engine debug message: ignore lines starting with '#' character
7776      */
7777     if(cps->debug && *message == '#') return;
7778
7779     /*
7780      * Look for book output
7781      */
7782     if (cps == &first && bookRequested) {
7783         if (message[0] == '\t' || message[0] == ' ') {
7784             /* Part of the book output is here; append it */
7785             strcat(bookOutput, message);
7786             strcat(bookOutput, "  \n");
7787             return;
7788         } else if (bookOutput[0] != NULLCHAR) {
7789             /* All of book output has arrived; display it */
7790             char *p = bookOutput;
7791             while (*p != NULLCHAR) {
7792                 if (*p == '\t') *p = ' ';
7793                 p++;
7794             }
7795             DisplayInformation(bookOutput);
7796             bookRequested = FALSE;
7797             /* Fall through to parse the current output */
7798         }
7799     }
7800
7801     /*
7802      * Look for machine move.
7803      */
7804     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7805         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7806     {
7807         /* This method is only useful on engines that support ping */
7808         if (cps->lastPing != cps->lastPong) {
7809           if (gameMode == BeginningOfGame) {
7810             /* Extra move from before last new; ignore */
7811             if (appData.debugMode) {
7812                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7813             }
7814           } else {
7815             if (appData.debugMode) {
7816                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7817                         cps->which, gameMode);
7818             }
7819
7820             SendToProgram("undo\n", cps);
7821           }
7822           return;
7823         }
7824
7825         switch (gameMode) {
7826           case BeginningOfGame:
7827             /* Extra move from before last reset; ignore */
7828             if (appData.debugMode) {
7829                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7830             }
7831             return;
7832
7833           case EndOfGame:
7834           case IcsIdle:
7835           default:
7836             /* Extra move after we tried to stop.  The mode test is
7837                not a reliable way of detecting this problem, but it's
7838                the best we can do on engines that don't support ping.
7839             */
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7842                         cps->which, gameMode);
7843             }
7844             SendToProgram("undo\n", cps);
7845             return;
7846
7847           case MachinePlaysWhite:
7848           case IcsPlayingWhite:
7849             machineWhite = TRUE;
7850             break;
7851
7852           case MachinePlaysBlack:
7853           case IcsPlayingBlack:
7854             machineWhite = FALSE;
7855             break;
7856
7857           case TwoMachinesPlay:
7858             machineWhite = (cps->twoMachinesColor[0] == 'w');
7859             break;
7860         }
7861         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7862             if (appData.debugMode) {
7863                 fprintf(debugFP,
7864                         "Ignoring move out of turn by %s, gameMode %d"
7865                         ", forwardMost %d\n",
7866                         cps->which, gameMode, forwardMostMove);
7867             }
7868             return;
7869         }
7870
7871     if (appData.debugMode) { int f = forwardMostMove;
7872         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7873                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7874                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7875     }
7876         if(cps->alphaRank) AlphaRank(machineMove, 4);
7877         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7878                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7879             /* Machine move could not be parsed; ignore it. */
7880           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7881                     machineMove, _(cps->which));
7882             DisplayError(buf1, 0);
7883             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7884                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7885             if (gameMode == TwoMachinesPlay) {
7886               GameEnds(machineWhite ? BlackWins : WhiteWins,
7887                        buf1, GE_XBOARD);
7888             }
7889             return;
7890         }
7891
7892         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7893         /* So we have to redo legality test with true e.p. status here,  */
7894         /* to make sure an illegal e.p. capture does not slip through,   */
7895         /* to cause a forfeit on a justified illegal-move complaint      */
7896         /* of the opponent.                                              */
7897         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7898            ChessMove moveType;
7899            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7900                              fromY, fromX, toY, toX, promoChar);
7901             if (appData.debugMode) {
7902                 int i;
7903                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7904                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7905                 fprintf(debugFP, "castling rights\n");
7906             }
7907             if(moveType == IllegalMove) {
7908               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7909                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7910                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7911                            buf1, GE_XBOARD);
7912                 return;
7913            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7914            /* [HGM] Kludge to handle engines that send FRC-style castling
7915               when they shouldn't (like TSCP-Gothic) */
7916            switch(moveType) {
7917              case WhiteASideCastleFR:
7918              case BlackASideCastleFR:
7919                toX+=2;
7920                currentMoveString[2]++;
7921                break;
7922              case WhiteHSideCastleFR:
7923              case BlackHSideCastleFR:
7924                toX--;
7925                currentMoveString[2]--;
7926                break;
7927              default: ; // nothing to do, but suppresses warning of pedantic compilers
7928            }
7929         }
7930         hintRequested = FALSE;
7931         lastHint[0] = NULLCHAR;
7932         bookRequested = FALSE;
7933         /* Program may be pondering now */
7934         cps->maybeThinking = TRUE;
7935         if (cps->sendTime == 2) cps->sendTime = 1;
7936         if (cps->offeredDraw) cps->offeredDraw--;
7937
7938         /* [AS] Save move info*/
7939         pvInfoList[ forwardMostMove ].score = programStats.score;
7940         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7941         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7942
7943         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7944
7945         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7946         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7947             int count = 0;
7948
7949             while( count < adjudicateLossPlies ) {
7950                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7951
7952                 if( count & 1 ) {
7953                     score = -score; /* Flip score for winning side */
7954                 }
7955
7956                 if( score > adjudicateLossThreshold ) {
7957                     break;
7958                 }
7959
7960                 count++;
7961             }
7962
7963             if( count >= adjudicateLossPlies ) {
7964                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7965
7966                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7967                     "Xboard adjudication",
7968                     GE_XBOARD );
7969
7970                 return;
7971             }
7972         }
7973
7974         if(Adjudicate(cps)) {
7975             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7976             return; // [HGM] adjudicate: for all automatic game ends
7977         }
7978
7979 #if ZIPPY
7980         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7981             first.initDone) {
7982           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7983                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7984                 SendToICS("draw ");
7985                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7986           }
7987           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7988           ics_user_moved = 1;
7989           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7990                 char buf[3*MSG_SIZ];
7991
7992                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7993                         programStats.score / 100.,
7994                         programStats.depth,
7995                         programStats.time / 100.,
7996                         (unsigned int)programStats.nodes,
7997                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7998                         programStats.movelist);
7999                 SendToICS(buf);
8000 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8001           }
8002         }
8003 #endif
8004
8005         /* [AS] Clear stats for next move */
8006         ClearProgramStats();
8007         thinkOutput[0] = NULLCHAR;
8008         hiddenThinkOutputState = 0;
8009
8010         bookHit = NULL;
8011         if (gameMode == TwoMachinesPlay) {
8012             /* [HGM] relaying draw offers moved to after reception of move */
8013             /* and interpreting offer as claim if it brings draw condition */
8014             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8015                 SendToProgram("draw\n", cps->other);
8016             }
8017             if (cps->other->sendTime) {
8018                 SendTimeRemaining(cps->other,
8019                                   cps->other->twoMachinesColor[0] == 'w');
8020             }
8021             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8022             if (firstMove && !bookHit) {
8023                 firstMove = FALSE;
8024                 if (cps->other->useColors) {
8025                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8026                 }
8027                 SendToProgram("go\n", cps->other);
8028             }
8029             cps->other->maybeThinking = TRUE;
8030         }
8031
8032         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8033
8034         if (!pausing && appData.ringBellAfterMoves) {
8035             RingBell();
8036         }
8037
8038         /*
8039          * Reenable menu items that were disabled while
8040          * machine was thinking
8041          */
8042         if (gameMode != TwoMachinesPlay)
8043             SetUserThinkingEnables();
8044
8045         // [HGM] book: after book hit opponent has received move and is now in force mode
8046         // force the book reply into it, and then fake that it outputted this move by jumping
8047         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8048         if(bookHit) {
8049                 static char bookMove[MSG_SIZ]; // a bit generous?
8050
8051                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8052                 strcat(bookMove, bookHit);
8053                 message = bookMove;
8054                 cps = cps->other;
8055                 programStats.nodes = programStats.depth = programStats.time =
8056                 programStats.score = programStats.got_only_move = 0;
8057                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8058
8059                 if(cps->lastPing != cps->lastPong) {
8060                     savedMessage = message; // args for deferred call
8061                     savedState = cps;
8062                     ScheduleDelayedEvent(DeferredBookMove, 10);
8063                     return;
8064                 }
8065                 goto FakeBookMove;
8066         }
8067
8068         return;
8069     }
8070
8071     /* Set special modes for chess engines.  Later something general
8072      *  could be added here; for now there is just one kludge feature,
8073      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8074      *  when "xboard" is given as an interactive command.
8075      */
8076     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8077         cps->useSigint = FALSE;
8078         cps->useSigterm = FALSE;
8079     }
8080     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8081       ParseFeatures(message+8, cps);
8082       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8083     }
8084
8085     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8086       int dummy, s=6; char buf[MSG_SIZ];
8087       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8088       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8089       ParseFEN(boards[0], &dummy, message+s);
8090       DrawPosition(TRUE, boards[0]);
8091       startedFromSetupPosition = TRUE;
8092       return;
8093     }
8094     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8095      * want this, I was asked to put it in, and obliged.
8096      */
8097     if (!strncmp(message, "setboard ", 9)) {
8098         Board initial_position;
8099
8100         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8101
8102         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8103             DisplayError(_("Bad FEN received from engine"), 0);
8104             return ;
8105         } else {
8106            Reset(TRUE, FALSE);
8107            CopyBoard(boards[0], initial_position);
8108            initialRulePlies = FENrulePlies;
8109            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8110            else gameMode = MachinePlaysBlack;
8111            DrawPosition(FALSE, boards[currentMove]);
8112         }
8113         return;
8114     }
8115
8116     /*
8117      * Look for communication commands
8118      */
8119     if (!strncmp(message, "telluser ", 9)) {
8120         if(message[9] == '\\' && message[10] == '\\')
8121             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8122         PlayTellSound();
8123         DisplayNote(message + 9);
8124         return;
8125     }
8126     if (!strncmp(message, "tellusererror ", 14)) {
8127         cps->userError = 1;
8128         if(message[14] == '\\' && message[15] == '\\')
8129             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8130         PlayTellSound();
8131         DisplayError(message + 14, 0);
8132         return;
8133     }
8134     if (!strncmp(message, "tellopponent ", 13)) {
8135       if (appData.icsActive) {
8136         if (loggedOn) {
8137           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8138           SendToICS(buf1);
8139         }
8140       } else {
8141         DisplayNote(message + 13);
8142       }
8143       return;
8144     }
8145     if (!strncmp(message, "tellothers ", 11)) {
8146       if (appData.icsActive) {
8147         if (loggedOn) {
8148           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8149           SendToICS(buf1);
8150         }
8151       }
8152       return;
8153     }
8154     if (!strncmp(message, "tellall ", 8)) {
8155       if (appData.icsActive) {
8156         if (loggedOn) {
8157           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8158           SendToICS(buf1);
8159         }
8160       } else {
8161         DisplayNote(message + 8);
8162       }
8163       return;
8164     }
8165     if (strncmp(message, "warning", 7) == 0) {
8166         /* Undocumented feature, use tellusererror in new code */
8167         DisplayError(message, 0);
8168         return;
8169     }
8170     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8171         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8172         strcat(realname, " query");
8173         AskQuestion(realname, buf2, buf1, cps->pr);
8174         return;
8175     }
8176     /* Commands from the engine directly to ICS.  We don't allow these to be
8177      *  sent until we are logged on. Crafty kibitzes have been known to
8178      *  interfere with the login process.
8179      */
8180     if (loggedOn) {
8181         if (!strncmp(message, "tellics ", 8)) {
8182             SendToICS(message + 8);
8183             SendToICS("\n");
8184             return;
8185         }
8186         if (!strncmp(message, "tellicsnoalias ", 15)) {
8187             SendToICS(ics_prefix);
8188             SendToICS(message + 15);
8189             SendToICS("\n");
8190             return;
8191         }
8192         /* The following are for backward compatibility only */
8193         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8194             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8195             SendToICS(ics_prefix);
8196             SendToICS(message);
8197             SendToICS("\n");
8198             return;
8199         }
8200     }
8201     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8202         return;
8203     }
8204     /*
8205      * If the move is illegal, cancel it and redraw the board.
8206      * Also deal with other error cases.  Matching is rather loose
8207      * here to accommodate engines written before the spec.
8208      */
8209     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8210         strncmp(message, "Error", 5) == 0) {
8211         if (StrStr(message, "name") ||
8212             StrStr(message, "rating") || StrStr(message, "?") ||
8213             StrStr(message, "result") || StrStr(message, "board") ||
8214             StrStr(message, "bk") || StrStr(message, "computer") ||
8215             StrStr(message, "variant") || StrStr(message, "hint") ||
8216             StrStr(message, "random") || StrStr(message, "depth") ||
8217             StrStr(message, "accepted")) {
8218             return;
8219         }
8220         if (StrStr(message, "protover")) {
8221           /* Program is responding to input, so it's apparently done
8222              initializing, and this error message indicates it is
8223              protocol version 1.  So we don't need to wait any longer
8224              for it to initialize and send feature commands. */
8225           FeatureDone(cps, 1);
8226           cps->protocolVersion = 1;
8227           return;
8228         }
8229         cps->maybeThinking = FALSE;
8230
8231         if (StrStr(message, "draw")) {
8232             /* Program doesn't have "draw" command */
8233             cps->sendDrawOffers = 0;
8234             return;
8235         }
8236         if (cps->sendTime != 1 &&
8237             (StrStr(message, "time") || StrStr(message, "otim"))) {
8238           /* Program apparently doesn't have "time" or "otim" command */
8239           cps->sendTime = 0;
8240           return;
8241         }
8242         if (StrStr(message, "analyze")) {
8243             cps->analysisSupport = FALSE;
8244             cps->analyzing = FALSE;
8245             Reset(FALSE, TRUE);
8246             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8247             DisplayError(buf2, 0);
8248             return;
8249         }
8250         if (StrStr(message, "(no matching move)st")) {
8251           /* Special kludge for GNU Chess 4 only */
8252           cps->stKludge = TRUE;
8253           SendTimeControl(cps, movesPerSession, timeControl,
8254                           timeIncrement, appData.searchDepth,
8255                           searchTime);
8256           return;
8257         }
8258         if (StrStr(message, "(no matching move)sd")) {
8259           /* Special kludge for GNU Chess 4 only */
8260           cps->sdKludge = TRUE;
8261           SendTimeControl(cps, movesPerSession, timeControl,
8262                           timeIncrement, appData.searchDepth,
8263                           searchTime);
8264           return;
8265         }
8266         if (!StrStr(message, "llegal")) {
8267             return;
8268         }
8269         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8270             gameMode == IcsIdle) return;
8271         if (forwardMostMove <= backwardMostMove) return;
8272         if (pausing) PauseEvent();
8273       if(appData.forceIllegal) {
8274             // [HGM] illegal: machine refused move; force position after move into it
8275           SendToProgram("force\n", cps);
8276           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8277                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8278                 // when black is to move, while there might be nothing on a2 or black
8279                 // might already have the move. So send the board as if white has the move.
8280                 // But first we must change the stm of the engine, as it refused the last move
8281                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8282                 if(WhiteOnMove(forwardMostMove)) {
8283                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8284                     SendBoard(cps, forwardMostMove); // kludgeless board
8285                 } else {
8286                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8287                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8288                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8289                 }
8290           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8291             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8292                  gameMode == TwoMachinesPlay)
8293               SendToProgram("go\n", cps);
8294             return;
8295       } else
8296         if (gameMode == PlayFromGameFile) {
8297             /* Stop reading this game file */
8298             gameMode = EditGame;
8299             ModeHighlight();
8300         }
8301         /* [HGM] illegal-move claim should forfeit game when Xboard */
8302         /* only passes fully legal moves                            */
8303         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8304             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8305                                 "False illegal-move claim", GE_XBOARD );
8306             return; // do not take back move we tested as valid
8307         }
8308         currentMove = forwardMostMove-1;
8309         DisplayMove(currentMove-1); /* before DisplayMoveError */
8310         SwitchClocks(forwardMostMove-1); // [HGM] race
8311         DisplayBothClocks();
8312         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8313                 parseList[currentMove], _(cps->which));
8314         DisplayMoveError(buf1);
8315         DrawPosition(FALSE, boards[currentMove]);
8316         return;
8317     }
8318     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8319         /* Program has a broken "time" command that
8320            outputs a string not ending in newline.
8321            Don't use it. */
8322         cps->sendTime = 0;
8323     }
8324
8325     /*
8326      * If chess program startup fails, exit with an error message.
8327      * Attempts to recover here are futile.
8328      */
8329     if ((StrStr(message, "unknown host") != NULL)
8330         || (StrStr(message, "No remote directory") != NULL)
8331         || (StrStr(message, "not found") != NULL)
8332         || (StrStr(message, "No such file") != NULL)
8333         || (StrStr(message, "can't alloc") != NULL)
8334         || (StrStr(message, "Permission denied") != NULL)) {
8335
8336         cps->maybeThinking = FALSE;
8337         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8338                 _(cps->which), cps->program, cps->host, message);
8339         RemoveInputSource(cps->isr);
8340         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8341             if(cps == &first) appData.noChessProgram = TRUE;
8342             DisplayError(buf1, 0);
8343         }
8344         return;
8345     }
8346
8347     /*
8348      * Look for hint output
8349      */
8350     if (sscanf(message, "Hint: %s", buf1) == 1) {
8351         if (cps == &first && hintRequested) {
8352             hintRequested = FALSE;
8353             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8354                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8355                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8356                                     PosFlags(forwardMostMove),
8357                                     fromY, fromX, toY, toX, promoChar, buf1);
8358                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8359                 DisplayInformation(buf2);
8360             } else {
8361                 /* Hint move could not be parsed!? */
8362               snprintf(buf2, sizeof(buf2),
8363                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8364                         buf1, _(cps->which));
8365                 DisplayError(buf2, 0);
8366             }
8367         } else {
8368           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8369         }
8370         return;
8371     }
8372
8373     /*
8374      * Ignore other messages if game is not in progress
8375      */
8376     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8377         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8378
8379     /*
8380      * look for win, lose, draw, or draw offer
8381      */
8382     if (strncmp(message, "1-0", 3) == 0) {
8383         char *p, *q, *r = "";
8384         p = strchr(message, '{');
8385         if (p) {
8386             q = strchr(p, '}');
8387             if (q) {
8388                 *q = NULLCHAR;
8389                 r = p + 1;
8390             }
8391         }
8392         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8393         return;
8394     } else if (strncmp(message, "0-1", 3) == 0) {
8395         char *p, *q, *r = "";
8396         p = strchr(message, '{');
8397         if (p) {
8398             q = strchr(p, '}');
8399             if (q) {
8400                 *q = NULLCHAR;
8401                 r = p + 1;
8402             }
8403         }
8404         /* Kludge for Arasan 4.1 bug */
8405         if (strcmp(r, "Black resigns") == 0) {
8406             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8407             return;
8408         }
8409         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8410         return;
8411     } else if (strncmp(message, "1/2", 3) == 0) {
8412         char *p, *q, *r = "";
8413         p = strchr(message, '{');
8414         if (p) {
8415             q = strchr(p, '}');
8416             if (q) {
8417                 *q = NULLCHAR;
8418                 r = p + 1;
8419             }
8420         }
8421
8422         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8423         return;
8424
8425     } else if (strncmp(message, "White resign", 12) == 0) {
8426         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8427         return;
8428     } else if (strncmp(message, "Black resign", 12) == 0) {
8429         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8430         return;
8431     } else if (strncmp(message, "White matches", 13) == 0 ||
8432                strncmp(message, "Black matches", 13) == 0   ) {
8433         /* [HGM] ignore GNUShogi noises */
8434         return;
8435     } else if (strncmp(message, "White", 5) == 0 &&
8436                message[5] != '(' &&
8437                StrStr(message, "Black") == NULL) {
8438         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8439         return;
8440     } else if (strncmp(message, "Black", 5) == 0 &&
8441                message[5] != '(') {
8442         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8443         return;
8444     } else if (strcmp(message, "resign") == 0 ||
8445                strcmp(message, "computer resigns") == 0) {
8446         switch (gameMode) {
8447           case MachinePlaysBlack:
8448           case IcsPlayingBlack:
8449             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8450             break;
8451           case MachinePlaysWhite:
8452           case IcsPlayingWhite:
8453             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8454             break;
8455           case TwoMachinesPlay:
8456             if (cps->twoMachinesColor[0] == 'w')
8457               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8458             else
8459               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8460             break;
8461           default:
8462             /* can't happen */
8463             break;
8464         }
8465         return;
8466     } else if (strncmp(message, "opponent mates", 14) == 0) {
8467         switch (gameMode) {
8468           case MachinePlaysBlack:
8469           case IcsPlayingBlack:
8470             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8471             break;
8472           case MachinePlaysWhite:
8473           case IcsPlayingWhite:
8474             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8475             break;
8476           case TwoMachinesPlay:
8477             if (cps->twoMachinesColor[0] == 'w')
8478               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8479             else
8480               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8481             break;
8482           default:
8483             /* can't happen */
8484             break;
8485         }
8486         return;
8487     } else if (strncmp(message, "computer mates", 14) == 0) {
8488         switch (gameMode) {
8489           case MachinePlaysBlack:
8490           case IcsPlayingBlack:
8491             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8492             break;
8493           case MachinePlaysWhite:
8494           case IcsPlayingWhite:
8495             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8496             break;
8497           case TwoMachinesPlay:
8498             if (cps->twoMachinesColor[0] == 'w')
8499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500             else
8501               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8502             break;
8503           default:
8504             /* can't happen */
8505             break;
8506         }
8507         return;
8508     } else if (strncmp(message, "checkmate", 9) == 0) {
8509         if (WhiteOnMove(forwardMostMove)) {
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8511         } else {
8512             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8513         }
8514         return;
8515     } else if (strstr(message, "Draw") != NULL ||
8516                strstr(message, "game is a draw") != NULL) {
8517         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8518         return;
8519     } else if (strstr(message, "offer") != NULL &&
8520                strstr(message, "draw") != NULL) {
8521 #if ZIPPY
8522         if (appData.zippyPlay && first.initDone) {
8523             /* Relay offer to ICS */
8524             SendToICS(ics_prefix);
8525             SendToICS("draw\n");
8526         }
8527 #endif
8528         cps->offeredDraw = 2; /* valid until this engine moves twice */
8529         if (gameMode == TwoMachinesPlay) {
8530             if (cps->other->offeredDraw) {
8531                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8532             /* [HGM] in two-machine mode we delay relaying draw offer      */
8533             /* until after we also have move, to see if it is really claim */
8534             }
8535         } else if (gameMode == MachinePlaysWhite ||
8536                    gameMode == MachinePlaysBlack) {
8537           if (userOfferedDraw) {
8538             DisplayInformation(_("Machine accepts your draw offer"));
8539             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8540           } else {
8541             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8542           }
8543         }
8544     }
8545
8546
8547     /*
8548      * Look for thinking output
8549      */
8550     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8551           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8552                                 ) {
8553         int plylev, mvleft, mvtot, curscore, time;
8554         char mvname[MOVE_LEN];
8555         u64 nodes; // [DM]
8556         char plyext;
8557         int ignore = FALSE;
8558         int prefixHint = FALSE;
8559         mvname[0] = NULLCHAR;
8560
8561         switch (gameMode) {
8562           case MachinePlaysBlack:
8563           case IcsPlayingBlack:
8564             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8565             break;
8566           case MachinePlaysWhite:
8567           case IcsPlayingWhite:
8568             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8569             break;
8570           case AnalyzeMode:
8571           case AnalyzeFile:
8572             break;
8573           case IcsObserving: /* [DM] icsEngineAnalyze */
8574             if (!appData.icsEngineAnalyze) ignore = TRUE;
8575             break;
8576           case TwoMachinesPlay:
8577             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8578                 ignore = TRUE;
8579             }
8580             break;
8581           default:
8582             ignore = TRUE;
8583             break;
8584         }
8585
8586         if (!ignore) {
8587             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8588             buf1[0] = NULLCHAR;
8589             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8590                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8591
8592                 if (plyext != ' ' && plyext != '\t') {
8593                     time *= 100;
8594                 }
8595
8596                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8597                 if( cps->scoreIsAbsolute &&
8598                     ( gameMode == MachinePlaysBlack ||
8599                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8600                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8601                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8602                      !WhiteOnMove(currentMove)
8603                     ) )
8604                 {
8605                     curscore = -curscore;
8606                 }
8607
8608                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8609
8610                 tempStats.depth = plylev;
8611                 tempStats.nodes = nodes;
8612                 tempStats.time = time;
8613                 tempStats.score = curscore;
8614                 tempStats.got_only_move = 0;
8615
8616                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8617                         int ticklen;
8618
8619                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8620                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8621                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8622                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8623                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8624                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8625                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8626                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8627                 }
8628
8629                 /* Buffer overflow protection */
8630                 if (pv[0] != NULLCHAR) {
8631                     if (strlen(pv) >= sizeof(tempStats.movelist)
8632                         && appData.debugMode) {
8633                         fprintf(debugFP,
8634                                 "PV is too long; using the first %u bytes.\n",
8635                                 (unsigned) sizeof(tempStats.movelist) - 1);
8636                     }
8637
8638                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8639                 } else {
8640                     sprintf(tempStats.movelist, " no PV\n");
8641                 }
8642
8643                 if (tempStats.seen_stat) {
8644                     tempStats.ok_to_send = 1;
8645                 }
8646
8647                 if (strchr(tempStats.movelist, '(') != NULL) {
8648                     tempStats.line_is_book = 1;
8649                     tempStats.nr_moves = 0;
8650                     tempStats.moves_left = 0;
8651                 } else {
8652                     tempStats.line_is_book = 0;
8653                 }
8654
8655                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8656                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8657
8658                 SendProgramStatsToFrontend( cps, &tempStats );
8659
8660                 /*
8661                     [AS] Protect the thinkOutput buffer from overflow... this
8662                     is only useful if buf1 hasn't overflowed first!
8663                 */
8664                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8665                          plylev,
8666                          (gameMode == TwoMachinesPlay ?
8667                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8668                          ((double) curscore) / 100.0,
8669                          prefixHint ? lastHint : "",
8670                          prefixHint ? " " : "" );
8671
8672                 if( buf1[0] != NULLCHAR ) {
8673                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8674
8675                     if( strlen(pv) > max_len ) {
8676                         if( appData.debugMode) {
8677                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8678                         }
8679                         pv[max_len+1] = '\0';
8680                     }
8681
8682                     strcat( thinkOutput, pv);
8683                 }
8684
8685                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8686                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8687                     DisplayMove(currentMove - 1);
8688                 }
8689                 return;
8690
8691             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8692                 /* crafty (9.25+) says "(only move) <move>"
8693                  * if there is only 1 legal move
8694                  */
8695                 sscanf(p, "(only move) %s", buf1);
8696                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8697                 sprintf(programStats.movelist, "%s (only move)", buf1);
8698                 programStats.depth = 1;
8699                 programStats.nr_moves = 1;
8700                 programStats.moves_left = 1;
8701                 programStats.nodes = 1;
8702                 programStats.time = 1;
8703                 programStats.got_only_move = 1;
8704
8705                 /* Not really, but we also use this member to
8706                    mean "line isn't going to change" (Crafty
8707                    isn't searching, so stats won't change) */
8708                 programStats.line_is_book = 1;
8709
8710                 SendProgramStatsToFrontend( cps, &programStats );
8711
8712                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8713                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8718                               &time, &nodes, &plylev, &mvleft,
8719                               &mvtot, mvname) >= 5) {
8720                 /* The stat01: line is from Crafty (9.29+) in response
8721                    to the "." command */
8722                 programStats.seen_stat = 1;
8723                 cps->maybeThinking = TRUE;
8724
8725                 if (programStats.got_only_move || !appData.periodicUpdates)
8726                   return;
8727
8728                 programStats.depth = plylev;
8729                 programStats.time = time;
8730                 programStats.nodes = nodes;
8731                 programStats.moves_left = mvleft;
8732                 programStats.nr_moves = mvtot;
8733                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8734                 programStats.ok_to_send = 1;
8735                 programStats.movelist[0] = '\0';
8736
8737                 SendProgramStatsToFrontend( cps, &programStats );
8738
8739                 return;
8740
8741             } else if (strncmp(message,"++",2) == 0) {
8742                 /* Crafty 9.29+ outputs this */
8743                 programStats.got_fail = 2;
8744                 return;
8745
8746             } else if (strncmp(message,"--",2) == 0) {
8747                 /* Crafty 9.29+ outputs this */
8748                 programStats.got_fail = 1;
8749                 return;
8750
8751             } else if (thinkOutput[0] != NULLCHAR &&
8752                        strncmp(message, "    ", 4) == 0) {
8753                 unsigned message_len;
8754
8755                 p = message;
8756                 while (*p && *p == ' ') p++;
8757
8758                 message_len = strlen( p );
8759
8760                 /* [AS] Avoid buffer overflow */
8761                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8762                     strcat(thinkOutput, " ");
8763                     strcat(thinkOutput, p);
8764                 }
8765
8766                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8767                     strcat(programStats.movelist, " ");
8768                     strcat(programStats.movelist, p);
8769                 }
8770
8771                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8772                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8773                     DisplayMove(currentMove - 1);
8774                 }
8775                 return;
8776             }
8777         }
8778         else {
8779             buf1[0] = NULLCHAR;
8780
8781             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8782                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8783             {
8784                 ChessProgramStats cpstats;
8785
8786                 if (plyext != ' ' && plyext != '\t') {
8787                     time *= 100;
8788                 }
8789
8790                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8791                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8792                     curscore = -curscore;
8793                 }
8794
8795                 cpstats.depth = plylev;
8796                 cpstats.nodes = nodes;
8797                 cpstats.time = time;
8798                 cpstats.score = curscore;
8799                 cpstats.got_only_move = 0;
8800                 cpstats.movelist[0] = '\0';
8801
8802                 if (buf1[0] != NULLCHAR) {
8803                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8804                 }
8805
8806                 cpstats.ok_to_send = 0;
8807                 cpstats.line_is_book = 0;
8808                 cpstats.nr_moves = 0;
8809                 cpstats.moves_left = 0;
8810
8811                 SendProgramStatsToFrontend( cps, &cpstats );
8812             }
8813         }
8814     }
8815 }
8816
8817
8818 /* Parse a game score from the character string "game", and
8819    record it as the history of the current game.  The game
8820    score is NOT assumed to start from the standard position.
8821    The display is not updated in any way.
8822    */
8823 void
8824 ParseGameHistory(game)
8825      char *game;
8826 {
8827     ChessMove moveType;
8828     int fromX, fromY, toX, toY, boardIndex;
8829     char promoChar;
8830     char *p, *q;
8831     char buf[MSG_SIZ];
8832
8833     if (appData.debugMode)
8834       fprintf(debugFP, "Parsing game history: %s\n", game);
8835
8836     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8837     gameInfo.site = StrSave(appData.icsHost);
8838     gameInfo.date = PGNDate();
8839     gameInfo.round = StrSave("-");
8840
8841     /* Parse out names of players */
8842     while (*game == ' ') game++;
8843     p = buf;
8844     while (*game != ' ') *p++ = *game++;
8845     *p = NULLCHAR;
8846     gameInfo.white = StrSave(buf);
8847     while (*game == ' ') game++;
8848     p = buf;
8849     while (*game != ' ' && *game != '\n') *p++ = *game++;
8850     *p = NULLCHAR;
8851     gameInfo.black = StrSave(buf);
8852
8853     /* Parse moves */
8854     boardIndex = blackPlaysFirst ? 1 : 0;
8855     yynewstr(game);
8856     for (;;) {
8857         yyboardindex = boardIndex;
8858         moveType = (ChessMove) Myylex();
8859         switch (moveType) {
8860           case IllegalMove:             /* maybe suicide chess, etc. */
8861   if (appData.debugMode) {
8862     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8863     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8864     setbuf(debugFP, NULL);
8865   }
8866           case WhitePromotion:
8867           case BlackPromotion:
8868           case WhiteNonPromotion:
8869           case BlackNonPromotion:
8870           case NormalMove:
8871           case WhiteCapturesEnPassant:
8872           case BlackCapturesEnPassant:
8873           case WhiteKingSideCastle:
8874           case WhiteQueenSideCastle:
8875           case BlackKingSideCastle:
8876           case BlackQueenSideCastle:
8877           case WhiteKingSideCastleWild:
8878           case WhiteQueenSideCastleWild:
8879           case BlackKingSideCastleWild:
8880           case BlackQueenSideCastleWild:
8881           /* PUSH Fabien */
8882           case WhiteHSideCastleFR:
8883           case WhiteASideCastleFR:
8884           case BlackHSideCastleFR:
8885           case BlackASideCastleFR:
8886           /* POP Fabien */
8887             fromX = currentMoveString[0] - AAA;
8888             fromY = currentMoveString[1] - ONE;
8889             toX = currentMoveString[2] - AAA;
8890             toY = currentMoveString[3] - ONE;
8891             promoChar = currentMoveString[4];
8892             break;
8893           case WhiteDrop:
8894           case BlackDrop:
8895             fromX = moveType == WhiteDrop ?
8896               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8897             (int) CharToPiece(ToLower(currentMoveString[0]));
8898             fromY = DROP_RANK;
8899             toX = currentMoveString[2] - AAA;
8900             toY = currentMoveString[3] - ONE;
8901             promoChar = NULLCHAR;
8902             break;
8903           case AmbiguousMove:
8904             /* bug? */
8905             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8906   if (appData.debugMode) {
8907     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8908     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8909     setbuf(debugFP, NULL);
8910   }
8911             DisplayError(buf, 0);
8912             return;
8913           case ImpossibleMove:
8914             /* bug? */
8915             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8916   if (appData.debugMode) {
8917     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8918     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8919     setbuf(debugFP, NULL);
8920   }
8921             DisplayError(buf, 0);
8922             return;
8923           case EndOfFile:
8924             if (boardIndex < backwardMostMove) {
8925                 /* Oops, gap.  How did that happen? */
8926                 DisplayError(_("Gap in move list"), 0);
8927                 return;
8928             }
8929             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8930             if (boardIndex > forwardMostMove) {
8931                 forwardMostMove = boardIndex;
8932             }
8933             return;
8934           case ElapsedTime:
8935             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8936                 strcat(parseList[boardIndex-1], " ");
8937                 strcat(parseList[boardIndex-1], yy_text);
8938             }
8939             continue;
8940           case Comment:
8941           case PGNTag:
8942           case NAG:
8943           default:
8944             /* ignore */
8945             continue;
8946           case WhiteWins:
8947           case BlackWins:
8948           case GameIsDrawn:
8949           case GameUnfinished:
8950             if (gameMode == IcsExamining) {
8951                 if (boardIndex < backwardMostMove) {
8952                     /* Oops, gap.  How did that happen? */
8953                     return;
8954                 }
8955                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8956                 return;
8957             }
8958             gameInfo.result = moveType;
8959             p = strchr(yy_text, '{');
8960             if (p == NULL) p = strchr(yy_text, '(');
8961             if (p == NULL) {
8962                 p = yy_text;
8963                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8964             } else {
8965                 q = strchr(p, *p == '{' ? '}' : ')');
8966                 if (q != NULL) *q = NULLCHAR;
8967                 p++;
8968             }
8969             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8970             gameInfo.resultDetails = StrSave(p);
8971             continue;
8972         }
8973         if (boardIndex >= forwardMostMove &&
8974             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8975             backwardMostMove = blackPlaysFirst ? 1 : 0;
8976             return;
8977         }
8978         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8979                                  fromY, fromX, toY, toX, promoChar,
8980                                  parseList[boardIndex]);
8981         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8982         /* currentMoveString is set as a side-effect of yylex */
8983         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8984         strcat(moveList[boardIndex], "\n");
8985         boardIndex++;
8986         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8987         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8988           case MT_NONE:
8989           case MT_STALEMATE:
8990           default:
8991             break;
8992           case MT_CHECK:
8993             if(gameInfo.variant != VariantShogi)
8994                 strcat(parseList[boardIndex - 1], "+");
8995             break;
8996           case MT_CHECKMATE:
8997           case MT_STAINMATE:
8998             strcat(parseList[boardIndex - 1], "#");
8999             break;
9000         }
9001     }
9002 }
9003
9004
9005 /* Apply a move to the given board  */
9006 void
9007 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9008      int fromX, fromY, toX, toY;
9009      int promoChar;
9010      Board board;
9011 {
9012   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9013   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9014
9015     /* [HGM] compute & store e.p. status and castling rights for new position */
9016     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9017
9018       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9019       oldEP = (signed char)board[EP_STATUS];
9020       board[EP_STATUS] = EP_NONE;
9021
9022   if (fromY == DROP_RANK) {
9023         /* must be first */
9024         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9025             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9026             return;
9027         }
9028         piece = board[toY][toX] = (ChessSquare) fromX;
9029   } else {
9030       int i;
9031
9032       if( board[toY][toX] != EmptySquare )
9033            board[EP_STATUS] = EP_CAPTURE;
9034
9035       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9036            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9037                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9038       } else
9039       if( board[fromY][fromX] == WhitePawn ) {
9040            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9041                board[EP_STATUS] = EP_PAWN_MOVE;
9042            if( toY-fromY==2) {
9043                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9044                         gameInfo.variant != VariantBerolina || toX < fromX)
9045                       board[EP_STATUS] = toX | berolina;
9046                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9047                         gameInfo.variant != VariantBerolina || toX > fromX)
9048                       board[EP_STATUS] = toX;
9049            }
9050       } else
9051       if( board[fromY][fromX] == BlackPawn ) {
9052            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9053                board[EP_STATUS] = EP_PAWN_MOVE;
9054            if( toY-fromY== -2) {
9055                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9056                         gameInfo.variant != VariantBerolina || toX < fromX)
9057                       board[EP_STATUS] = toX | berolina;
9058                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9059                         gameInfo.variant != VariantBerolina || toX > fromX)
9060                       board[EP_STATUS] = toX;
9061            }
9062        }
9063
9064        for(i=0; i<nrCastlingRights; i++) {
9065            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9066               board[CASTLING][i] == toX   && castlingRank[i] == toY
9067              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9068        }
9069
9070      if (fromX == toX && fromY == toY) return;
9071
9072      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9073      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9074      if(gameInfo.variant == VariantKnightmate)
9075          king += (int) WhiteUnicorn - (int) WhiteKing;
9076
9077     /* Code added by Tord: */
9078     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9079     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9080         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9081       board[fromY][fromX] = EmptySquare;
9082       board[toY][toX] = EmptySquare;
9083       if((toX > fromX) != (piece == WhiteRook)) {
9084         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9085       } else {
9086         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9087       }
9088     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9089                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9090       board[fromY][fromX] = EmptySquare;
9091       board[toY][toX] = EmptySquare;
9092       if((toX > fromX) != (piece == BlackRook)) {
9093         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9094       } else {
9095         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9096       }
9097     /* End of code added by Tord */
9098
9099     } else if (board[fromY][fromX] == king
9100         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9101         && toY == fromY && toX > fromX+1) {
9102         board[fromY][fromX] = EmptySquare;
9103         board[toY][toX] = king;
9104         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9105         board[fromY][BOARD_RGHT-1] = EmptySquare;
9106     } else if (board[fromY][fromX] == king
9107         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9108                && toY == fromY && toX < fromX-1) {
9109         board[fromY][fromX] = EmptySquare;
9110         board[toY][toX] = king;
9111         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9112         board[fromY][BOARD_LEFT] = EmptySquare;
9113     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9114                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9115                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9116                ) {
9117         /* white pawn promotion */
9118         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9119         if(gameInfo.variant==VariantBughouse ||
9120            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9121             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9122         board[fromY][fromX] = EmptySquare;
9123     } else if ((fromY >= BOARD_HEIGHT>>1)
9124                && (toX != fromX)
9125                && gameInfo.variant != VariantXiangqi
9126                && gameInfo.variant != VariantBerolina
9127                && (board[fromY][fromX] == WhitePawn)
9128                && (board[toY][toX] == EmptySquare)) {
9129         board[fromY][fromX] = EmptySquare;
9130         board[toY][toX] = WhitePawn;
9131         captured = board[toY - 1][toX];
9132         board[toY - 1][toX] = EmptySquare;
9133     } else if ((fromY == BOARD_HEIGHT-4)
9134                && (toX == fromX)
9135                && gameInfo.variant == VariantBerolina
9136                && (board[fromY][fromX] == WhitePawn)
9137                && (board[toY][toX] == EmptySquare)) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = WhitePawn;
9140         if(oldEP & EP_BEROLIN_A) {
9141                 captured = board[fromY][fromX-1];
9142                 board[fromY][fromX-1] = EmptySquare;
9143         }else{  captured = board[fromY][fromX+1];
9144                 board[fromY][fromX+1] = EmptySquare;
9145         }
9146     } else if (board[fromY][fromX] == king
9147         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9148                && toY == fromY && toX > fromX+1) {
9149         board[fromY][fromX] = EmptySquare;
9150         board[toY][toX] = king;
9151         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9152         board[fromY][BOARD_RGHT-1] = EmptySquare;
9153     } else if (board[fromY][fromX] == king
9154         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9155                && toY == fromY && toX < fromX-1) {
9156         board[fromY][fromX] = EmptySquare;
9157         board[toY][toX] = king;
9158         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9159         board[fromY][BOARD_LEFT] = EmptySquare;
9160     } else if (fromY == 7 && fromX == 3
9161                && board[fromY][fromX] == BlackKing
9162                && toY == 7 && toX == 5) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = BlackKing;
9165         board[fromY][7] = EmptySquare;
9166         board[toY][4] = BlackRook;
9167     } else if (fromY == 7 && fromX == 3
9168                && board[fromY][fromX] == BlackKing
9169                && toY == 7 && toX == 1) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = BlackKing;
9172         board[fromY][0] = EmptySquare;
9173         board[toY][2] = BlackRook;
9174     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9175                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9176                && toY < promoRank && promoChar
9177                ) {
9178         /* black pawn promotion */
9179         board[toY][toX] = CharToPiece(ToLower(promoChar));
9180         if(gameInfo.variant==VariantBughouse ||
9181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9183         board[fromY][fromX] = EmptySquare;
9184     } else if ((fromY < BOARD_HEIGHT>>1)
9185                && (toX != fromX)
9186                && gameInfo.variant != VariantXiangqi
9187                && gameInfo.variant != VariantBerolina
9188                && (board[fromY][fromX] == BlackPawn)
9189                && (board[toY][toX] == EmptySquare)) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = BlackPawn;
9192         captured = board[toY + 1][toX];
9193         board[toY + 1][toX] = EmptySquare;
9194     } else if ((fromY == 3)
9195                && (toX == fromX)
9196                && gameInfo.variant == VariantBerolina
9197                && (board[fromY][fromX] == BlackPawn)
9198                && (board[toY][toX] == EmptySquare)) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackPawn;
9201         if(oldEP & EP_BEROLIN_A) {
9202                 captured = board[fromY][fromX-1];
9203                 board[fromY][fromX-1] = EmptySquare;
9204         }else{  captured = board[fromY][fromX+1];
9205                 board[fromY][fromX+1] = EmptySquare;
9206         }
9207     } else {
9208         board[toY][toX] = board[fromY][fromX];
9209         board[fromY][fromX] = EmptySquare;
9210     }
9211   }
9212
9213     if (gameInfo.holdingsWidth != 0) {
9214
9215       /* !!A lot more code needs to be written to support holdings  */
9216       /* [HGM] OK, so I have written it. Holdings are stored in the */
9217       /* penultimate board files, so they are automaticlly stored   */
9218       /* in the game history.                                       */
9219       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9220                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9221         /* Delete from holdings, by decreasing count */
9222         /* and erasing image if necessary            */
9223         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9224         if(p < (int) BlackPawn) { /* white drop */
9225              p -= (int)WhitePawn;
9226                  p = PieceToNumber((ChessSquare)p);
9227              if(p >= gameInfo.holdingsSize) p = 0;
9228              if(--board[p][BOARD_WIDTH-2] <= 0)
9229                   board[p][BOARD_WIDTH-1] = EmptySquare;
9230              if((int)board[p][BOARD_WIDTH-2] < 0)
9231                         board[p][BOARD_WIDTH-2] = 0;
9232         } else {                  /* black drop */
9233              p -= (int)BlackPawn;
9234                  p = PieceToNumber((ChessSquare)p);
9235              if(p >= gameInfo.holdingsSize) p = 0;
9236              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9237                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9238              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9239                         board[BOARD_HEIGHT-1-p][1] = 0;
9240         }
9241       }
9242       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9243           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9244         /* [HGM] holdings: Add to holdings, if holdings exist */
9245         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9246                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9247                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9248         }
9249         p = (int) captured;
9250         if (p >= (int) BlackPawn) {
9251           p -= (int)BlackPawn;
9252           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9253                   /* in Shogi restore piece to its original  first */
9254                   captured = (ChessSquare) (DEMOTED captured);
9255                   p = DEMOTED p;
9256           }
9257           p = PieceToNumber((ChessSquare)p);
9258           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9259           board[p][BOARD_WIDTH-2]++;
9260           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9261         } else {
9262           p -= (int)WhitePawn;
9263           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9264                   captured = (ChessSquare) (DEMOTED captured);
9265                   p = DEMOTED p;
9266           }
9267           p = PieceToNumber((ChessSquare)p);
9268           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9269           board[BOARD_HEIGHT-1-p][1]++;
9270           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9271         }
9272       }
9273     } else if (gameInfo.variant == VariantAtomic) {
9274       if (captured != EmptySquare) {
9275         int y, x;
9276         for (y = toY-1; y <= toY+1; y++) {
9277           for (x = toX-1; x <= toX+1; x++) {
9278             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9279                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9280               board[y][x] = EmptySquare;
9281             }
9282           }
9283         }
9284         board[toY][toX] = EmptySquare;
9285       }
9286     }
9287     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9288         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9289     } else
9290     if(promoChar == '+') {
9291         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9292         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9293     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9294         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9295     }
9296     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9297                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9298         // [HGM] superchess: take promotion piece out of holdings
9299         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9300         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9301             if(!--board[k][BOARD_WIDTH-2])
9302                 board[k][BOARD_WIDTH-1] = EmptySquare;
9303         } else {
9304             if(!--board[BOARD_HEIGHT-1-k][1])
9305                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9306         }
9307     }
9308
9309 }
9310
9311 /* Updates forwardMostMove */
9312 void
9313 MakeMove(fromX, fromY, toX, toY, promoChar)
9314      int fromX, fromY, toX, toY;
9315      int promoChar;
9316 {
9317 //    forwardMostMove++; // [HGM] bare: moved downstream
9318
9319     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9320         int timeLeft; static int lastLoadFlag=0; int king, piece;
9321         piece = boards[forwardMostMove][fromY][fromX];
9322         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9323         if(gameInfo.variant == VariantKnightmate)
9324             king += (int) WhiteUnicorn - (int) WhiteKing;
9325         if(forwardMostMove == 0) {
9326             if(blackPlaysFirst)
9327                 fprintf(serverMoves, "%s;", second.tidy);
9328             fprintf(serverMoves, "%s;", first.tidy);
9329             if(!blackPlaysFirst)
9330                 fprintf(serverMoves, "%s;", second.tidy);
9331         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9332         lastLoadFlag = loadFlag;
9333         // print base move
9334         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9335         // print castling suffix
9336         if( toY == fromY && piece == king ) {
9337             if(toX-fromX > 1)
9338                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9339             if(fromX-toX >1)
9340                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9341         }
9342         // e.p. suffix
9343         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9344              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9345              boards[forwardMostMove][toY][toX] == EmptySquare
9346              && fromX != toX && fromY != toY)
9347                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9348         // promotion suffix
9349         if(promoChar != NULLCHAR)
9350                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9351         if(!loadFlag) {
9352             fprintf(serverMoves, "/%d/%d",
9353                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9354             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9355             else                      timeLeft = blackTimeRemaining/1000;
9356             fprintf(serverMoves, "/%d", timeLeft);
9357         }
9358         fflush(serverMoves);
9359     }
9360
9361     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9362       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9363                         0, 1);
9364       return;
9365     }
9366     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9367     if (commentList[forwardMostMove+1] != NULL) {
9368         free(commentList[forwardMostMove+1]);
9369         commentList[forwardMostMove+1] = NULL;
9370     }
9371     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9372     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9373     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9374     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9375     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9376     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9377     gameInfo.result = GameUnfinished;
9378     if (gameInfo.resultDetails != NULL) {
9379         free(gameInfo.resultDetails);
9380         gameInfo.resultDetails = NULL;
9381     }
9382     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9383                               moveList[forwardMostMove - 1]);
9384     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9385                              PosFlags(forwardMostMove - 1),
9386                              fromY, fromX, toY, toX, promoChar,
9387                              parseList[forwardMostMove - 1]);
9388     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9389       case MT_NONE:
9390       case MT_STALEMATE:
9391       default:
9392         break;
9393       case MT_CHECK:
9394         if(gameInfo.variant != VariantShogi)
9395             strcat(parseList[forwardMostMove - 1], "+");
9396         break;
9397       case MT_CHECKMATE:
9398       case MT_STAINMATE:
9399         strcat(parseList[forwardMostMove - 1], "#");
9400         break;
9401     }
9402     if (appData.debugMode) {
9403         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9404     }
9405
9406 }
9407
9408 /* Updates currentMove if not pausing */
9409 void
9410 ShowMove(fromX, fromY, toX, toY)
9411 {
9412     int instant = (gameMode == PlayFromGameFile) ?
9413         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9414     if(appData.noGUI) return;
9415     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9416         if (!instant) {
9417             if (forwardMostMove == currentMove + 1) {
9418                 AnimateMove(boards[forwardMostMove - 1],
9419                             fromX, fromY, toX, toY);
9420             }
9421             if (appData.highlightLastMove) {
9422                 SetHighlights(fromX, fromY, toX, toY);
9423             }
9424         }
9425         currentMove = forwardMostMove;
9426     }
9427
9428     if (instant) return;
9429
9430     DisplayMove(currentMove - 1);
9431     DrawPosition(FALSE, boards[currentMove]);
9432     DisplayBothClocks();
9433     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9434     DisplayBook(currentMove);
9435 }
9436
9437 void SendEgtPath(ChessProgramState *cps)
9438 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9439         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9440
9441         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9442
9443         while(*p) {
9444             char c, *q = name+1, *r, *s;
9445
9446             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9447             while(*p && *p != ',') *q++ = *p++;
9448             *q++ = ':'; *q = 0;
9449             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9450                 strcmp(name, ",nalimov:") == 0 ) {
9451                 // take nalimov path from the menu-changeable option first, if it is defined
9452               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9453                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9454             } else
9455             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9456                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9457                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9458                 s = r = StrStr(s, ":") + 1; // beginning of path info
9459                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9460                 c = *r; *r = 0;             // temporarily null-terminate path info
9461                     *--q = 0;               // strip of trailig ':' from name
9462                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9463                 *r = c;
9464                 SendToProgram(buf,cps);     // send egtbpath command for this format
9465             }
9466             if(*p == ',') p++; // read away comma to position for next format name
9467         }
9468 }
9469
9470 void
9471 InitChessProgram(cps, setup)
9472      ChessProgramState *cps;
9473      int setup; /* [HGM] needed to setup FRC opening position */
9474 {
9475     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9476     if (appData.noChessProgram) return;
9477     hintRequested = FALSE;
9478     bookRequested = FALSE;
9479
9480     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9481     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9482     if(cps->memSize) { /* [HGM] memory */
9483       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9484         SendToProgram(buf, cps);
9485     }
9486     SendEgtPath(cps); /* [HGM] EGT */
9487     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9488       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9489         SendToProgram(buf, cps);
9490     }
9491
9492     SendToProgram(cps->initString, cps);
9493     if (gameInfo.variant != VariantNormal &&
9494         gameInfo.variant != VariantLoadable
9495         /* [HGM] also send variant if board size non-standard */
9496         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9497                                             ) {
9498       char *v = VariantName(gameInfo.variant);
9499       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9500         /* [HGM] in protocol 1 we have to assume all variants valid */
9501         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9502         DisplayFatalError(buf, 0, 1);
9503         return;
9504       }
9505
9506       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9507       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9508       if( gameInfo.variant == VariantXiangqi )
9509            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9510       if( gameInfo.variant == VariantShogi )
9511            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9512       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9513            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9514       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9515           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9516            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9517       if( gameInfo.variant == VariantCourier )
9518            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9519       if( gameInfo.variant == VariantSuper )
9520            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9521       if( gameInfo.variant == VariantGreat )
9522            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9523       if( gameInfo.variant == VariantSChess )
9524            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9525       if( gameInfo.variant == VariantGrand )
9526            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9527
9528       if(overruled) {
9529         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9530                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9531            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9532            if(StrStr(cps->variants, b) == NULL) {
9533                // specific sized variant not known, check if general sizing allowed
9534                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9535                    if(StrStr(cps->variants, "boardsize") == NULL) {
9536                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9537                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9538                        DisplayFatalError(buf, 0, 1);
9539                        return;
9540                    }
9541                    /* [HGM] here we really should compare with the maximum supported board size */
9542                }
9543            }
9544       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9545       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9546       SendToProgram(buf, cps);
9547     }
9548     currentlyInitializedVariant = gameInfo.variant;
9549
9550     /* [HGM] send opening position in FRC to first engine */
9551     if(setup) {
9552           SendToProgram("force\n", cps);
9553           SendBoard(cps, 0);
9554           /* engine is now in force mode! Set flag to wake it up after first move. */
9555           setboardSpoiledMachineBlack = 1;
9556     }
9557
9558     if (cps->sendICS) {
9559       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9560       SendToProgram(buf, cps);
9561     }
9562     cps->maybeThinking = FALSE;
9563     cps->offeredDraw = 0;
9564     if (!appData.icsActive) {
9565         SendTimeControl(cps, movesPerSession, timeControl,
9566                         timeIncrement, appData.searchDepth,
9567                         searchTime);
9568     }
9569     if (appData.showThinking
9570         // [HGM] thinking: four options require thinking output to be sent
9571         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9572                                 ) {
9573         SendToProgram("post\n", cps);
9574     }
9575     SendToProgram("hard\n", cps);
9576     if (!appData.ponderNextMove) {
9577         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9578            it without being sure what state we are in first.  "hard"
9579            is not a toggle, so that one is OK.
9580          */
9581         SendToProgram("easy\n", cps);
9582     }
9583     if (cps->usePing) {
9584       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9585       SendToProgram(buf, cps);
9586     }
9587     cps->initDone = TRUE;
9588     ClearEngineOutputPane(cps == &second);
9589 }
9590
9591
9592 void
9593 StartChessProgram(cps)
9594      ChessProgramState *cps;
9595 {
9596     char buf[MSG_SIZ];
9597     int err;
9598
9599     if (appData.noChessProgram) return;
9600     cps->initDone = FALSE;
9601
9602     if (strcmp(cps->host, "localhost") == 0) {
9603         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9604     } else if (*appData.remoteShell == NULLCHAR) {
9605         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9606     } else {
9607         if (*appData.remoteUser == NULLCHAR) {
9608           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9609                     cps->program);
9610         } else {
9611           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9612                     cps->host, appData.remoteUser, cps->program);
9613         }
9614         err = StartChildProcess(buf, "", &cps->pr);
9615     }
9616
9617     if (err != 0) {
9618       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9619         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9620         if(cps != &first) return;
9621         appData.noChessProgram = TRUE;
9622         ThawUI();
9623         SetNCPMode();
9624 //      DisplayFatalError(buf, err, 1);
9625 //      cps->pr = NoProc;
9626 //      cps->isr = NULL;
9627         return;
9628     }
9629
9630     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9631     if (cps->protocolVersion > 1) {
9632       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9633       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9634       cps->comboCnt = 0;  //                and values of combo boxes
9635       SendToProgram(buf, cps);
9636     } else {
9637       SendToProgram("xboard\n", cps);
9638     }
9639 }
9640
9641 void
9642 TwoMachinesEventIfReady P((void))
9643 {
9644   static int curMess = 0;
9645   if (first.lastPing != first.lastPong) {
9646     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9647     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9648     return;
9649   }
9650   if (second.lastPing != second.lastPong) {
9651     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9652     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9653     return;
9654   }
9655   DisplayMessage("", ""); curMess = 0;
9656   ThawUI();
9657   TwoMachinesEvent();
9658 }
9659
9660 char *
9661 MakeName(char *template)
9662 {
9663     time_t clock;
9664     struct tm *tm;
9665     static char buf[MSG_SIZ];
9666     char *p = buf;
9667     int i;
9668
9669     clock = time((time_t *)NULL);
9670     tm = localtime(&clock);
9671
9672     while(*p++ = *template++) if(p[-1] == '%') {
9673         switch(*template++) {
9674           case 0:   *p = 0; return buf;
9675           case 'Y': i = tm->tm_year+1900; break;
9676           case 'y': i = tm->tm_year-100; break;
9677           case 'M': i = tm->tm_mon+1; break;
9678           case 'd': i = tm->tm_mday; break;
9679           case 'h': i = tm->tm_hour; break;
9680           case 'm': i = tm->tm_min; break;
9681           case 's': i = tm->tm_sec; break;
9682           default:  i = 0;
9683         }
9684         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9685     }
9686     return buf;
9687 }
9688
9689 int
9690 CountPlayers(char *p)
9691 {
9692     int n = 0;
9693     while(p = strchr(p, '\n')) p++, n++; // count participants
9694     return n;
9695 }
9696
9697 FILE *
9698 WriteTourneyFile(char *results)
9699 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9700     FILE *f = fopen(appData.tourneyFile, "w");
9701     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9702         // create a file with tournament description
9703         fprintf(f, "-participants {%s}\n", appData.participants);
9704         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9705         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9706         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9707         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9708         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9709         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9710         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9711         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9712         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9713         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9714         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9715         if(searchTime > 0)
9716                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9717         else {
9718                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9719                 fprintf(f, "-tc %s\n", appData.timeControl);
9720                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9721         }
9722         fprintf(f, "-results \"%s\"\n", results);
9723     }
9724     return f;
9725 }
9726
9727 int
9728 CreateTourney(char *name)
9729 {
9730         FILE *f;
9731         if(name[0] == NULLCHAR) {
9732             if(appData.participants[0])
9733                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9734             return 0;
9735         }
9736         f = fopen(name, "r");
9737         if(f) { // file exists
9738             ASSIGN(appData.tourneyFile, name);
9739             ParseArgsFromFile(f); // parse it
9740         } else {
9741             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9742             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9743                 DisplayError(_("Not enough participants"), 0);
9744                 return 0;
9745             }
9746             ASSIGN(appData.tourneyFile, name);
9747             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9748             if((f = WriteTourneyFile("")) == NULL) return 0;
9749         }
9750         fclose(f);
9751         appData.noChessProgram = FALSE;
9752         appData.clockMode = TRUE;
9753         SetGNUMode();
9754         return 1;
9755 }
9756
9757 #define MAXENGINES 1000
9758 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9759
9760 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9761 {
9762     char buf[MSG_SIZ], *p, *q;
9763     int i=1;
9764     while(*names) {
9765         p = names; q = buf;
9766         while(*p && *p != '\n') *q++ = *p++;
9767         *q = 0;
9768         if(engineList[i]) free(engineList[i]);
9769         engineList[i] = strdup(buf);
9770         if(*p == '\n') p++;
9771         TidyProgramName(engineList[i], "localhost", buf);
9772         if(engineMnemonic[i]) free(engineMnemonic[i]);
9773         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9774             strcat(buf, " (");
9775             sscanf(q + 8, "%s", buf + strlen(buf));
9776             strcat(buf, ")");
9777         }
9778         engineMnemonic[i] = strdup(buf);
9779         names = p; i++;
9780       if(i > MAXENGINES - 2) break;
9781     }
9782     engineList[i] = NULL;
9783 }
9784
9785 // following implemented as macro to avoid type limitations
9786 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9787
9788 void SwapEngines(int n)
9789 {   // swap settings for first engine and other engine (so far only some selected options)
9790     int h;
9791     char *p;
9792     if(n == 0) return;
9793     SWAP(directory, p)
9794     SWAP(chessProgram, p)
9795     SWAP(isUCI, h)
9796     SWAP(hasOwnBookUCI, h)
9797     SWAP(protocolVersion, h)
9798     SWAP(reuse, h)
9799     SWAP(scoreIsAbsolute, h)
9800     SWAP(timeOdds, h)
9801     SWAP(logo, p)
9802     SWAP(pgnName, p)
9803     SWAP(pvSAN, h)
9804 }
9805
9806 void
9807 SetPlayer(int player)
9808 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9809     int i;
9810     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9811     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9812     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9813     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9814     if(mnemonic[i]) {
9815         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9816         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9817         ParseArgsFromString(buf);
9818     }
9819     free(engineName);
9820 }
9821
9822 int
9823 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9824 {   // determine players from game number
9825     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9826
9827     if(appData.tourneyType == 0) {
9828         roundsPerCycle = (nPlayers - 1) | 1;
9829         pairingsPerRound = nPlayers / 2;
9830     } else if(appData.tourneyType > 0) {
9831         roundsPerCycle = nPlayers - appData.tourneyType;
9832         pairingsPerRound = appData.tourneyType;
9833     }
9834     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9835     gamesPerCycle = gamesPerRound * roundsPerCycle;
9836     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9837     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9838     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9839     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9840     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9841     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9842
9843     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9844     if(appData.roundSync) *syncInterval = gamesPerRound;
9845
9846     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9847
9848     if(appData.tourneyType == 0) {
9849         if(curPairing == (nPlayers-1)/2 ) {
9850             *whitePlayer = curRound;
9851             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9852         } else {
9853             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9854             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9855             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9856             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9857         }
9858     } else if(appData.tourneyType > 0) {
9859         *whitePlayer = curPairing;
9860         *blackPlayer = curRound + appData.tourneyType;
9861     }
9862
9863     // take care of white/black alternation per round. 
9864     // For cycles and games this is already taken care of by default, derived from matchGame!
9865     return curRound & 1;
9866 }
9867
9868 int
9869 NextTourneyGame(int nr, int *swapColors)
9870 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9871     char *p, *q;
9872     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9873     FILE *tf;
9874     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9875     tf = fopen(appData.tourneyFile, "r");
9876     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9877     ParseArgsFromFile(tf); fclose(tf);
9878     InitTimeControls(); // TC might be altered from tourney file
9879
9880     nPlayers = CountPlayers(appData.participants); // count participants
9881     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9882     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9883
9884     if(syncInterval) {
9885         p = q = appData.results;
9886         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9887         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9888             DisplayMessage(_("Waiting for other game(s)"),"");
9889             waitingForGame = TRUE;
9890             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9891             return 0;
9892         }
9893         waitingForGame = FALSE;
9894     }
9895
9896     if(appData.tourneyType < 0) {
9897         if(nr>=0 && !pairingReceived) {
9898             char buf[1<<16];
9899             if(pairing.pr == NoProc) {
9900                 if(!appData.pairingEngine[0]) {
9901                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9902                     return 0;
9903                 }
9904                 StartChessProgram(&pairing); // starts the pairing engine
9905             }
9906             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9907             SendToProgram(buf, &pairing);
9908             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9909             SendToProgram(buf, &pairing);
9910             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9911         }
9912         pairingReceived = 0;                              // ... so we continue here 
9913         *swapColors = 0;
9914         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9915         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9916         matchGame = 1; roundNr = nr / syncInterval + 1;
9917     }
9918
9919     if(first.pr != NoProc) return 1; // engines already loaded
9920
9921     // redefine engines, engine dir, etc.
9922     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9923     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9924     SwapEngines(1);
9925     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9926     SwapEngines(1);         // and make that valid for second engine by swapping
9927     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9928     InitEngine(&second, 1);
9929     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9930     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9931     return 1;
9932 }
9933
9934 void
9935 NextMatchGame()
9936 {   // performs game initialization that does not invoke engines, and then tries to start the game
9937     int firstWhite, swapColors = 0;
9938     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9939     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9940     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9941     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9942     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9943     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9944     Reset(FALSE, first.pr != NoProc);
9945     appData.noChessProgram = FALSE;
9946     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9947     TwoMachinesEvent();
9948 }
9949
9950 void UserAdjudicationEvent( int result )
9951 {
9952     ChessMove gameResult = GameIsDrawn;
9953
9954     if( result > 0 ) {
9955         gameResult = WhiteWins;
9956     }
9957     else if( result < 0 ) {
9958         gameResult = BlackWins;
9959     }
9960
9961     if( gameMode == TwoMachinesPlay ) {
9962         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9963     }
9964 }
9965
9966
9967 // [HGM] save: calculate checksum of game to make games easily identifiable
9968 int StringCheckSum(char *s)
9969 {
9970         int i = 0;
9971         if(s==NULL) return 0;
9972         while(*s) i = i*259 + *s++;
9973         return i;
9974 }
9975
9976 int GameCheckSum()
9977 {
9978         int i, sum=0;
9979         for(i=backwardMostMove; i<forwardMostMove; i++) {
9980                 sum += pvInfoList[i].depth;
9981                 sum += StringCheckSum(parseList[i]);
9982                 sum += StringCheckSum(commentList[i]);
9983                 sum *= 261;
9984         }
9985         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9986         return sum + StringCheckSum(commentList[i]);
9987 } // end of save patch
9988
9989 void
9990 GameEnds(result, resultDetails, whosays)
9991      ChessMove result;
9992      char *resultDetails;
9993      int whosays;
9994 {
9995     GameMode nextGameMode;
9996     int isIcsGame;
9997     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9998
9999     if(endingGame) return; /* [HGM] crash: forbid recursion */
10000     endingGame = 1;
10001     if(twoBoards) { // [HGM] dual: switch back to one board
10002         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10003         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10004     }
10005     if (appData.debugMode) {
10006       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10007               result, resultDetails ? resultDetails : "(null)", whosays);
10008     }
10009
10010     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10011
10012     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10013         /* If we are playing on ICS, the server decides when the
10014            game is over, but the engine can offer to draw, claim
10015            a draw, or resign.
10016          */
10017 #if ZIPPY
10018         if (appData.zippyPlay && first.initDone) {
10019             if (result == GameIsDrawn) {
10020                 /* In case draw still needs to be claimed */
10021                 SendToICS(ics_prefix);
10022                 SendToICS("draw\n");
10023             } else if (StrCaseStr(resultDetails, "resign")) {
10024                 SendToICS(ics_prefix);
10025                 SendToICS("resign\n");
10026             }
10027         }
10028 #endif
10029         endingGame = 0; /* [HGM] crash */
10030         return;
10031     }
10032
10033     /* If we're loading the game from a file, stop */
10034     if (whosays == GE_FILE) {
10035       (void) StopLoadGameTimer();
10036       gameFileFP = NULL;
10037     }
10038
10039     /* Cancel draw offers */
10040     first.offeredDraw = second.offeredDraw = 0;
10041
10042     /* If this is an ICS game, only ICS can really say it's done;
10043        if not, anyone can. */
10044     isIcsGame = (gameMode == IcsPlayingWhite ||
10045                  gameMode == IcsPlayingBlack ||
10046                  gameMode == IcsObserving    ||
10047                  gameMode == IcsExamining);
10048
10049     if (!isIcsGame || whosays == GE_ICS) {
10050         /* OK -- not an ICS game, or ICS said it was done */
10051         StopClocks();
10052         if (!isIcsGame && !appData.noChessProgram)
10053           SetUserThinkingEnables();
10054
10055         /* [HGM] if a machine claims the game end we verify this claim */
10056         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10057             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10058                 char claimer;
10059                 ChessMove trueResult = (ChessMove) -1;
10060
10061                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10062                                             first.twoMachinesColor[0] :
10063                                             second.twoMachinesColor[0] ;
10064
10065                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10066                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10067                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10068                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10069                 } else
10070                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10071                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10072                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10073                 } else
10074                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10075                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10076                 }
10077
10078                 // now verify win claims, but not in drop games, as we don't understand those yet
10079                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10080                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10081                     (result == WhiteWins && claimer == 'w' ||
10082                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10083                       if (appData.debugMode) {
10084                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10085                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10086                       }
10087                       if(result != trueResult) {
10088                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10089                               result = claimer == 'w' ? BlackWins : WhiteWins;
10090                               resultDetails = buf;
10091                       }
10092                 } else
10093                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10094                     && (forwardMostMove <= backwardMostMove ||
10095                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10096                         (claimer=='b')==(forwardMostMove&1))
10097                                                                                   ) {
10098                       /* [HGM] verify: draws that were not flagged are false claims */
10099                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10100                       result = claimer == 'w' ? BlackWins : WhiteWins;
10101                       resultDetails = buf;
10102                 }
10103                 /* (Claiming a loss is accepted no questions asked!) */
10104             }
10105             /* [HGM] bare: don't allow bare King to win */
10106             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10107                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10108                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10109                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10110                && result != GameIsDrawn)
10111             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10112                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10113                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10114                         if(p >= 0 && p <= (int)WhiteKing) k++;
10115                 }
10116                 if (appData.debugMode) {
10117                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10118                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10119                 }
10120                 if(k <= 1) {
10121                         result = GameIsDrawn;
10122                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10123                         resultDetails = buf;
10124                 }
10125             }
10126         }
10127
10128
10129         if(serverMoves != NULL && !loadFlag) { char c = '=';
10130             if(result==WhiteWins) c = '+';
10131             if(result==BlackWins) c = '-';
10132             if(resultDetails != NULL)
10133                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10134         }
10135         if (resultDetails != NULL) {
10136             gameInfo.result = result;
10137             gameInfo.resultDetails = StrSave(resultDetails);
10138
10139             /* display last move only if game was not loaded from file */
10140             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10141                 DisplayMove(currentMove - 1);
10142
10143             if (forwardMostMove != 0) {
10144                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10145                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10146                                                                 ) {
10147                     if (*appData.saveGameFile != NULLCHAR) {
10148                         SaveGameToFile(appData.saveGameFile, TRUE);
10149                     } else if (appData.autoSaveGames) {
10150                         AutoSaveGame();
10151                     }
10152                     if (*appData.savePositionFile != NULLCHAR) {
10153                         SavePositionToFile(appData.savePositionFile);
10154                     }
10155                 }
10156             }
10157
10158             /* Tell program how game ended in case it is learning */
10159             /* [HGM] Moved this to after saving the PGN, just in case */
10160             /* engine died and we got here through time loss. In that */
10161             /* case we will get a fatal error writing the pipe, which */
10162             /* would otherwise lose us the PGN.                       */
10163             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10164             /* output during GameEnds should never be fatal anymore   */
10165             if (gameMode == MachinePlaysWhite ||
10166                 gameMode == MachinePlaysBlack ||
10167                 gameMode == TwoMachinesPlay ||
10168                 gameMode == IcsPlayingWhite ||
10169                 gameMode == IcsPlayingBlack ||
10170                 gameMode == BeginningOfGame) {
10171                 char buf[MSG_SIZ];
10172                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10173                         resultDetails);
10174                 if (first.pr != NoProc) {
10175                     SendToProgram(buf, &first);
10176                 }
10177                 if (second.pr != NoProc &&
10178                     gameMode == TwoMachinesPlay) {
10179                     SendToProgram(buf, &second);
10180                 }
10181             }
10182         }
10183
10184         if (appData.icsActive) {
10185             if (appData.quietPlay &&
10186                 (gameMode == IcsPlayingWhite ||
10187                  gameMode == IcsPlayingBlack)) {
10188                 SendToICS(ics_prefix);
10189                 SendToICS("set shout 1\n");
10190             }
10191             nextGameMode = IcsIdle;
10192             ics_user_moved = FALSE;
10193             /* clean up premove.  It's ugly when the game has ended and the
10194              * premove highlights are still on the board.
10195              */
10196             if (gotPremove) {
10197               gotPremove = FALSE;
10198               ClearPremoveHighlights();
10199               DrawPosition(FALSE, boards[currentMove]);
10200             }
10201             if (whosays == GE_ICS) {
10202                 switch (result) {
10203                 case WhiteWins:
10204                     if (gameMode == IcsPlayingWhite)
10205                         PlayIcsWinSound();
10206                     else if(gameMode == IcsPlayingBlack)
10207                         PlayIcsLossSound();
10208                     break;
10209                 case BlackWins:
10210                     if (gameMode == IcsPlayingBlack)
10211                         PlayIcsWinSound();
10212                     else if(gameMode == IcsPlayingWhite)
10213                         PlayIcsLossSound();
10214                     break;
10215                 case GameIsDrawn:
10216                     PlayIcsDrawSound();
10217                     break;
10218                 default:
10219                     PlayIcsUnfinishedSound();
10220                 }
10221             }
10222         } else if (gameMode == EditGame ||
10223                    gameMode == PlayFromGameFile ||
10224                    gameMode == AnalyzeMode ||
10225                    gameMode == AnalyzeFile) {
10226             nextGameMode = gameMode;
10227         } else {
10228             nextGameMode = EndOfGame;
10229         }
10230         pausing = FALSE;
10231         ModeHighlight();
10232     } else {
10233         nextGameMode = gameMode;
10234     }
10235
10236     if (appData.noChessProgram) {
10237         gameMode = nextGameMode;
10238         ModeHighlight();
10239         endingGame = 0; /* [HGM] crash */
10240         return;
10241     }
10242
10243     if (first.reuse) {
10244         /* Put first chess program into idle state */
10245         if (first.pr != NoProc &&
10246             (gameMode == MachinePlaysWhite ||
10247              gameMode == MachinePlaysBlack ||
10248              gameMode == TwoMachinesPlay ||
10249              gameMode == IcsPlayingWhite ||
10250              gameMode == IcsPlayingBlack ||
10251              gameMode == BeginningOfGame)) {
10252             SendToProgram("force\n", &first);
10253             if (first.usePing) {
10254               char buf[MSG_SIZ];
10255               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10256               SendToProgram(buf, &first);
10257             }
10258         }
10259     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10260         /* Kill off first chess program */
10261         if (first.isr != NULL)
10262           RemoveInputSource(first.isr);
10263         first.isr = NULL;
10264
10265         if (first.pr != NoProc) {
10266             ExitAnalyzeMode();
10267             DoSleep( appData.delayBeforeQuit );
10268             SendToProgram("quit\n", &first);
10269             DoSleep( appData.delayAfterQuit );
10270             DestroyChildProcess(first.pr, first.useSigterm);
10271         }
10272         first.pr = NoProc;
10273     }
10274     if (second.reuse) {
10275         /* Put second chess program into idle state */
10276         if (second.pr != NoProc &&
10277             gameMode == TwoMachinesPlay) {
10278             SendToProgram("force\n", &second);
10279             if (second.usePing) {
10280               char buf[MSG_SIZ];
10281               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10282               SendToProgram(buf, &second);
10283             }
10284         }
10285     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10286         /* Kill off second chess program */
10287         if (second.isr != NULL)
10288           RemoveInputSource(second.isr);
10289         second.isr = NULL;
10290
10291         if (second.pr != NoProc) {
10292             DoSleep( appData.delayBeforeQuit );
10293             SendToProgram("quit\n", &second);
10294             DoSleep( appData.delayAfterQuit );
10295             DestroyChildProcess(second.pr, second.useSigterm);
10296         }
10297         second.pr = NoProc;
10298     }
10299
10300     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10301         char resChar = '=';
10302         switch (result) {
10303         case WhiteWins:
10304           resChar = '+';
10305           if (first.twoMachinesColor[0] == 'w') {
10306             first.matchWins++;
10307           } else {
10308             second.matchWins++;
10309           }
10310           break;
10311         case BlackWins:
10312           resChar = '-';
10313           if (first.twoMachinesColor[0] == 'b') {
10314             first.matchWins++;
10315           } else {
10316             second.matchWins++;
10317           }
10318           break;
10319         case GameUnfinished:
10320           resChar = ' ';
10321         default:
10322           break;
10323         }
10324
10325         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10326         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10327             ReserveGame(nextGame, resChar); // sets nextGame
10328             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10329             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10330         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10331
10332         if (nextGame <= appData.matchGames && !abortMatch) {
10333             gameMode = nextGameMode;
10334             matchGame = nextGame; // this will be overruled in tourney mode!
10335             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10336             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10337             endingGame = 0; /* [HGM] crash */
10338             return;
10339         } else {
10340             gameMode = nextGameMode;
10341             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10342                      first.tidy, second.tidy,
10343                      first.matchWins, second.matchWins,
10344                      appData.matchGames - (first.matchWins + second.matchWins));
10345             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10346             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10347             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10348                 first.twoMachinesColor = "black\n";
10349                 second.twoMachinesColor = "white\n";
10350             } else {
10351                 first.twoMachinesColor = "white\n";
10352                 second.twoMachinesColor = "black\n";
10353             }
10354         }
10355     }
10356     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10357         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10358       ExitAnalyzeMode();
10359     gameMode = nextGameMode;
10360     ModeHighlight();
10361     endingGame = 0;  /* [HGM] crash */
10362     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10363         if(matchMode == TRUE) { // match through command line: exit with or without popup
10364             if(ranking) {
10365                 ToNrEvent(forwardMostMove);
10366                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10367                 else ExitEvent(0);
10368             } else DisplayFatalError(buf, 0, 0);
10369         } else { // match through menu; just stop, with or without popup
10370             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10371             ModeHighlight();
10372             if(ranking){
10373                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10374             } else DisplayNote(buf);
10375       }
10376       if(ranking) free(ranking);
10377     }
10378 }
10379
10380 /* Assumes program was just initialized (initString sent).
10381    Leaves program in force mode. */
10382 void
10383 FeedMovesToProgram(cps, upto)
10384      ChessProgramState *cps;
10385      int upto;
10386 {
10387     int i;
10388
10389     if (appData.debugMode)
10390       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10391               startedFromSetupPosition ? "position and " : "",
10392               backwardMostMove, upto, cps->which);
10393     if(currentlyInitializedVariant != gameInfo.variant) {
10394       char buf[MSG_SIZ];
10395         // [HGM] variantswitch: make engine aware of new variant
10396         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10397                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10398         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10399         SendToProgram(buf, cps);
10400         currentlyInitializedVariant = gameInfo.variant;
10401     }
10402     SendToProgram("force\n", cps);
10403     if (startedFromSetupPosition) {
10404         SendBoard(cps, backwardMostMove);
10405     if (appData.debugMode) {
10406         fprintf(debugFP, "feedMoves\n");
10407     }
10408     }
10409     for (i = backwardMostMove; i < upto; i++) {
10410         SendMoveToProgram(i, cps);
10411     }
10412 }
10413
10414
10415 int
10416 ResurrectChessProgram()
10417 {
10418      /* The chess program may have exited.
10419         If so, restart it and feed it all the moves made so far. */
10420     static int doInit = 0;
10421
10422     if (appData.noChessProgram) return 1;
10423
10424     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10425         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10426         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10427         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10428     } else {
10429         if (first.pr != NoProc) return 1;
10430         StartChessProgram(&first);
10431     }
10432     InitChessProgram(&first, FALSE);
10433     FeedMovesToProgram(&first, currentMove);
10434
10435     if (!first.sendTime) {
10436         /* can't tell gnuchess what its clock should read,
10437            so we bow to its notion. */
10438         ResetClocks();
10439         timeRemaining[0][currentMove] = whiteTimeRemaining;
10440         timeRemaining[1][currentMove] = blackTimeRemaining;
10441     }
10442
10443     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10444                 appData.icsEngineAnalyze) && first.analysisSupport) {
10445       SendToProgram("analyze\n", &first);
10446       first.analyzing = TRUE;
10447     }
10448     return 1;
10449 }
10450
10451 /*
10452  * Button procedures
10453  */
10454 void
10455 Reset(redraw, init)
10456      int redraw, init;
10457 {
10458     int i;
10459
10460     if (appData.debugMode) {
10461         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10462                 redraw, init, gameMode);
10463     }
10464     CleanupTail(); // [HGM] vari: delete any stored variations
10465     pausing = pauseExamInvalid = FALSE;
10466     startedFromSetupPosition = blackPlaysFirst = FALSE;
10467     firstMove = TRUE;
10468     whiteFlag = blackFlag = FALSE;
10469     userOfferedDraw = FALSE;
10470     hintRequested = bookRequested = FALSE;
10471     first.maybeThinking = FALSE;
10472     second.maybeThinking = FALSE;
10473     first.bookSuspend = FALSE; // [HGM] book
10474     second.bookSuspend = FALSE;
10475     thinkOutput[0] = NULLCHAR;
10476     lastHint[0] = NULLCHAR;
10477     ClearGameInfo(&gameInfo);
10478     gameInfo.variant = StringToVariant(appData.variant);
10479     ics_user_moved = ics_clock_paused = FALSE;
10480     ics_getting_history = H_FALSE;
10481     ics_gamenum = -1;
10482     white_holding[0] = black_holding[0] = NULLCHAR;
10483     ClearProgramStats();
10484     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10485
10486     ResetFrontEnd();
10487     ClearHighlights();
10488     flipView = appData.flipView;
10489     ClearPremoveHighlights();
10490     gotPremove = FALSE;
10491     alarmSounded = FALSE;
10492
10493     GameEnds(EndOfFile, NULL, GE_PLAYER);
10494     if(appData.serverMovesName != NULL) {
10495         /* [HGM] prepare to make moves file for broadcasting */
10496         clock_t t = clock();
10497         if(serverMoves != NULL) fclose(serverMoves);
10498         serverMoves = fopen(appData.serverMovesName, "r");
10499         if(serverMoves != NULL) {
10500             fclose(serverMoves);
10501             /* delay 15 sec before overwriting, so all clients can see end */
10502             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10503         }
10504         serverMoves = fopen(appData.serverMovesName, "w");
10505     }
10506
10507     ExitAnalyzeMode();
10508     gameMode = BeginningOfGame;
10509     ModeHighlight();
10510     if(appData.icsActive) gameInfo.variant = VariantNormal;
10511     currentMove = forwardMostMove = backwardMostMove = 0;
10512     InitPosition(redraw);
10513     for (i = 0; i < MAX_MOVES; i++) {
10514         if (commentList[i] != NULL) {
10515             free(commentList[i]);
10516             commentList[i] = NULL;
10517         }
10518     }
10519     ResetClocks();
10520     timeRemaining[0][0] = whiteTimeRemaining;
10521     timeRemaining[1][0] = blackTimeRemaining;
10522
10523     if (first.pr == NULL) {
10524         StartChessProgram(&first);
10525     }
10526     if (init) {
10527             InitChessProgram(&first, startedFromSetupPosition);
10528     }
10529     DisplayTitle("");
10530     DisplayMessage("", "");
10531     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10532     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10533 }
10534
10535 void
10536 AutoPlayGameLoop()
10537 {
10538     for (;;) {
10539         if (!AutoPlayOneMove())
10540           return;
10541         if (matchMode || appData.timeDelay == 0)
10542           continue;
10543         if (appData.timeDelay < 0)
10544           return;
10545         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10546         break;
10547     }
10548 }
10549
10550
10551 int
10552 AutoPlayOneMove()
10553 {
10554     int fromX, fromY, toX, toY;
10555
10556     if (appData.debugMode) {
10557       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10558     }
10559
10560     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10561       return FALSE;
10562
10563     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10564       pvInfoList[currentMove].depth = programStats.depth;
10565       pvInfoList[currentMove].score = programStats.score;
10566       pvInfoList[currentMove].time  = 0;
10567       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10568     }
10569
10570     if (currentMove >= forwardMostMove) {
10571       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10572       gameMode = EditGame;
10573       ModeHighlight();
10574
10575       /* [AS] Clear current move marker at the end of a game */
10576       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10577
10578       return FALSE;
10579     }
10580
10581     toX = moveList[currentMove][2] - AAA;
10582     toY = moveList[currentMove][3] - ONE;
10583
10584     if (moveList[currentMove][1] == '@') {
10585         if (appData.highlightLastMove) {
10586             SetHighlights(-1, -1, toX, toY);
10587         }
10588     } else {
10589         fromX = moveList[currentMove][0] - AAA;
10590         fromY = moveList[currentMove][1] - ONE;
10591
10592         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10593
10594         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10595
10596         if (appData.highlightLastMove) {
10597             SetHighlights(fromX, fromY, toX, toY);
10598         }
10599     }
10600     DisplayMove(currentMove);
10601     SendMoveToProgram(currentMove++, &first);
10602     DisplayBothClocks();
10603     DrawPosition(FALSE, boards[currentMove]);
10604     // [HGM] PV info: always display, routine tests if empty
10605     DisplayComment(currentMove - 1, commentList[currentMove]);
10606     return TRUE;
10607 }
10608
10609
10610 int
10611 LoadGameOneMove(readAhead)
10612      ChessMove readAhead;
10613 {
10614     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10615     char promoChar = NULLCHAR;
10616     ChessMove moveType;
10617     char move[MSG_SIZ];
10618     char *p, *q;
10619
10620     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10621         gameMode != AnalyzeMode && gameMode != Training) {
10622         gameFileFP = NULL;
10623         return FALSE;
10624     }
10625
10626     yyboardindex = forwardMostMove;
10627     if (readAhead != EndOfFile) {
10628       moveType = readAhead;
10629     } else {
10630       if (gameFileFP == NULL)
10631           return FALSE;
10632       moveType = (ChessMove) Myylex();
10633     }
10634
10635     done = FALSE;
10636     switch (moveType) {
10637       case Comment:
10638         if (appData.debugMode)
10639           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10640         p = yy_text;
10641
10642         /* append the comment but don't display it */
10643         AppendComment(currentMove, p, FALSE);
10644         return TRUE;
10645
10646       case WhiteCapturesEnPassant:
10647       case BlackCapturesEnPassant:
10648       case WhitePromotion:
10649       case BlackPromotion:
10650       case WhiteNonPromotion:
10651       case BlackNonPromotion:
10652       case NormalMove:
10653       case WhiteKingSideCastle:
10654       case WhiteQueenSideCastle:
10655       case BlackKingSideCastle:
10656       case BlackQueenSideCastle:
10657       case WhiteKingSideCastleWild:
10658       case WhiteQueenSideCastleWild:
10659       case BlackKingSideCastleWild:
10660       case BlackQueenSideCastleWild:
10661       /* PUSH Fabien */
10662       case WhiteHSideCastleFR:
10663       case WhiteASideCastleFR:
10664       case BlackHSideCastleFR:
10665       case BlackASideCastleFR:
10666       /* POP Fabien */
10667         if (appData.debugMode)
10668           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10669         fromX = currentMoveString[0] - AAA;
10670         fromY = currentMoveString[1] - ONE;
10671         toX = currentMoveString[2] - AAA;
10672         toY = currentMoveString[3] - ONE;
10673         promoChar = currentMoveString[4];
10674         break;
10675
10676       case WhiteDrop:
10677       case BlackDrop:
10678         if (appData.debugMode)
10679           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10680         fromX = moveType == WhiteDrop ?
10681           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10682         (int) CharToPiece(ToLower(currentMoveString[0]));
10683         fromY = DROP_RANK;
10684         toX = currentMoveString[2] - AAA;
10685         toY = currentMoveString[3] - ONE;
10686         break;
10687
10688       case WhiteWins:
10689       case BlackWins:
10690       case GameIsDrawn:
10691       case GameUnfinished:
10692         if (appData.debugMode)
10693           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10694         p = strchr(yy_text, '{');
10695         if (p == NULL) p = strchr(yy_text, '(');
10696         if (p == NULL) {
10697             p = yy_text;
10698             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10699         } else {
10700             q = strchr(p, *p == '{' ? '}' : ')');
10701             if (q != NULL) *q = NULLCHAR;
10702             p++;
10703         }
10704         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10705         GameEnds(moveType, p, GE_FILE);
10706         done = TRUE;
10707         if (cmailMsgLoaded) {
10708             ClearHighlights();
10709             flipView = WhiteOnMove(currentMove);
10710             if (moveType == GameUnfinished) flipView = !flipView;
10711             if (appData.debugMode)
10712               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10713         }
10714         break;
10715
10716       case EndOfFile:
10717         if (appData.debugMode)
10718           fprintf(debugFP, "Parser hit end of file\n");
10719         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10720           case MT_NONE:
10721           case MT_CHECK:
10722             break;
10723           case MT_CHECKMATE:
10724           case MT_STAINMATE:
10725             if (WhiteOnMove(currentMove)) {
10726                 GameEnds(BlackWins, "Black mates", GE_FILE);
10727             } else {
10728                 GameEnds(WhiteWins, "White mates", GE_FILE);
10729             }
10730             break;
10731           case MT_STALEMATE:
10732             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10733             break;
10734         }
10735         done = TRUE;
10736         break;
10737
10738       case MoveNumberOne:
10739         if (lastLoadGameStart == GNUChessGame) {
10740             /* GNUChessGames have numbers, but they aren't move numbers */
10741             if (appData.debugMode)
10742               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10743                       yy_text, (int) moveType);
10744             return LoadGameOneMove(EndOfFile); /* tail recursion */
10745         }
10746         /* else fall thru */
10747
10748       case XBoardGame:
10749       case GNUChessGame:
10750       case PGNTag:
10751         /* Reached start of next game in file */
10752         if (appData.debugMode)
10753           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10754         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10755           case MT_NONE:
10756           case MT_CHECK:
10757             break;
10758           case MT_CHECKMATE:
10759           case MT_STAINMATE:
10760             if (WhiteOnMove(currentMove)) {
10761                 GameEnds(BlackWins, "Black mates", GE_FILE);
10762             } else {
10763                 GameEnds(WhiteWins, "White mates", GE_FILE);
10764             }
10765             break;
10766           case MT_STALEMATE:
10767             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10768             break;
10769         }
10770         done = TRUE;
10771         break;
10772
10773       case PositionDiagram:     /* should not happen; ignore */
10774       case ElapsedTime:         /* ignore */
10775       case NAG:                 /* ignore */
10776         if (appData.debugMode)
10777           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10778                   yy_text, (int) moveType);
10779         return LoadGameOneMove(EndOfFile); /* tail recursion */
10780
10781       case IllegalMove:
10782         if (appData.testLegality) {
10783             if (appData.debugMode)
10784               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10785             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10786                     (forwardMostMove / 2) + 1,
10787                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10788             DisplayError(move, 0);
10789             done = TRUE;
10790         } else {
10791             if (appData.debugMode)
10792               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10793                       yy_text, currentMoveString);
10794             fromX = currentMoveString[0] - AAA;
10795             fromY = currentMoveString[1] - ONE;
10796             toX = currentMoveString[2] - AAA;
10797             toY = currentMoveString[3] - ONE;
10798             promoChar = currentMoveString[4];
10799         }
10800         break;
10801
10802       case AmbiguousMove:
10803         if (appData.debugMode)
10804           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10805         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10806                 (forwardMostMove / 2) + 1,
10807                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10808         DisplayError(move, 0);
10809         done = TRUE;
10810         break;
10811
10812       default:
10813       case ImpossibleMove:
10814         if (appData.debugMode)
10815           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10816         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10817                 (forwardMostMove / 2) + 1,
10818                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10819         DisplayError(move, 0);
10820         done = TRUE;
10821         break;
10822     }
10823
10824     if (done) {
10825         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10826             DrawPosition(FALSE, boards[currentMove]);
10827             DisplayBothClocks();
10828             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10829               DisplayComment(currentMove - 1, commentList[currentMove]);
10830         }
10831         (void) StopLoadGameTimer();
10832         gameFileFP = NULL;
10833         cmailOldMove = forwardMostMove;
10834         return FALSE;
10835     } else {
10836         /* currentMoveString is set as a side-effect of yylex */
10837
10838         thinkOutput[0] = NULLCHAR;
10839         MakeMove(fromX, fromY, toX, toY, promoChar);
10840         currentMove = forwardMostMove;
10841         return TRUE;
10842     }
10843 }
10844
10845 /* Load the nth game from the given file */
10846 int
10847 LoadGameFromFile(filename, n, title, useList)
10848      char *filename;
10849      int n;
10850      char *title;
10851      /*Boolean*/ int useList;
10852 {
10853     FILE *f;
10854     char buf[MSG_SIZ];
10855
10856     if (strcmp(filename, "-") == 0) {
10857         f = stdin;
10858         title = "stdin";
10859     } else {
10860         f = fopen(filename, "rb");
10861         if (f == NULL) {
10862           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10863             DisplayError(buf, errno);
10864             return FALSE;
10865         }
10866     }
10867     if (fseek(f, 0, 0) == -1) {
10868         /* f is not seekable; probably a pipe */
10869         useList = FALSE;
10870     }
10871     if (useList && n == 0) {
10872         int error = GameListBuild(f);
10873         if (error) {
10874             DisplayError(_("Cannot build game list"), error);
10875         } else if (!ListEmpty(&gameList) &&
10876                    ((ListGame *) gameList.tailPred)->number > 1) {
10877             GameListPopUp(f, title);
10878             return TRUE;
10879         }
10880         GameListDestroy();
10881         n = 1;
10882     }
10883     if (n == 0) n = 1;
10884     return LoadGame(f, n, title, FALSE);
10885 }
10886
10887
10888 void
10889 MakeRegisteredMove()
10890 {
10891     int fromX, fromY, toX, toY;
10892     char promoChar;
10893     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10894         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10895           case CMAIL_MOVE:
10896           case CMAIL_DRAW:
10897             if (appData.debugMode)
10898               fprintf(debugFP, "Restoring %s for game %d\n",
10899                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10900
10901             thinkOutput[0] = NULLCHAR;
10902             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10903             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10904             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10905             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10906             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10907             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10908             MakeMove(fromX, fromY, toX, toY, promoChar);
10909             ShowMove(fromX, fromY, toX, toY);
10910
10911             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10912               case MT_NONE:
10913               case MT_CHECK:
10914                 break;
10915
10916               case MT_CHECKMATE:
10917               case MT_STAINMATE:
10918                 if (WhiteOnMove(currentMove)) {
10919                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10920                 } else {
10921                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10922                 }
10923                 break;
10924
10925               case MT_STALEMATE:
10926                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10927                 break;
10928             }
10929
10930             break;
10931
10932           case CMAIL_RESIGN:
10933             if (WhiteOnMove(currentMove)) {
10934                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10935             } else {
10936                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10937             }
10938             break;
10939
10940           case CMAIL_ACCEPT:
10941             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10942             break;
10943
10944           default:
10945             break;
10946         }
10947     }
10948
10949     return;
10950 }
10951
10952 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10953 int
10954 CmailLoadGame(f, gameNumber, title, useList)
10955      FILE *f;
10956      int gameNumber;
10957      char *title;
10958      int useList;
10959 {
10960     int retVal;
10961
10962     if (gameNumber > nCmailGames) {
10963         DisplayError(_("No more games in this message"), 0);
10964         return FALSE;
10965     }
10966     if (f == lastLoadGameFP) {
10967         int offset = gameNumber - lastLoadGameNumber;
10968         if (offset == 0) {
10969             cmailMsg[0] = NULLCHAR;
10970             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10971                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10972                 nCmailMovesRegistered--;
10973             }
10974             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10975             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10976                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10977             }
10978         } else {
10979             if (! RegisterMove()) return FALSE;
10980         }
10981     }
10982
10983     retVal = LoadGame(f, gameNumber, title, useList);
10984
10985     /* Make move registered during previous look at this game, if any */
10986     MakeRegisteredMove();
10987
10988     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10989         commentList[currentMove]
10990           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10991         DisplayComment(currentMove - 1, commentList[currentMove]);
10992     }
10993
10994     return retVal;
10995 }
10996
10997 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10998 int
10999 ReloadGame(offset)
11000      int offset;
11001 {
11002     int gameNumber = lastLoadGameNumber + offset;
11003     if (lastLoadGameFP == NULL) {
11004         DisplayError(_("No game has been loaded yet"), 0);
11005         return FALSE;
11006     }
11007     if (gameNumber <= 0) {
11008         DisplayError(_("Can't back up any further"), 0);
11009         return FALSE;
11010     }
11011     if (cmailMsgLoaded) {
11012         return CmailLoadGame(lastLoadGameFP, gameNumber,
11013                              lastLoadGameTitle, lastLoadGameUseList);
11014     } else {
11015         return LoadGame(lastLoadGameFP, gameNumber,
11016                         lastLoadGameTitle, lastLoadGameUseList);
11017     }
11018 }
11019
11020
11021
11022 /* Load the nth game from open file f */
11023 int
11024 LoadGame(f, gameNumber, title, useList)
11025      FILE *f;
11026      int gameNumber;
11027      char *title;
11028      int useList;
11029 {
11030     ChessMove cm;
11031     char buf[MSG_SIZ];
11032     int gn = gameNumber;
11033     ListGame *lg = NULL;
11034     int numPGNTags = 0;
11035     int err;
11036     GameMode oldGameMode;
11037     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11038
11039     if (appData.debugMode)
11040         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11041
11042     if (gameMode == Training )
11043         SetTrainingModeOff();
11044
11045     oldGameMode = gameMode;
11046     if (gameMode != BeginningOfGame) {
11047       Reset(FALSE, TRUE);
11048     }
11049
11050     gameFileFP = f;
11051     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11052         fclose(lastLoadGameFP);
11053     }
11054
11055     if (useList) {
11056         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11057
11058         if (lg) {
11059             fseek(f, lg->offset, 0);
11060             GameListHighlight(gameNumber);
11061             gn = 1;
11062         }
11063         else {
11064             DisplayError(_("Game number out of range"), 0);
11065             return FALSE;
11066         }
11067     } else {
11068         GameListDestroy();
11069         if (fseek(f, 0, 0) == -1) {
11070             if (f == lastLoadGameFP ?
11071                 gameNumber == lastLoadGameNumber + 1 :
11072                 gameNumber == 1) {
11073                 gn = 1;
11074             } else {
11075                 DisplayError(_("Can't seek on game file"), 0);
11076                 return FALSE;
11077             }
11078         }
11079     }
11080     lastLoadGameFP = f;
11081     lastLoadGameNumber = gameNumber;
11082     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11083     lastLoadGameUseList = useList;
11084
11085     yynewfile(f);
11086
11087     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11088       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11089                 lg->gameInfo.black);
11090             DisplayTitle(buf);
11091     } else if (*title != NULLCHAR) {
11092         if (gameNumber > 1) {
11093           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11094             DisplayTitle(buf);
11095         } else {
11096             DisplayTitle(title);
11097         }
11098     }
11099
11100     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11101         gameMode = PlayFromGameFile;
11102         ModeHighlight();
11103     }
11104
11105     currentMove = forwardMostMove = backwardMostMove = 0;
11106     CopyBoard(boards[0], initialPosition);
11107     StopClocks();
11108
11109     /*
11110      * Skip the first gn-1 games in the file.
11111      * Also skip over anything that precedes an identifiable
11112      * start of game marker, to avoid being confused by
11113      * garbage at the start of the file.  Currently
11114      * recognized start of game markers are the move number "1",
11115      * the pattern "gnuchess .* game", the pattern
11116      * "^[#;%] [^ ]* game file", and a PGN tag block.
11117      * A game that starts with one of the latter two patterns
11118      * will also have a move number 1, possibly
11119      * following a position diagram.
11120      * 5-4-02: Let's try being more lenient and allowing a game to
11121      * start with an unnumbered move.  Does that break anything?
11122      */
11123     cm = lastLoadGameStart = EndOfFile;
11124     while (gn > 0) {
11125         yyboardindex = forwardMostMove;
11126         cm = (ChessMove) Myylex();
11127         switch (cm) {
11128           case EndOfFile:
11129             if (cmailMsgLoaded) {
11130                 nCmailGames = CMAIL_MAX_GAMES - gn;
11131             } else {
11132                 Reset(TRUE, TRUE);
11133                 DisplayError(_("Game not found in file"), 0);
11134             }
11135             return FALSE;
11136
11137           case GNUChessGame:
11138           case XBoardGame:
11139             gn--;
11140             lastLoadGameStart = cm;
11141             break;
11142
11143           case MoveNumberOne:
11144             switch (lastLoadGameStart) {
11145               case GNUChessGame:
11146               case XBoardGame:
11147               case PGNTag:
11148                 break;
11149               case MoveNumberOne:
11150               case EndOfFile:
11151                 gn--;           /* count this game */
11152                 lastLoadGameStart = cm;
11153                 break;
11154               default:
11155                 /* impossible */
11156                 break;
11157             }
11158             break;
11159
11160           case PGNTag:
11161             switch (lastLoadGameStart) {
11162               case GNUChessGame:
11163               case PGNTag:
11164               case MoveNumberOne:
11165               case EndOfFile:
11166                 gn--;           /* count this game */
11167                 lastLoadGameStart = cm;
11168                 break;
11169               case XBoardGame:
11170                 lastLoadGameStart = cm; /* game counted already */
11171                 break;
11172               default:
11173                 /* impossible */
11174                 break;
11175             }
11176             if (gn > 0) {
11177                 do {
11178                     yyboardindex = forwardMostMove;
11179                     cm = (ChessMove) Myylex();
11180                 } while (cm == PGNTag || cm == Comment);
11181             }
11182             break;
11183
11184           case WhiteWins:
11185           case BlackWins:
11186           case GameIsDrawn:
11187             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11188                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11189                     != CMAIL_OLD_RESULT) {
11190                     nCmailResults ++ ;
11191                     cmailResult[  CMAIL_MAX_GAMES
11192                                 - gn - 1] = CMAIL_OLD_RESULT;
11193                 }
11194             }
11195             break;
11196
11197           case NormalMove:
11198             /* Only a NormalMove can be at the start of a game
11199              * without a position diagram. */
11200             if (lastLoadGameStart == EndOfFile ) {
11201               gn--;
11202               lastLoadGameStart = MoveNumberOne;
11203             }
11204             break;
11205
11206           default:
11207             break;
11208         }
11209     }
11210
11211     if (appData.debugMode)
11212       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11213
11214     if (cm == XBoardGame) {
11215         /* Skip any header junk before position diagram and/or move 1 */
11216         for (;;) {
11217             yyboardindex = forwardMostMove;
11218             cm = (ChessMove) Myylex();
11219
11220             if (cm == EndOfFile ||
11221                 cm == GNUChessGame || cm == XBoardGame) {
11222                 /* Empty game; pretend end-of-file and handle later */
11223                 cm = EndOfFile;
11224                 break;
11225             }
11226
11227             if (cm == MoveNumberOne || cm == PositionDiagram ||
11228                 cm == PGNTag || cm == Comment)
11229               break;
11230         }
11231     } else if (cm == GNUChessGame) {
11232         if (gameInfo.event != NULL) {
11233             free(gameInfo.event);
11234         }
11235         gameInfo.event = StrSave(yy_text);
11236     }
11237
11238     startedFromSetupPosition = FALSE;
11239     while (cm == PGNTag) {
11240         if (appData.debugMode)
11241           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11242         err = ParsePGNTag(yy_text, &gameInfo);
11243         if (!err) numPGNTags++;
11244
11245         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11246         if(gameInfo.variant != oldVariant) {
11247             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11248             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11249             InitPosition(TRUE);
11250             oldVariant = gameInfo.variant;
11251             if (appData.debugMode)
11252               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11253         }
11254
11255
11256         if (gameInfo.fen != NULL) {
11257           Board initial_position;
11258           startedFromSetupPosition = TRUE;
11259           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11260             Reset(TRUE, TRUE);
11261             DisplayError(_("Bad FEN position in file"), 0);
11262             return FALSE;
11263           }
11264           CopyBoard(boards[0], initial_position);
11265           if (blackPlaysFirst) {
11266             currentMove = forwardMostMove = backwardMostMove = 1;
11267             CopyBoard(boards[1], initial_position);
11268             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11269             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11270             timeRemaining[0][1] = whiteTimeRemaining;
11271             timeRemaining[1][1] = blackTimeRemaining;
11272             if (commentList[0] != NULL) {
11273               commentList[1] = commentList[0];
11274               commentList[0] = NULL;
11275             }
11276           } else {
11277             currentMove = forwardMostMove = backwardMostMove = 0;
11278           }
11279           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11280           {   int i;
11281               initialRulePlies = FENrulePlies;
11282               for( i=0; i< nrCastlingRights; i++ )
11283                   initialRights[i] = initial_position[CASTLING][i];
11284           }
11285           yyboardindex = forwardMostMove;
11286           free(gameInfo.fen);
11287           gameInfo.fen = NULL;
11288         }
11289
11290         yyboardindex = forwardMostMove;
11291         cm = (ChessMove) Myylex();
11292
11293         /* Handle comments interspersed among the tags */
11294         while (cm == Comment) {
11295             char *p;
11296             if (appData.debugMode)
11297               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11298             p = yy_text;
11299             AppendComment(currentMove, p, FALSE);
11300             yyboardindex = forwardMostMove;
11301             cm = (ChessMove) Myylex();
11302         }
11303     }
11304
11305     /* don't rely on existence of Event tag since if game was
11306      * pasted from clipboard the Event tag may not exist
11307      */
11308     if (numPGNTags > 0){
11309         char *tags;
11310         if (gameInfo.variant == VariantNormal) {
11311           VariantClass v = StringToVariant(gameInfo.event);
11312           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11313           if(v < VariantShogi) gameInfo.variant = v;
11314         }
11315         if (!matchMode) {
11316           if( appData.autoDisplayTags ) {
11317             tags = PGNTags(&gameInfo);
11318             TagsPopUp(tags, CmailMsg());
11319             free(tags);
11320           }
11321         }
11322     } else {
11323         /* Make something up, but don't display it now */
11324         SetGameInfo();
11325         TagsPopDown();
11326     }
11327
11328     if (cm == PositionDiagram) {
11329         int i, j;
11330         char *p;
11331         Board initial_position;
11332
11333         if (appData.debugMode)
11334           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11335
11336         if (!startedFromSetupPosition) {
11337             p = yy_text;
11338             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11339               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11340                 switch (*p) {
11341                   case '{':
11342                   case '[':
11343                   case '-':
11344                   case ' ':
11345                   case '\t':
11346                   case '\n':
11347                   case '\r':
11348                     break;
11349                   default:
11350                     initial_position[i][j++] = CharToPiece(*p);
11351                     break;
11352                 }
11353             while (*p == ' ' || *p == '\t' ||
11354                    *p == '\n' || *p == '\r') p++;
11355
11356             if (strncmp(p, "black", strlen("black"))==0)
11357               blackPlaysFirst = TRUE;
11358             else
11359               blackPlaysFirst = FALSE;
11360             startedFromSetupPosition = TRUE;
11361
11362             CopyBoard(boards[0], initial_position);
11363             if (blackPlaysFirst) {
11364                 currentMove = forwardMostMove = backwardMostMove = 1;
11365                 CopyBoard(boards[1], initial_position);
11366                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11367                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11368                 timeRemaining[0][1] = whiteTimeRemaining;
11369                 timeRemaining[1][1] = blackTimeRemaining;
11370                 if (commentList[0] != NULL) {
11371                     commentList[1] = commentList[0];
11372                     commentList[0] = NULL;
11373                 }
11374             } else {
11375                 currentMove = forwardMostMove = backwardMostMove = 0;
11376             }
11377         }
11378         yyboardindex = forwardMostMove;
11379         cm = (ChessMove) Myylex();
11380     }
11381
11382     if (first.pr == NoProc) {
11383         StartChessProgram(&first);
11384     }
11385     InitChessProgram(&first, FALSE);
11386     SendToProgram("force\n", &first);
11387     if (startedFromSetupPosition) {
11388         SendBoard(&first, forwardMostMove);
11389     if (appData.debugMode) {
11390         fprintf(debugFP, "Load Game\n");
11391     }
11392         DisplayBothClocks();
11393     }
11394
11395     /* [HGM] server: flag to write setup moves in broadcast file as one */
11396     loadFlag = appData.suppressLoadMoves;
11397
11398     while (cm == Comment) {
11399         char *p;
11400         if (appData.debugMode)
11401           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11402         p = yy_text;
11403         AppendComment(currentMove, p, FALSE);
11404         yyboardindex = forwardMostMove;
11405         cm = (ChessMove) Myylex();
11406     }
11407
11408     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11409         cm == WhiteWins || cm == BlackWins ||
11410         cm == GameIsDrawn || cm == GameUnfinished) {
11411         DisplayMessage("", _("No moves in game"));
11412         if (cmailMsgLoaded) {
11413             if (appData.debugMode)
11414               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11415             ClearHighlights();
11416             flipView = FALSE;
11417         }
11418         DrawPosition(FALSE, boards[currentMove]);
11419         DisplayBothClocks();
11420         gameMode = EditGame;
11421         ModeHighlight();
11422         gameFileFP = NULL;
11423         cmailOldMove = 0;
11424         return TRUE;
11425     }
11426
11427     // [HGM] PV info: routine tests if comment empty
11428     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11429         DisplayComment(currentMove - 1, commentList[currentMove]);
11430     }
11431     if (!matchMode && appData.timeDelay != 0)
11432       DrawPosition(FALSE, boards[currentMove]);
11433
11434     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11435       programStats.ok_to_send = 1;
11436     }
11437
11438     /* if the first token after the PGN tags is a move
11439      * and not move number 1, retrieve it from the parser
11440      */
11441     if (cm != MoveNumberOne)
11442         LoadGameOneMove(cm);
11443
11444     /* load the remaining moves from the file */
11445     while (LoadGameOneMove(EndOfFile)) {
11446       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11447       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11448     }
11449
11450     /* rewind to the start of the game */
11451     currentMove = backwardMostMove;
11452
11453     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11454
11455     if (oldGameMode == AnalyzeFile ||
11456         oldGameMode == AnalyzeMode) {
11457       AnalyzeFileEvent();
11458     }
11459
11460     if (matchMode || appData.timeDelay == 0) {
11461       ToEndEvent();
11462       gameMode = EditGame;
11463       ModeHighlight();
11464     } else if (appData.timeDelay > 0) {
11465       AutoPlayGameLoop();
11466     }
11467
11468     if (appData.debugMode)
11469         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11470
11471     loadFlag = 0; /* [HGM] true game starts */
11472     return TRUE;
11473 }
11474
11475 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11476 int
11477 ReloadPosition(offset)
11478      int offset;
11479 {
11480     int positionNumber = lastLoadPositionNumber + offset;
11481     if (lastLoadPositionFP == NULL) {
11482         DisplayError(_("No position has been loaded yet"), 0);
11483         return FALSE;
11484     }
11485     if (positionNumber <= 0) {
11486         DisplayError(_("Can't back up any further"), 0);
11487         return FALSE;
11488     }
11489     return LoadPosition(lastLoadPositionFP, positionNumber,
11490                         lastLoadPositionTitle);
11491 }
11492
11493 /* Load the nth position from the given file */
11494 int
11495 LoadPositionFromFile(filename, n, title)
11496      char *filename;
11497      int n;
11498      char *title;
11499 {
11500     FILE *f;
11501     char buf[MSG_SIZ];
11502
11503     if (strcmp(filename, "-") == 0) {
11504         return LoadPosition(stdin, n, "stdin");
11505     } else {
11506         f = fopen(filename, "rb");
11507         if (f == NULL) {
11508             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11509             DisplayError(buf, errno);
11510             return FALSE;
11511         } else {
11512             return LoadPosition(f, n, title);
11513         }
11514     }
11515 }
11516
11517 /* Load the nth position from the given open file, and close it */
11518 int
11519 LoadPosition(f, positionNumber, title)
11520      FILE *f;
11521      int positionNumber;
11522      char *title;
11523 {
11524     char *p, line[MSG_SIZ];
11525     Board initial_position;
11526     int i, j, fenMode, pn;
11527
11528     if (gameMode == Training )
11529         SetTrainingModeOff();
11530
11531     if (gameMode != BeginningOfGame) {
11532         Reset(FALSE, TRUE);
11533     }
11534     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11535         fclose(lastLoadPositionFP);
11536     }
11537     if (positionNumber == 0) positionNumber = 1;
11538     lastLoadPositionFP = f;
11539     lastLoadPositionNumber = positionNumber;
11540     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11541     if (first.pr == NoProc) {
11542       StartChessProgram(&first);
11543       InitChessProgram(&first, FALSE);
11544     }
11545     pn = positionNumber;
11546     if (positionNumber < 0) {
11547         /* Negative position number means to seek to that byte offset */
11548         if (fseek(f, -positionNumber, 0) == -1) {
11549             DisplayError(_("Can't seek on position file"), 0);
11550             return FALSE;
11551         };
11552         pn = 1;
11553     } else {
11554         if (fseek(f, 0, 0) == -1) {
11555             if (f == lastLoadPositionFP ?
11556                 positionNumber == lastLoadPositionNumber + 1 :
11557                 positionNumber == 1) {
11558                 pn = 1;
11559             } else {
11560                 DisplayError(_("Can't seek on position file"), 0);
11561                 return FALSE;
11562             }
11563         }
11564     }
11565     /* See if this file is FEN or old-style xboard */
11566     if (fgets(line, MSG_SIZ, f) == NULL) {
11567         DisplayError(_("Position not found in file"), 0);
11568         return FALSE;
11569     }
11570     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11571     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11572
11573     if (pn >= 2) {
11574         if (fenMode || line[0] == '#') pn--;
11575         while (pn > 0) {
11576             /* skip positions before number pn */
11577             if (fgets(line, MSG_SIZ, f) == NULL) {
11578                 Reset(TRUE, TRUE);
11579                 DisplayError(_("Position not found in file"), 0);
11580                 return FALSE;
11581             }
11582             if (fenMode || line[0] == '#') pn--;
11583         }
11584     }
11585
11586     if (fenMode) {
11587         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11588             DisplayError(_("Bad FEN position in file"), 0);
11589             return FALSE;
11590         }
11591     } else {
11592         (void) fgets(line, MSG_SIZ, f);
11593         (void) fgets(line, MSG_SIZ, f);
11594
11595         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11596             (void) fgets(line, MSG_SIZ, f);
11597             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11598                 if (*p == ' ')
11599                   continue;
11600                 initial_position[i][j++] = CharToPiece(*p);
11601             }
11602         }
11603
11604         blackPlaysFirst = FALSE;
11605         if (!feof(f)) {
11606             (void) fgets(line, MSG_SIZ, f);
11607             if (strncmp(line, "black", strlen("black"))==0)
11608               blackPlaysFirst = TRUE;
11609         }
11610     }
11611     startedFromSetupPosition = TRUE;
11612
11613     SendToProgram("force\n", &first);
11614     CopyBoard(boards[0], initial_position);
11615     if (blackPlaysFirst) {
11616         currentMove = forwardMostMove = backwardMostMove = 1;
11617         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11618         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11619         CopyBoard(boards[1], initial_position);
11620         DisplayMessage("", _("Black to play"));
11621     } else {
11622         currentMove = forwardMostMove = backwardMostMove = 0;
11623         DisplayMessage("", _("White to play"));
11624     }
11625     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11626     SendBoard(&first, forwardMostMove);
11627     if (appData.debugMode) {
11628 int i, j;
11629   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11630   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11631         fprintf(debugFP, "Load Position\n");
11632     }
11633
11634     if (positionNumber > 1) {
11635       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11636         DisplayTitle(line);
11637     } else {
11638         DisplayTitle(title);
11639     }
11640     gameMode = EditGame;
11641     ModeHighlight();
11642     ResetClocks();
11643     timeRemaining[0][1] = whiteTimeRemaining;
11644     timeRemaining[1][1] = blackTimeRemaining;
11645     DrawPosition(FALSE, boards[currentMove]);
11646
11647     return TRUE;
11648 }
11649
11650
11651 void
11652 CopyPlayerNameIntoFileName(dest, src)
11653      char **dest, *src;
11654 {
11655     while (*src != NULLCHAR && *src != ',') {
11656         if (*src == ' ') {
11657             *(*dest)++ = '_';
11658             src++;
11659         } else {
11660             *(*dest)++ = *src++;
11661         }
11662     }
11663 }
11664
11665 char *DefaultFileName(ext)
11666      char *ext;
11667 {
11668     static char def[MSG_SIZ];
11669     char *p;
11670
11671     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11672         p = def;
11673         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11674         *p++ = '-';
11675         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11676         *p++ = '.';
11677         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11678     } else {
11679         def[0] = NULLCHAR;
11680     }
11681     return def;
11682 }
11683
11684 /* Save the current game to the given file */
11685 int
11686 SaveGameToFile(filename, append)
11687      char *filename;
11688      int append;
11689 {
11690     FILE *f;
11691     char buf[MSG_SIZ];
11692     int result;
11693
11694     if (strcmp(filename, "-") == 0) {
11695         return SaveGame(stdout, 0, NULL);
11696     } else {
11697         f = fopen(filename, append ? "a" : "w");
11698         if (f == NULL) {
11699             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11700             DisplayError(buf, errno);
11701             return FALSE;
11702         } else {
11703             safeStrCpy(buf, lastMsg, MSG_SIZ);
11704             DisplayMessage(_("Waiting for access to save file"), "");
11705             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11706             DisplayMessage(_("Saving game"), "");
11707             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11708             result = SaveGame(f, 0, NULL);
11709             DisplayMessage(buf, "");
11710             return result;
11711         }
11712     }
11713 }
11714
11715 char *
11716 SavePart(str)
11717      char *str;
11718 {
11719     static char buf[MSG_SIZ];
11720     char *p;
11721
11722     p = strchr(str, ' ');
11723     if (p == NULL) return str;
11724     strncpy(buf, str, p - str);
11725     buf[p - str] = NULLCHAR;
11726     return buf;
11727 }
11728
11729 #define PGN_MAX_LINE 75
11730
11731 #define PGN_SIDE_WHITE  0
11732 #define PGN_SIDE_BLACK  1
11733
11734 /* [AS] */
11735 static int FindFirstMoveOutOfBook( int side )
11736 {
11737     int result = -1;
11738
11739     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11740         int index = backwardMostMove;
11741         int has_book_hit = 0;
11742
11743         if( (index % 2) != side ) {
11744             index++;
11745         }
11746
11747         while( index < forwardMostMove ) {
11748             /* Check to see if engine is in book */
11749             int depth = pvInfoList[index].depth;
11750             int score = pvInfoList[index].score;
11751             int in_book = 0;
11752
11753             if( depth <= 2 ) {
11754                 in_book = 1;
11755             }
11756             else if( score == 0 && depth == 63 ) {
11757                 in_book = 1; /* Zappa */
11758             }
11759             else if( score == 2 && depth == 99 ) {
11760                 in_book = 1; /* Abrok */
11761             }
11762
11763             has_book_hit += in_book;
11764
11765             if( ! in_book ) {
11766                 result = index;
11767
11768                 break;
11769             }
11770
11771             index += 2;
11772         }
11773     }
11774
11775     return result;
11776 }
11777
11778 /* [AS] */
11779 void GetOutOfBookInfo( char * buf )
11780 {
11781     int oob[2];
11782     int i;
11783     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11784
11785     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11786     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11787
11788     *buf = '\0';
11789
11790     if( oob[0] >= 0 || oob[1] >= 0 ) {
11791         for( i=0; i<2; i++ ) {
11792             int idx = oob[i];
11793
11794             if( idx >= 0 ) {
11795                 if( i > 0 && oob[0] >= 0 ) {
11796                     strcat( buf, "   " );
11797                 }
11798
11799                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11800                 sprintf( buf+strlen(buf), "%s%.2f",
11801                     pvInfoList[idx].score >= 0 ? "+" : "",
11802                     pvInfoList[idx].score / 100.0 );
11803             }
11804         }
11805     }
11806 }
11807
11808 /* Save game in PGN style and close the file */
11809 int
11810 SaveGamePGN(f)
11811      FILE *f;
11812 {
11813     int i, offset, linelen, newblock;
11814     time_t tm;
11815 //    char *movetext;
11816     char numtext[32];
11817     int movelen, numlen, blank;
11818     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11819
11820     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11821
11822     tm = time((time_t *) NULL);
11823
11824     PrintPGNTags(f, &gameInfo);
11825
11826     if (backwardMostMove > 0 || startedFromSetupPosition) {
11827         char *fen = PositionToFEN(backwardMostMove, NULL);
11828         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11829         fprintf(f, "\n{--------------\n");
11830         PrintPosition(f, backwardMostMove);
11831         fprintf(f, "--------------}\n");
11832         free(fen);
11833     }
11834     else {
11835         /* [AS] Out of book annotation */
11836         if( appData.saveOutOfBookInfo ) {
11837             char buf[64];
11838
11839             GetOutOfBookInfo( buf );
11840
11841             if( buf[0] != '\0' ) {
11842                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11843             }
11844         }
11845
11846         fprintf(f, "\n");
11847     }
11848
11849     i = backwardMostMove;
11850     linelen = 0;
11851     newblock = TRUE;
11852
11853     while (i < forwardMostMove) {
11854         /* Print comments preceding this move */
11855         if (commentList[i] != NULL) {
11856             if (linelen > 0) fprintf(f, "\n");
11857             fprintf(f, "%s", commentList[i]);
11858             linelen = 0;
11859             newblock = TRUE;
11860         }
11861
11862         /* Format move number */
11863         if ((i % 2) == 0)
11864           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11865         else
11866           if (newblock)
11867             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11868           else
11869             numtext[0] = NULLCHAR;
11870
11871         numlen = strlen(numtext);
11872         newblock = FALSE;
11873
11874         /* Print move number */
11875         blank = linelen > 0 && numlen > 0;
11876         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11877             fprintf(f, "\n");
11878             linelen = 0;
11879             blank = 0;
11880         }
11881         if (blank) {
11882             fprintf(f, " ");
11883             linelen++;
11884         }
11885         fprintf(f, "%s", numtext);
11886         linelen += numlen;
11887
11888         /* Get move */
11889         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11890         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11891
11892         /* Print move */
11893         blank = linelen > 0 && movelen > 0;
11894         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11895             fprintf(f, "\n");
11896             linelen = 0;
11897             blank = 0;
11898         }
11899         if (blank) {
11900             fprintf(f, " ");
11901             linelen++;
11902         }
11903         fprintf(f, "%s", move_buffer);
11904         linelen += movelen;
11905
11906         /* [AS] Add PV info if present */
11907         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11908             /* [HGM] add time */
11909             char buf[MSG_SIZ]; int seconds;
11910
11911             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11912
11913             if( seconds <= 0)
11914               buf[0] = 0;
11915             else
11916               if( seconds < 30 )
11917                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11918               else
11919                 {
11920                   seconds = (seconds + 4)/10; // round to full seconds
11921                   if( seconds < 60 )
11922                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11923                   else
11924                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11925                 }
11926
11927             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11928                       pvInfoList[i].score >= 0 ? "+" : "",
11929                       pvInfoList[i].score / 100.0,
11930                       pvInfoList[i].depth,
11931                       buf );
11932
11933             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11934
11935             /* Print score/depth */
11936             blank = linelen > 0 && movelen > 0;
11937             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11938                 fprintf(f, "\n");
11939                 linelen = 0;
11940                 blank = 0;
11941             }
11942             if (blank) {
11943                 fprintf(f, " ");
11944                 linelen++;
11945             }
11946             fprintf(f, "%s", move_buffer);
11947             linelen += movelen;
11948         }
11949
11950         i++;
11951     }
11952
11953     /* Start a new line */
11954     if (linelen > 0) fprintf(f, "\n");
11955
11956     /* Print comments after last move */
11957     if (commentList[i] != NULL) {
11958         fprintf(f, "%s\n", commentList[i]);
11959     }
11960
11961     /* Print result */
11962     if (gameInfo.resultDetails != NULL &&
11963         gameInfo.resultDetails[0] != NULLCHAR) {
11964         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11965                 PGNResult(gameInfo.result));
11966     } else {
11967         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11968     }
11969
11970     fclose(f);
11971     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11972     return TRUE;
11973 }
11974
11975 /* Save game in old style and close the file */
11976 int
11977 SaveGameOldStyle(f)
11978      FILE *f;
11979 {
11980     int i, offset;
11981     time_t tm;
11982
11983     tm = time((time_t *) NULL);
11984
11985     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11986     PrintOpponents(f);
11987
11988     if (backwardMostMove > 0 || startedFromSetupPosition) {
11989         fprintf(f, "\n[--------------\n");
11990         PrintPosition(f, backwardMostMove);
11991         fprintf(f, "--------------]\n");
11992     } else {
11993         fprintf(f, "\n");
11994     }
11995
11996     i = backwardMostMove;
11997     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11998
11999     while (i < forwardMostMove) {
12000         if (commentList[i] != NULL) {
12001             fprintf(f, "[%s]\n", commentList[i]);
12002         }
12003
12004         if ((i % 2) == 1) {
12005             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12006             i++;
12007         } else {
12008             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12009             i++;
12010             if (commentList[i] != NULL) {
12011                 fprintf(f, "\n");
12012                 continue;
12013             }
12014             if (i >= forwardMostMove) {
12015                 fprintf(f, "\n");
12016                 break;
12017             }
12018             fprintf(f, "%s\n", parseList[i]);
12019             i++;
12020         }
12021     }
12022
12023     if (commentList[i] != NULL) {
12024         fprintf(f, "[%s]\n", commentList[i]);
12025     }
12026
12027     /* This isn't really the old style, but it's close enough */
12028     if (gameInfo.resultDetails != NULL &&
12029         gameInfo.resultDetails[0] != NULLCHAR) {
12030         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12031                 gameInfo.resultDetails);
12032     } else {
12033         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12034     }
12035
12036     fclose(f);
12037     return TRUE;
12038 }
12039
12040 /* Save the current game to open file f and close the file */
12041 int
12042 SaveGame(f, dummy, dummy2)
12043      FILE *f;
12044      int dummy;
12045      char *dummy2;
12046 {
12047     if (gameMode == EditPosition) EditPositionDone(TRUE);
12048     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12049     if (appData.oldSaveStyle)
12050       return SaveGameOldStyle(f);
12051     else
12052       return SaveGamePGN(f);
12053 }
12054
12055 /* Save the current position to the given file */
12056 int
12057 SavePositionToFile(filename)
12058      char *filename;
12059 {
12060     FILE *f;
12061     char buf[MSG_SIZ];
12062
12063     if (strcmp(filename, "-") == 0) {
12064         return SavePosition(stdout, 0, NULL);
12065     } else {
12066         f = fopen(filename, "a");
12067         if (f == NULL) {
12068             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12069             DisplayError(buf, errno);
12070             return FALSE;
12071         } else {
12072             safeStrCpy(buf, lastMsg, MSG_SIZ);
12073             DisplayMessage(_("Waiting for access to save file"), "");
12074             flock(fileno(f), LOCK_EX); // [HGM] lock
12075             DisplayMessage(_("Saving position"), "");
12076             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12077             SavePosition(f, 0, NULL);
12078             DisplayMessage(buf, "");
12079             return TRUE;
12080         }
12081     }
12082 }
12083
12084 /* Save the current position to the given open file and close the file */
12085 int
12086 SavePosition(f, dummy, dummy2)
12087      FILE *f;
12088      int dummy;
12089      char *dummy2;
12090 {
12091     time_t tm;
12092     char *fen;
12093
12094     if (gameMode == EditPosition) EditPositionDone(TRUE);
12095     if (appData.oldSaveStyle) {
12096         tm = time((time_t *) NULL);
12097
12098         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12099         PrintOpponents(f);
12100         fprintf(f, "[--------------\n");
12101         PrintPosition(f, currentMove);
12102         fprintf(f, "--------------]\n");
12103     } else {
12104         fen = PositionToFEN(currentMove, NULL);
12105         fprintf(f, "%s\n", fen);
12106         free(fen);
12107     }
12108     fclose(f);
12109     return TRUE;
12110 }
12111
12112 void
12113 ReloadCmailMsgEvent(unregister)
12114      int unregister;
12115 {
12116 #if !WIN32
12117     static char *inFilename = NULL;
12118     static char *outFilename;
12119     int i;
12120     struct stat inbuf, outbuf;
12121     int status;
12122
12123     /* Any registered moves are unregistered if unregister is set, */
12124     /* i.e. invoked by the signal handler */
12125     if (unregister) {
12126         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12127             cmailMoveRegistered[i] = FALSE;
12128             if (cmailCommentList[i] != NULL) {
12129                 free(cmailCommentList[i]);
12130                 cmailCommentList[i] = NULL;
12131             }
12132         }
12133         nCmailMovesRegistered = 0;
12134     }
12135
12136     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12137         cmailResult[i] = CMAIL_NOT_RESULT;
12138     }
12139     nCmailResults = 0;
12140
12141     if (inFilename == NULL) {
12142         /* Because the filenames are static they only get malloced once  */
12143         /* and they never get freed                                      */
12144         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12145         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12146
12147         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12148         sprintf(outFilename, "%s.out", appData.cmailGameName);
12149     }
12150
12151     status = stat(outFilename, &outbuf);
12152     if (status < 0) {
12153         cmailMailedMove = FALSE;
12154     } else {
12155         status = stat(inFilename, &inbuf);
12156         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12157     }
12158
12159     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12160        counts the games, notes how each one terminated, etc.
12161
12162        It would be nice to remove this kludge and instead gather all
12163        the information while building the game list.  (And to keep it
12164        in the game list nodes instead of having a bunch of fixed-size
12165        parallel arrays.)  Note this will require getting each game's
12166        termination from the PGN tags, as the game list builder does
12167        not process the game moves.  --mann
12168        */
12169     cmailMsgLoaded = TRUE;
12170     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12171
12172     /* Load first game in the file or popup game menu */
12173     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12174
12175 #endif /* !WIN32 */
12176     return;
12177 }
12178
12179 int
12180 RegisterMove()
12181 {
12182     FILE *f;
12183     char string[MSG_SIZ];
12184
12185     if (   cmailMailedMove
12186         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12187         return TRUE;            /* Allow free viewing  */
12188     }
12189
12190     /* Unregister move to ensure that we don't leave RegisterMove        */
12191     /* with the move registered when the conditions for registering no   */
12192     /* longer hold                                                       */
12193     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12194         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12195         nCmailMovesRegistered --;
12196
12197         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12198           {
12199               free(cmailCommentList[lastLoadGameNumber - 1]);
12200               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12201           }
12202     }
12203
12204     if (cmailOldMove == -1) {
12205         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12206         return FALSE;
12207     }
12208
12209     if (currentMove > cmailOldMove + 1) {
12210         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12211         return FALSE;
12212     }
12213
12214     if (currentMove < cmailOldMove) {
12215         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12216         return FALSE;
12217     }
12218
12219     if (forwardMostMove > currentMove) {
12220         /* Silently truncate extra moves */
12221         TruncateGame();
12222     }
12223
12224     if (   (currentMove == cmailOldMove + 1)
12225         || (   (currentMove == cmailOldMove)
12226             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12227                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12228         if (gameInfo.result != GameUnfinished) {
12229             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12230         }
12231
12232         if (commentList[currentMove] != NULL) {
12233             cmailCommentList[lastLoadGameNumber - 1]
12234               = StrSave(commentList[currentMove]);
12235         }
12236         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12237
12238         if (appData.debugMode)
12239           fprintf(debugFP, "Saving %s for game %d\n",
12240                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12241
12242         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12243
12244         f = fopen(string, "w");
12245         if (appData.oldSaveStyle) {
12246             SaveGameOldStyle(f); /* also closes the file */
12247
12248             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12249             f = fopen(string, "w");
12250             SavePosition(f, 0, NULL); /* also closes the file */
12251         } else {
12252             fprintf(f, "{--------------\n");
12253             PrintPosition(f, currentMove);
12254             fprintf(f, "--------------}\n\n");
12255
12256             SaveGame(f, 0, NULL); /* also closes the file*/
12257         }
12258
12259         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12260         nCmailMovesRegistered ++;
12261     } else if (nCmailGames == 1) {
12262         DisplayError(_("You have not made a move yet"), 0);
12263         return FALSE;
12264     }
12265
12266     return TRUE;
12267 }
12268
12269 void
12270 MailMoveEvent()
12271 {
12272 #if !WIN32
12273     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12274     FILE *commandOutput;
12275     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12276     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12277     int nBuffers;
12278     int i;
12279     int archived;
12280     char *arcDir;
12281
12282     if (! cmailMsgLoaded) {
12283         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12284         return;
12285     }
12286
12287     if (nCmailGames == nCmailResults) {
12288         DisplayError(_("No unfinished games"), 0);
12289         return;
12290     }
12291
12292 #if CMAIL_PROHIBIT_REMAIL
12293     if (cmailMailedMove) {
12294       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);
12295         DisplayError(msg, 0);
12296         return;
12297     }
12298 #endif
12299
12300     if (! (cmailMailedMove || RegisterMove())) return;
12301
12302     if (   cmailMailedMove
12303         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12304       snprintf(string, MSG_SIZ, partCommandString,
12305                appData.debugMode ? " -v" : "", appData.cmailGameName);
12306         commandOutput = popen(string, "r");
12307
12308         if (commandOutput == NULL) {
12309             DisplayError(_("Failed to invoke cmail"), 0);
12310         } else {
12311             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12312                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12313             }
12314             if (nBuffers > 1) {
12315                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12316                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12317                 nBytes = MSG_SIZ - 1;
12318             } else {
12319                 (void) memcpy(msg, buffer, nBytes);
12320             }
12321             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12322
12323             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12324                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12325
12326                 archived = TRUE;
12327                 for (i = 0; i < nCmailGames; i ++) {
12328                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12329                         archived = FALSE;
12330                     }
12331                 }
12332                 if (   archived
12333                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12334                         != NULL)) {
12335                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12336                            arcDir,
12337                            appData.cmailGameName,
12338                            gameInfo.date);
12339                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12340                     cmailMsgLoaded = FALSE;
12341                 }
12342             }
12343
12344             DisplayInformation(msg);
12345             pclose(commandOutput);
12346         }
12347     } else {
12348         if ((*cmailMsg) != '\0') {
12349             DisplayInformation(cmailMsg);
12350         }
12351     }
12352
12353     return;
12354 #endif /* !WIN32 */
12355 }
12356
12357 char *
12358 CmailMsg()
12359 {
12360 #if WIN32
12361     return NULL;
12362 #else
12363     int  prependComma = 0;
12364     char number[5];
12365     char string[MSG_SIZ];       /* Space for game-list */
12366     int  i;
12367
12368     if (!cmailMsgLoaded) return "";
12369
12370     if (cmailMailedMove) {
12371       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12372     } else {
12373         /* Create a list of games left */
12374       snprintf(string, MSG_SIZ, "[");
12375         for (i = 0; i < nCmailGames; i ++) {
12376             if (! (   cmailMoveRegistered[i]
12377                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12378                 if (prependComma) {
12379                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12380                 } else {
12381                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12382                     prependComma = 1;
12383                 }
12384
12385                 strcat(string, number);
12386             }
12387         }
12388         strcat(string, "]");
12389
12390         if (nCmailMovesRegistered + nCmailResults == 0) {
12391             switch (nCmailGames) {
12392               case 1:
12393                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12394                 break;
12395
12396               case 2:
12397                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12398                 break;
12399
12400               default:
12401                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12402                          nCmailGames);
12403                 break;
12404             }
12405         } else {
12406             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12407               case 1:
12408                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12409                          string);
12410                 break;
12411
12412               case 0:
12413                 if (nCmailResults == nCmailGames) {
12414                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12415                 } else {
12416                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12417                 }
12418                 break;
12419
12420               default:
12421                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12422                          string);
12423             }
12424         }
12425     }
12426     return cmailMsg;
12427 #endif /* WIN32 */
12428 }
12429
12430 void
12431 ResetGameEvent()
12432 {
12433     if (gameMode == Training)
12434       SetTrainingModeOff();
12435
12436     Reset(TRUE, TRUE);
12437     cmailMsgLoaded = FALSE;
12438     if (appData.icsActive) {
12439       SendToICS(ics_prefix);
12440       SendToICS("refresh\n");
12441     }
12442 }
12443
12444 void
12445 ExitEvent(status)
12446      int status;
12447 {
12448     exiting++;
12449     if (exiting > 2) {
12450       /* Give up on clean exit */
12451       exit(status);
12452     }
12453     if (exiting > 1) {
12454       /* Keep trying for clean exit */
12455       return;
12456     }
12457
12458     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12459
12460     if (telnetISR != NULL) {
12461       RemoveInputSource(telnetISR);
12462     }
12463     if (icsPR != NoProc) {
12464       DestroyChildProcess(icsPR, TRUE);
12465     }
12466
12467     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12468     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12469
12470     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12471     /* make sure this other one finishes before killing it!                  */
12472     if(endingGame) { int count = 0;
12473         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12474         while(endingGame && count++ < 10) DoSleep(1);
12475         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12476     }
12477
12478     /* Kill off chess programs */
12479     if (first.pr != NoProc) {
12480         ExitAnalyzeMode();
12481
12482         DoSleep( appData.delayBeforeQuit );
12483         SendToProgram("quit\n", &first);
12484         DoSleep( appData.delayAfterQuit );
12485         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12486     }
12487     if (second.pr != NoProc) {
12488         DoSleep( appData.delayBeforeQuit );
12489         SendToProgram("quit\n", &second);
12490         DoSleep( appData.delayAfterQuit );
12491         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12492     }
12493     if (first.isr != NULL) {
12494         RemoveInputSource(first.isr);
12495     }
12496     if (second.isr != NULL) {
12497         RemoveInputSource(second.isr);
12498     }
12499
12500     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12501     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12502
12503     ShutDownFrontEnd();
12504     exit(status);
12505 }
12506
12507 void
12508 PauseEvent()
12509 {
12510     if (appData.debugMode)
12511         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12512     if (pausing) {
12513         pausing = FALSE;
12514         ModeHighlight();
12515         if (gameMode == MachinePlaysWhite ||
12516             gameMode == MachinePlaysBlack) {
12517             StartClocks();
12518         } else {
12519             DisplayBothClocks();
12520         }
12521         if (gameMode == PlayFromGameFile) {
12522             if (appData.timeDelay >= 0)
12523                 AutoPlayGameLoop();
12524         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12525             Reset(FALSE, TRUE);
12526             SendToICS(ics_prefix);
12527             SendToICS("refresh\n");
12528         } else if (currentMove < forwardMostMove) {
12529             ForwardInner(forwardMostMove);
12530         }
12531         pauseExamInvalid = FALSE;
12532     } else {
12533         switch (gameMode) {
12534           default:
12535             return;
12536           case IcsExamining:
12537             pauseExamForwardMostMove = forwardMostMove;
12538             pauseExamInvalid = FALSE;
12539             /* fall through */
12540           case IcsObserving:
12541           case IcsPlayingWhite:
12542           case IcsPlayingBlack:
12543             pausing = TRUE;
12544             ModeHighlight();
12545             return;
12546           case PlayFromGameFile:
12547             (void) StopLoadGameTimer();
12548             pausing = TRUE;
12549             ModeHighlight();
12550             break;
12551           case BeginningOfGame:
12552             if (appData.icsActive) return;
12553             /* else fall through */
12554           case MachinePlaysWhite:
12555           case MachinePlaysBlack:
12556           case TwoMachinesPlay:
12557             if (forwardMostMove == 0)
12558               return;           /* don't pause if no one has moved */
12559             if ((gameMode == MachinePlaysWhite &&
12560                  !WhiteOnMove(forwardMostMove)) ||
12561                 (gameMode == MachinePlaysBlack &&
12562                  WhiteOnMove(forwardMostMove))) {
12563                 StopClocks();
12564             }
12565             pausing = TRUE;
12566             ModeHighlight();
12567             break;
12568         }
12569     }
12570 }
12571
12572 void
12573 EditCommentEvent()
12574 {
12575     char title[MSG_SIZ];
12576
12577     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12578       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12579     } else {
12580       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12581                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12582                parseList[currentMove - 1]);
12583     }
12584
12585     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12586 }
12587
12588
12589 void
12590 EditTagsEvent()
12591 {
12592     char *tags = PGNTags(&gameInfo);
12593     bookUp = FALSE;
12594     EditTagsPopUp(tags, NULL);
12595     free(tags);
12596 }
12597
12598 void
12599 AnalyzeModeEvent()
12600 {
12601     if (appData.noChessProgram || gameMode == AnalyzeMode)
12602       return;
12603
12604     if (gameMode != AnalyzeFile) {
12605         if (!appData.icsEngineAnalyze) {
12606                EditGameEvent();
12607                if (gameMode != EditGame) return;
12608         }
12609         ResurrectChessProgram();
12610         SendToProgram("analyze\n", &first);
12611         first.analyzing = TRUE;
12612         /*first.maybeThinking = TRUE;*/
12613         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12614         EngineOutputPopUp();
12615     }
12616     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12617     pausing = FALSE;
12618     ModeHighlight();
12619     SetGameInfo();
12620
12621     StartAnalysisClock();
12622     GetTimeMark(&lastNodeCountTime);
12623     lastNodeCount = 0;
12624 }
12625
12626 void
12627 AnalyzeFileEvent()
12628 {
12629     if (appData.noChessProgram || gameMode == AnalyzeFile)
12630       return;
12631
12632     if (gameMode != AnalyzeMode) {
12633         EditGameEvent();
12634         if (gameMode != EditGame) return;
12635         ResurrectChessProgram();
12636         SendToProgram("analyze\n", &first);
12637         first.analyzing = TRUE;
12638         /*first.maybeThinking = TRUE;*/
12639         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12640         EngineOutputPopUp();
12641     }
12642     gameMode = AnalyzeFile;
12643     pausing = FALSE;
12644     ModeHighlight();
12645     SetGameInfo();
12646
12647     StartAnalysisClock();
12648     GetTimeMark(&lastNodeCountTime);
12649     lastNodeCount = 0;
12650 }
12651
12652 void
12653 MachineWhiteEvent()
12654 {
12655     char buf[MSG_SIZ];
12656     char *bookHit = NULL;
12657
12658     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12659       return;
12660
12661
12662     if (gameMode == PlayFromGameFile ||
12663         gameMode == TwoMachinesPlay  ||
12664         gameMode == Training         ||
12665         gameMode == AnalyzeMode      ||
12666         gameMode == EndOfGame)
12667         EditGameEvent();
12668
12669     if (gameMode == EditPosition)
12670         EditPositionDone(TRUE);
12671
12672     if (!WhiteOnMove(currentMove)) {
12673         DisplayError(_("It is not White's turn"), 0);
12674         return;
12675     }
12676
12677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12678       ExitAnalyzeMode();
12679
12680     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12681         gameMode == AnalyzeFile)
12682         TruncateGame();
12683
12684     ResurrectChessProgram();    /* in case it isn't running */
12685     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12686         gameMode = MachinePlaysWhite;
12687         ResetClocks();
12688     } else
12689     gameMode = MachinePlaysWhite;
12690     pausing = FALSE;
12691     ModeHighlight();
12692     SetGameInfo();
12693     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12694     DisplayTitle(buf);
12695     if (first.sendName) {
12696       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12697       SendToProgram(buf, &first);
12698     }
12699     if (first.sendTime) {
12700       if (first.useColors) {
12701         SendToProgram("black\n", &first); /*gnu kludge*/
12702       }
12703       SendTimeRemaining(&first, TRUE);
12704     }
12705     if (first.useColors) {
12706       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12707     }
12708     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12709     SetMachineThinkingEnables();
12710     first.maybeThinking = TRUE;
12711     StartClocks();
12712     firstMove = FALSE;
12713
12714     if (appData.autoFlipView && !flipView) {
12715       flipView = !flipView;
12716       DrawPosition(FALSE, NULL);
12717       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12718     }
12719
12720     if(bookHit) { // [HGM] book: simulate book reply
12721         static char bookMove[MSG_SIZ]; // a bit generous?
12722
12723         programStats.nodes = programStats.depth = programStats.time =
12724         programStats.score = programStats.got_only_move = 0;
12725         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12726
12727         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12728         strcat(bookMove, bookHit);
12729         HandleMachineMove(bookMove, &first);
12730     }
12731 }
12732
12733 void
12734 MachineBlackEvent()
12735 {
12736   char buf[MSG_SIZ];
12737   char *bookHit = NULL;
12738
12739     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12740         return;
12741
12742
12743     if (gameMode == PlayFromGameFile ||
12744         gameMode == TwoMachinesPlay  ||
12745         gameMode == Training         ||
12746         gameMode == AnalyzeMode      ||
12747         gameMode == EndOfGame)
12748         EditGameEvent();
12749
12750     if (gameMode == EditPosition)
12751         EditPositionDone(TRUE);
12752
12753     if (WhiteOnMove(currentMove)) {
12754         DisplayError(_("It is not Black's turn"), 0);
12755         return;
12756     }
12757
12758     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12759       ExitAnalyzeMode();
12760
12761     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12762         gameMode == AnalyzeFile)
12763         TruncateGame();
12764
12765     ResurrectChessProgram();    /* in case it isn't running */
12766     gameMode = MachinePlaysBlack;
12767     pausing = FALSE;
12768     ModeHighlight();
12769     SetGameInfo();
12770     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12771     DisplayTitle(buf);
12772     if (first.sendName) {
12773       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12774       SendToProgram(buf, &first);
12775     }
12776     if (first.sendTime) {
12777       if (first.useColors) {
12778         SendToProgram("white\n", &first); /*gnu kludge*/
12779       }
12780       SendTimeRemaining(&first, FALSE);
12781     }
12782     if (first.useColors) {
12783       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12784     }
12785     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12786     SetMachineThinkingEnables();
12787     first.maybeThinking = TRUE;
12788     StartClocks();
12789
12790     if (appData.autoFlipView && flipView) {
12791       flipView = !flipView;
12792       DrawPosition(FALSE, NULL);
12793       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12794     }
12795     if(bookHit) { // [HGM] book: simulate book reply
12796         static char bookMove[MSG_SIZ]; // a bit generous?
12797
12798         programStats.nodes = programStats.depth = programStats.time =
12799         programStats.score = programStats.got_only_move = 0;
12800         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12801
12802         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12803         strcat(bookMove, bookHit);
12804         HandleMachineMove(bookMove, &first);
12805     }
12806 }
12807
12808
12809 void
12810 DisplayTwoMachinesTitle()
12811 {
12812     char buf[MSG_SIZ];
12813     if (appData.matchGames > 0) {
12814         if(appData.tourneyFile[0]) {
12815           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12816                    gameInfo.white, gameInfo.black,
12817                    nextGame+1, appData.matchGames+1,
12818                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12819         } else 
12820         if (first.twoMachinesColor[0] == 'w') {
12821           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12822                    gameInfo.white, gameInfo.black,
12823                    first.matchWins, second.matchWins,
12824                    matchGame - 1 - (first.matchWins + second.matchWins));
12825         } else {
12826           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12827                    gameInfo.white, gameInfo.black,
12828                    second.matchWins, first.matchWins,
12829                    matchGame - 1 - (first.matchWins + second.matchWins));
12830         }
12831     } else {
12832       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12833     }
12834     DisplayTitle(buf);
12835 }
12836
12837 void
12838 SettingsMenuIfReady()
12839 {
12840   if (second.lastPing != second.lastPong) {
12841     DisplayMessage("", _("Waiting for second chess program"));
12842     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12843     return;
12844   }
12845   ThawUI();
12846   DisplayMessage("", "");
12847   SettingsPopUp(&second);
12848 }
12849
12850 int
12851 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12852 {
12853     char buf[MSG_SIZ];
12854     if (cps->pr == NULL) {
12855         StartChessProgram(cps);
12856         if (cps->protocolVersion == 1) {
12857           retry();
12858         } else {
12859           /* kludge: allow timeout for initial "feature" command */
12860           FreezeUI();
12861           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12862           DisplayMessage("", buf);
12863           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12864         }
12865         return 1;
12866     }
12867     return 0;
12868 }
12869
12870 void
12871 TwoMachinesEvent P((void))
12872 {
12873     int i;
12874     char buf[MSG_SIZ];
12875     ChessProgramState *onmove;
12876     char *bookHit = NULL;
12877     static int stalling = 0;
12878     TimeMark now;
12879     long wait;
12880
12881     if (appData.noChessProgram) return;
12882
12883     switch (gameMode) {
12884       case TwoMachinesPlay:
12885         return;
12886       case MachinePlaysWhite:
12887       case MachinePlaysBlack:
12888         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12889             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12890             return;
12891         }
12892         /* fall through */
12893       case BeginningOfGame:
12894       case PlayFromGameFile:
12895       case EndOfGame:
12896         EditGameEvent();
12897         if (gameMode != EditGame) return;
12898         break;
12899       case EditPosition:
12900         EditPositionDone(TRUE);
12901         break;
12902       case AnalyzeMode:
12903       case AnalyzeFile:
12904         ExitAnalyzeMode();
12905         break;
12906       case EditGame:
12907       default:
12908         break;
12909     }
12910
12911 //    forwardMostMove = currentMove;
12912     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12913
12914     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12915
12916     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12917     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12918       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12919       return;
12920     }
12921     if(!stalling) {
12922       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12923       SendToProgram("force\n", &second);
12924       stalling = 1;
12925       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12926       return;
12927     }
12928     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12929     if(appData.matchPause>10000 || appData.matchPause<10)
12930                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12931     wait = SubtractTimeMarks(&now, &pauseStart);
12932     if(wait < appData.matchPause) {
12933         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12934         return;
12935     }
12936     stalling = 0;
12937     DisplayMessage("", "");
12938     if (startedFromSetupPosition) {
12939         SendBoard(&second, backwardMostMove);
12940     if (appData.debugMode) {
12941         fprintf(debugFP, "Two Machines\n");
12942     }
12943     }
12944     for (i = backwardMostMove; i < forwardMostMove; i++) {
12945         SendMoveToProgram(i, &second);
12946     }
12947
12948     gameMode = TwoMachinesPlay;
12949     pausing = FALSE;
12950     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12951     SetGameInfo();
12952     DisplayTwoMachinesTitle();
12953     firstMove = TRUE;
12954     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12955         onmove = &first;
12956     } else {
12957         onmove = &second;
12958     }
12959     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12960     SendToProgram(first.computerString, &first);
12961     if (first.sendName) {
12962       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12963       SendToProgram(buf, &first);
12964     }
12965     SendToProgram(second.computerString, &second);
12966     if (second.sendName) {
12967       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12968       SendToProgram(buf, &second);
12969     }
12970
12971     ResetClocks();
12972     if (!first.sendTime || !second.sendTime) {
12973         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12974         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12975     }
12976     if (onmove->sendTime) {
12977       if (onmove->useColors) {
12978         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12979       }
12980       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12981     }
12982     if (onmove->useColors) {
12983       SendToProgram(onmove->twoMachinesColor, onmove);
12984     }
12985     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12986 //    SendToProgram("go\n", onmove);
12987     onmove->maybeThinking = TRUE;
12988     SetMachineThinkingEnables();
12989
12990     StartClocks();
12991
12992     if(bookHit) { // [HGM] book: simulate book reply
12993         static char bookMove[MSG_SIZ]; // a bit generous?
12994
12995         programStats.nodes = programStats.depth = programStats.time =
12996         programStats.score = programStats.got_only_move = 0;
12997         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12998
12999         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13000         strcat(bookMove, bookHit);
13001         savedMessage = bookMove; // args for deferred call
13002         savedState = onmove;
13003         ScheduleDelayedEvent(DeferredBookMove, 1);
13004     }
13005 }
13006
13007 void
13008 TrainingEvent()
13009 {
13010     if (gameMode == Training) {
13011       SetTrainingModeOff();
13012       gameMode = PlayFromGameFile;
13013       DisplayMessage("", _("Training mode off"));
13014     } else {
13015       gameMode = Training;
13016       animateTraining = appData.animate;
13017
13018       /* make sure we are not already at the end of the game */
13019       if (currentMove < forwardMostMove) {
13020         SetTrainingModeOn();
13021         DisplayMessage("", _("Training mode on"));
13022       } else {
13023         gameMode = PlayFromGameFile;
13024         DisplayError(_("Already at end of game"), 0);
13025       }
13026     }
13027     ModeHighlight();
13028 }
13029
13030 void
13031 IcsClientEvent()
13032 {
13033     if (!appData.icsActive) return;
13034     switch (gameMode) {
13035       case IcsPlayingWhite:
13036       case IcsPlayingBlack:
13037       case IcsObserving:
13038       case IcsIdle:
13039       case BeginningOfGame:
13040       case IcsExamining:
13041         return;
13042
13043       case EditGame:
13044         break;
13045
13046       case EditPosition:
13047         EditPositionDone(TRUE);
13048         break;
13049
13050       case AnalyzeMode:
13051       case AnalyzeFile:
13052         ExitAnalyzeMode();
13053         break;
13054
13055       default:
13056         EditGameEvent();
13057         break;
13058     }
13059
13060     gameMode = IcsIdle;
13061     ModeHighlight();
13062     return;
13063 }
13064
13065
13066 void
13067 EditGameEvent()
13068 {
13069     int i;
13070
13071     switch (gameMode) {
13072       case Training:
13073         SetTrainingModeOff();
13074         break;
13075       case MachinePlaysWhite:
13076       case MachinePlaysBlack:
13077       case BeginningOfGame:
13078         SendToProgram("force\n", &first);
13079         SetUserThinkingEnables();
13080         break;
13081       case PlayFromGameFile:
13082         (void) StopLoadGameTimer();
13083         if (gameFileFP != NULL) {
13084             gameFileFP = NULL;
13085         }
13086         break;
13087       case EditPosition:
13088         EditPositionDone(TRUE);
13089         break;
13090       case AnalyzeMode:
13091       case AnalyzeFile:
13092         ExitAnalyzeMode();
13093         SendToProgram("force\n", &first);
13094         break;
13095       case TwoMachinesPlay:
13096         GameEnds(EndOfFile, NULL, GE_PLAYER);
13097         ResurrectChessProgram();
13098         SetUserThinkingEnables();
13099         break;
13100       case EndOfGame:
13101         ResurrectChessProgram();
13102         break;
13103       case IcsPlayingBlack:
13104       case IcsPlayingWhite:
13105         DisplayError(_("Warning: You are still playing a game"), 0);
13106         break;
13107       case IcsObserving:
13108         DisplayError(_("Warning: You are still observing a game"), 0);
13109         break;
13110       case IcsExamining:
13111         DisplayError(_("Warning: You are still examining a game"), 0);
13112         break;
13113       case IcsIdle:
13114         break;
13115       case EditGame:
13116       default:
13117         return;
13118     }
13119
13120     pausing = FALSE;
13121     StopClocks();
13122     first.offeredDraw = second.offeredDraw = 0;
13123
13124     if (gameMode == PlayFromGameFile) {
13125         whiteTimeRemaining = timeRemaining[0][currentMove];
13126         blackTimeRemaining = timeRemaining[1][currentMove];
13127         DisplayTitle("");
13128     }
13129
13130     if (gameMode == MachinePlaysWhite ||
13131         gameMode == MachinePlaysBlack ||
13132         gameMode == TwoMachinesPlay ||
13133         gameMode == EndOfGame) {
13134         i = forwardMostMove;
13135         while (i > currentMove) {
13136             SendToProgram("undo\n", &first);
13137             i--;
13138         }
13139         whiteTimeRemaining = timeRemaining[0][currentMove];
13140         blackTimeRemaining = timeRemaining[1][currentMove];
13141         DisplayBothClocks();
13142         if (whiteFlag || blackFlag) {
13143             whiteFlag = blackFlag = 0;
13144         }
13145         DisplayTitle("");
13146     }
13147
13148     gameMode = EditGame;
13149     ModeHighlight();
13150     SetGameInfo();
13151 }
13152
13153
13154 void
13155 EditPositionEvent()
13156 {
13157     if (gameMode == EditPosition) {
13158         EditGameEvent();
13159         return;
13160     }
13161
13162     EditGameEvent();
13163     if (gameMode != EditGame) return;
13164
13165     gameMode = EditPosition;
13166     ModeHighlight();
13167     SetGameInfo();
13168     if (currentMove > 0)
13169       CopyBoard(boards[0], boards[currentMove]);
13170
13171     blackPlaysFirst = !WhiteOnMove(currentMove);
13172     ResetClocks();
13173     currentMove = forwardMostMove = backwardMostMove = 0;
13174     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13175     DisplayMove(-1);
13176 }
13177
13178 void
13179 ExitAnalyzeMode()
13180 {
13181     /* [DM] icsEngineAnalyze - possible call from other functions */
13182     if (appData.icsEngineAnalyze) {
13183         appData.icsEngineAnalyze = FALSE;
13184
13185         DisplayMessage("",_("Close ICS engine analyze..."));
13186     }
13187     if (first.analysisSupport && first.analyzing) {
13188       SendToProgram("exit\n", &first);
13189       first.analyzing = FALSE;
13190     }
13191     thinkOutput[0] = NULLCHAR;
13192 }
13193
13194 void
13195 EditPositionDone(Boolean fakeRights)
13196 {
13197     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13198
13199     startedFromSetupPosition = TRUE;
13200     InitChessProgram(&first, FALSE);
13201     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13202       boards[0][EP_STATUS] = EP_NONE;
13203       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13204     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13205         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13206         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13207       } else boards[0][CASTLING][2] = NoRights;
13208     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13209         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13210         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13211       } else boards[0][CASTLING][5] = NoRights;
13212     }
13213     SendToProgram("force\n", &first);
13214     if (blackPlaysFirst) {
13215         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13216         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13217         currentMove = forwardMostMove = backwardMostMove = 1;
13218         CopyBoard(boards[1], boards[0]);
13219     } else {
13220         currentMove = forwardMostMove = backwardMostMove = 0;
13221     }
13222     SendBoard(&first, forwardMostMove);
13223     if (appData.debugMode) {
13224         fprintf(debugFP, "EditPosDone\n");
13225     }
13226     DisplayTitle("");
13227     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13228     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13229     gameMode = EditGame;
13230     ModeHighlight();
13231     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13232     ClearHighlights(); /* [AS] */
13233 }
13234
13235 /* Pause for `ms' milliseconds */
13236 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13237 void
13238 TimeDelay(ms)
13239      long ms;
13240 {
13241     TimeMark m1, m2;
13242
13243     GetTimeMark(&m1);
13244     do {
13245         GetTimeMark(&m2);
13246     } while (SubtractTimeMarks(&m2, &m1) < ms);
13247 }
13248
13249 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13250 void
13251 SendMultiLineToICS(buf)
13252      char *buf;
13253 {
13254     char temp[MSG_SIZ+1], *p;
13255     int len;
13256
13257     len = strlen(buf);
13258     if (len > MSG_SIZ)
13259       len = MSG_SIZ;
13260
13261     strncpy(temp, buf, len);
13262     temp[len] = 0;
13263
13264     p = temp;
13265     while (*p) {
13266         if (*p == '\n' || *p == '\r')
13267           *p = ' ';
13268         ++p;
13269     }
13270
13271     strcat(temp, "\n");
13272     SendToICS(temp);
13273     SendToPlayer(temp, strlen(temp));
13274 }
13275
13276 void
13277 SetWhiteToPlayEvent()
13278 {
13279     if (gameMode == EditPosition) {
13280         blackPlaysFirst = FALSE;
13281         DisplayBothClocks();    /* works because currentMove is 0 */
13282     } else if (gameMode == IcsExamining) {
13283         SendToICS(ics_prefix);
13284         SendToICS("tomove white\n");
13285     }
13286 }
13287
13288 void
13289 SetBlackToPlayEvent()
13290 {
13291     if (gameMode == EditPosition) {
13292         blackPlaysFirst = TRUE;
13293         currentMove = 1;        /* kludge */
13294         DisplayBothClocks();
13295         currentMove = 0;
13296     } else if (gameMode == IcsExamining) {
13297         SendToICS(ics_prefix);
13298         SendToICS("tomove black\n");
13299     }
13300 }
13301
13302 void
13303 EditPositionMenuEvent(selection, x, y)
13304      ChessSquare selection;
13305      int x, y;
13306 {
13307     char buf[MSG_SIZ];
13308     ChessSquare piece = boards[0][y][x];
13309
13310     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13311
13312     switch (selection) {
13313       case ClearBoard:
13314         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13315             SendToICS(ics_prefix);
13316             SendToICS("bsetup clear\n");
13317         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13318             SendToICS(ics_prefix);
13319             SendToICS("clearboard\n");
13320         } else {
13321             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13322                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13323                 for (y = 0; y < BOARD_HEIGHT; y++) {
13324                     if (gameMode == IcsExamining) {
13325                         if (boards[currentMove][y][x] != EmptySquare) {
13326                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13327                                     AAA + x, ONE + y);
13328                             SendToICS(buf);
13329                         }
13330                     } else {
13331                         boards[0][y][x] = p;
13332                     }
13333                 }
13334             }
13335         }
13336         if (gameMode == EditPosition) {
13337             DrawPosition(FALSE, boards[0]);
13338         }
13339         break;
13340
13341       case WhitePlay:
13342         SetWhiteToPlayEvent();
13343         break;
13344
13345       case BlackPlay:
13346         SetBlackToPlayEvent();
13347         break;
13348
13349       case EmptySquare:
13350         if (gameMode == IcsExamining) {
13351             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13352             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13353             SendToICS(buf);
13354         } else {
13355             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13356                 if(x == BOARD_LEFT-2) {
13357                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13358                     boards[0][y][1] = 0;
13359                 } else
13360                 if(x == BOARD_RGHT+1) {
13361                     if(y >= gameInfo.holdingsSize) break;
13362                     boards[0][y][BOARD_WIDTH-2] = 0;
13363                 } else break;
13364             }
13365             boards[0][y][x] = EmptySquare;
13366             DrawPosition(FALSE, boards[0]);
13367         }
13368         break;
13369
13370       case PromotePiece:
13371         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13372            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13373             selection = (ChessSquare) (PROMOTED piece);
13374         } else if(piece == EmptySquare) selection = WhiteSilver;
13375         else selection = (ChessSquare)((int)piece - 1);
13376         goto defaultlabel;
13377
13378       case DemotePiece:
13379         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13380            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13381             selection = (ChessSquare) (DEMOTED piece);
13382         } else if(piece == EmptySquare) selection = BlackSilver;
13383         else selection = (ChessSquare)((int)piece + 1);
13384         goto defaultlabel;
13385
13386       case WhiteQueen:
13387       case BlackQueen:
13388         if(gameInfo.variant == VariantShatranj ||
13389            gameInfo.variant == VariantXiangqi  ||
13390            gameInfo.variant == VariantCourier  ||
13391            gameInfo.variant == VariantMakruk     )
13392             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13393         goto defaultlabel;
13394
13395       case WhiteKing:
13396       case BlackKing:
13397         if(gameInfo.variant == VariantXiangqi)
13398             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13399         if(gameInfo.variant == VariantKnightmate)
13400             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13401       default:
13402         defaultlabel:
13403         if (gameMode == IcsExamining) {
13404             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13405             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13406                      PieceToChar(selection), AAA + x, ONE + y);
13407             SendToICS(buf);
13408         } else {
13409             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13410                 int n;
13411                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13412                     n = PieceToNumber(selection - BlackPawn);
13413                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13414                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13415                     boards[0][BOARD_HEIGHT-1-n][1]++;
13416                 } else
13417                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13418                     n = PieceToNumber(selection);
13419                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13420                     boards[0][n][BOARD_WIDTH-1] = selection;
13421                     boards[0][n][BOARD_WIDTH-2]++;
13422                 }
13423             } else
13424             boards[0][y][x] = selection;
13425             DrawPosition(TRUE, boards[0]);
13426         }
13427         break;
13428     }
13429 }
13430
13431
13432 void
13433 DropMenuEvent(selection, x, y)
13434      ChessSquare selection;
13435      int x, y;
13436 {
13437     ChessMove moveType;
13438
13439     switch (gameMode) {
13440       case IcsPlayingWhite:
13441       case MachinePlaysBlack:
13442         if (!WhiteOnMove(currentMove)) {
13443             DisplayMoveError(_("It is Black's turn"));
13444             return;
13445         }
13446         moveType = WhiteDrop;
13447         break;
13448       case IcsPlayingBlack:
13449       case MachinePlaysWhite:
13450         if (WhiteOnMove(currentMove)) {
13451             DisplayMoveError(_("It is White's turn"));
13452             return;
13453         }
13454         moveType = BlackDrop;
13455         break;
13456       case EditGame:
13457         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13458         break;
13459       default:
13460         return;
13461     }
13462
13463     if (moveType == BlackDrop && selection < BlackPawn) {
13464       selection = (ChessSquare) ((int) selection
13465                                  + (int) BlackPawn - (int) WhitePawn);
13466     }
13467     if (boards[currentMove][y][x] != EmptySquare) {
13468         DisplayMoveError(_("That square is occupied"));
13469         return;
13470     }
13471
13472     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13473 }
13474
13475 void
13476 AcceptEvent()
13477 {
13478     /* Accept a pending offer of any kind from opponent */
13479
13480     if (appData.icsActive) {
13481         SendToICS(ics_prefix);
13482         SendToICS("accept\n");
13483     } else if (cmailMsgLoaded) {
13484         if (currentMove == cmailOldMove &&
13485             commentList[cmailOldMove] != NULL &&
13486             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13487                    "Black offers a draw" : "White offers a draw")) {
13488             TruncateGame();
13489             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13490             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13491         } else {
13492             DisplayError(_("There is no pending offer on this move"), 0);
13493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13494         }
13495     } else {
13496         /* Not used for offers from chess program */
13497     }
13498 }
13499
13500 void
13501 DeclineEvent()
13502 {
13503     /* Decline a pending offer of any kind from opponent */
13504
13505     if (appData.icsActive) {
13506         SendToICS(ics_prefix);
13507         SendToICS("decline\n");
13508     } else if (cmailMsgLoaded) {
13509         if (currentMove == cmailOldMove &&
13510             commentList[cmailOldMove] != NULL &&
13511             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13512                    "Black offers a draw" : "White offers a draw")) {
13513 #ifdef NOTDEF
13514             AppendComment(cmailOldMove, "Draw declined", TRUE);
13515             DisplayComment(cmailOldMove - 1, "Draw declined");
13516 #endif /*NOTDEF*/
13517         } else {
13518             DisplayError(_("There is no pending offer on this move"), 0);
13519         }
13520     } else {
13521         /* Not used for offers from chess program */
13522     }
13523 }
13524
13525 void
13526 RematchEvent()
13527 {
13528     /* Issue ICS rematch command */
13529     if (appData.icsActive) {
13530         SendToICS(ics_prefix);
13531         SendToICS("rematch\n");
13532     }
13533 }
13534
13535 void
13536 CallFlagEvent()
13537 {
13538     /* Call your opponent's flag (claim a win on time) */
13539     if (appData.icsActive) {
13540         SendToICS(ics_prefix);
13541         SendToICS("flag\n");
13542     } else {
13543         switch (gameMode) {
13544           default:
13545             return;
13546           case MachinePlaysWhite:
13547             if (whiteFlag) {
13548                 if (blackFlag)
13549                   GameEnds(GameIsDrawn, "Both players ran out of time",
13550                            GE_PLAYER);
13551                 else
13552                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13553             } else {
13554                 DisplayError(_("Your opponent is not out of time"), 0);
13555             }
13556             break;
13557           case MachinePlaysBlack:
13558             if (blackFlag) {
13559                 if (whiteFlag)
13560                   GameEnds(GameIsDrawn, "Both players ran out of time",
13561                            GE_PLAYER);
13562                 else
13563                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13564             } else {
13565                 DisplayError(_("Your opponent is not out of time"), 0);
13566             }
13567             break;
13568         }
13569     }
13570 }
13571
13572 void
13573 ClockClick(int which)
13574 {       // [HGM] code moved to back-end from winboard.c
13575         if(which) { // black clock
13576           if (gameMode == EditPosition || gameMode == IcsExamining) {
13577             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13578             SetBlackToPlayEvent();
13579           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13580           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13581           } else if (shiftKey) {
13582             AdjustClock(which, -1);
13583           } else if (gameMode == IcsPlayingWhite ||
13584                      gameMode == MachinePlaysBlack) {
13585             CallFlagEvent();
13586           }
13587         } else { // white clock
13588           if (gameMode == EditPosition || gameMode == IcsExamining) {
13589             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13590             SetWhiteToPlayEvent();
13591           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13592           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13593           } else if (shiftKey) {
13594             AdjustClock(which, -1);
13595           } else if (gameMode == IcsPlayingBlack ||
13596                    gameMode == MachinePlaysWhite) {
13597             CallFlagEvent();
13598           }
13599         }
13600 }
13601
13602 void
13603 DrawEvent()
13604 {
13605     /* Offer draw or accept pending draw offer from opponent */
13606
13607     if (appData.icsActive) {
13608         /* Note: tournament rules require draw offers to be
13609            made after you make your move but before you punch
13610            your clock.  Currently ICS doesn't let you do that;
13611            instead, you immediately punch your clock after making
13612            a move, but you can offer a draw at any time. */
13613
13614         SendToICS(ics_prefix);
13615         SendToICS("draw\n");
13616         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13617     } else if (cmailMsgLoaded) {
13618         if (currentMove == cmailOldMove &&
13619             commentList[cmailOldMove] != NULL &&
13620             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13621                    "Black offers a draw" : "White offers a draw")) {
13622             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13624         } else if (currentMove == cmailOldMove + 1) {
13625             char *offer = WhiteOnMove(cmailOldMove) ?
13626               "White offers a draw" : "Black offers a draw";
13627             AppendComment(currentMove, offer, TRUE);
13628             DisplayComment(currentMove - 1, offer);
13629             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13630         } else {
13631             DisplayError(_("You must make your move before offering a draw"), 0);
13632             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13633         }
13634     } else if (first.offeredDraw) {
13635         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13636     } else {
13637         if (first.sendDrawOffers) {
13638             SendToProgram("draw\n", &first);
13639             userOfferedDraw = TRUE;
13640         }
13641     }
13642 }
13643
13644 void
13645 AdjournEvent()
13646 {
13647     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13648
13649     if (appData.icsActive) {
13650         SendToICS(ics_prefix);
13651         SendToICS("adjourn\n");
13652     } else {
13653         /* Currently GNU Chess doesn't offer or accept Adjourns */
13654     }
13655 }
13656
13657
13658 void
13659 AbortEvent()
13660 {
13661     /* Offer Abort or accept pending Abort offer from opponent */
13662
13663     if (appData.icsActive) {
13664         SendToICS(ics_prefix);
13665         SendToICS("abort\n");
13666     } else {
13667         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13668     }
13669 }
13670
13671 void
13672 ResignEvent()
13673 {
13674     /* Resign.  You can do this even if it's not your turn. */
13675
13676     if (appData.icsActive) {
13677         SendToICS(ics_prefix);
13678         SendToICS("resign\n");
13679     } else {
13680         switch (gameMode) {
13681           case MachinePlaysWhite:
13682             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13683             break;
13684           case MachinePlaysBlack:
13685             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13686             break;
13687           case EditGame:
13688             if (cmailMsgLoaded) {
13689                 TruncateGame();
13690                 if (WhiteOnMove(cmailOldMove)) {
13691                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13692                 } else {
13693                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13694                 }
13695                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13696             }
13697             break;
13698           default:
13699             break;
13700         }
13701     }
13702 }
13703
13704
13705 void
13706 StopObservingEvent()
13707 {
13708     /* Stop observing current games */
13709     SendToICS(ics_prefix);
13710     SendToICS("unobserve\n");
13711 }
13712
13713 void
13714 StopExaminingEvent()
13715 {
13716     /* Stop observing current game */
13717     SendToICS(ics_prefix);
13718     SendToICS("unexamine\n");
13719 }
13720
13721 void
13722 ForwardInner(target)
13723      int target;
13724 {
13725     int limit;
13726
13727     if (appData.debugMode)
13728         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13729                 target, currentMove, forwardMostMove);
13730
13731     if (gameMode == EditPosition)
13732       return;
13733
13734     if (gameMode == PlayFromGameFile && !pausing)
13735       PauseEvent();
13736
13737     if (gameMode == IcsExamining && pausing)
13738       limit = pauseExamForwardMostMove;
13739     else
13740       limit = forwardMostMove;
13741
13742     if (target > limit) target = limit;
13743
13744     if (target > 0 && moveList[target - 1][0]) {
13745         int fromX, fromY, toX, toY;
13746         toX = moveList[target - 1][2] - AAA;
13747         toY = moveList[target - 1][3] - ONE;
13748         if (moveList[target - 1][1] == '@') {
13749             if (appData.highlightLastMove) {
13750                 SetHighlights(-1, -1, toX, toY);
13751             }
13752         } else {
13753             fromX = moveList[target - 1][0] - AAA;
13754             fromY = moveList[target - 1][1] - ONE;
13755             if (target == currentMove + 1) {
13756                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13757             }
13758             if (appData.highlightLastMove) {
13759                 SetHighlights(fromX, fromY, toX, toY);
13760             }
13761         }
13762     }
13763     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13764         gameMode == Training || gameMode == PlayFromGameFile ||
13765         gameMode == AnalyzeFile) {
13766         while (currentMove < target) {
13767             SendMoveToProgram(currentMove++, &first);
13768         }
13769     } else {
13770         currentMove = target;
13771     }
13772
13773     if (gameMode == EditGame || gameMode == EndOfGame) {
13774         whiteTimeRemaining = timeRemaining[0][currentMove];
13775         blackTimeRemaining = timeRemaining[1][currentMove];
13776     }
13777     DisplayBothClocks();
13778     DisplayMove(currentMove - 1);
13779     DrawPosition(FALSE, boards[currentMove]);
13780     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13781     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13782         DisplayComment(currentMove - 1, commentList[currentMove]);
13783     }
13784     DisplayBook(currentMove);
13785 }
13786
13787
13788 void
13789 ForwardEvent()
13790 {
13791     if (gameMode == IcsExamining && !pausing) {
13792         SendToICS(ics_prefix);
13793         SendToICS("forward\n");
13794     } else {
13795         ForwardInner(currentMove + 1);
13796     }
13797 }
13798
13799 void
13800 ToEndEvent()
13801 {
13802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13803         /* to optimze, we temporarily turn off analysis mode while we feed
13804          * the remaining moves to the engine. Otherwise we get analysis output
13805          * after each move.
13806          */
13807         if (first.analysisSupport) {
13808           SendToProgram("exit\nforce\n", &first);
13809           first.analyzing = FALSE;
13810         }
13811     }
13812
13813     if (gameMode == IcsExamining && !pausing) {
13814         SendToICS(ics_prefix);
13815         SendToICS("forward 999999\n");
13816     } else {
13817         ForwardInner(forwardMostMove);
13818     }
13819
13820     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13821         /* we have fed all the moves, so reactivate analysis mode */
13822         SendToProgram("analyze\n", &first);
13823         first.analyzing = TRUE;
13824         /*first.maybeThinking = TRUE;*/
13825         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13826     }
13827 }
13828
13829 void
13830 BackwardInner(target)
13831      int target;
13832 {
13833     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13834
13835     if (appData.debugMode)
13836         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13837                 target, currentMove, forwardMostMove);
13838
13839     if (gameMode == EditPosition) return;
13840     if (currentMove <= backwardMostMove) {
13841         ClearHighlights();
13842         DrawPosition(full_redraw, boards[currentMove]);
13843         return;
13844     }
13845     if (gameMode == PlayFromGameFile && !pausing)
13846       PauseEvent();
13847
13848     if (moveList[target][0]) {
13849         int fromX, fromY, toX, toY;
13850         toX = moveList[target][2] - AAA;
13851         toY = moveList[target][3] - ONE;
13852         if (moveList[target][1] == '@') {
13853             if (appData.highlightLastMove) {
13854                 SetHighlights(-1, -1, toX, toY);
13855             }
13856         } else {
13857             fromX = moveList[target][0] - AAA;
13858             fromY = moveList[target][1] - ONE;
13859             if (target == currentMove - 1) {
13860                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13861             }
13862             if (appData.highlightLastMove) {
13863                 SetHighlights(fromX, fromY, toX, toY);
13864             }
13865         }
13866     }
13867     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13868         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13869         while (currentMove > target) {
13870             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
13871                 // null move cannot be undone. Reload program with move history before it.
13872                 int i;
13873                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
13874                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
13875                 }
13876                 SendBoard(&first, i); 
13877                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
13878                 break;
13879             }
13880             SendToProgram("undo\n", &first);
13881             currentMove--;
13882         }
13883     } else {
13884         currentMove = target;
13885     }
13886
13887     if (gameMode == EditGame || gameMode == EndOfGame) {
13888         whiteTimeRemaining = timeRemaining[0][currentMove];
13889         blackTimeRemaining = timeRemaining[1][currentMove];
13890     }
13891     DisplayBothClocks();
13892     DisplayMove(currentMove - 1);
13893     DrawPosition(full_redraw, boards[currentMove]);
13894     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13895     // [HGM] PV info: routine tests if comment empty
13896     DisplayComment(currentMove - 1, commentList[currentMove]);
13897     DisplayBook(currentMove);
13898 }
13899
13900 void
13901 BackwardEvent()
13902 {
13903     if (gameMode == IcsExamining && !pausing) {
13904         SendToICS(ics_prefix);
13905         SendToICS("backward\n");
13906     } else {
13907         BackwardInner(currentMove - 1);
13908     }
13909 }
13910
13911 void
13912 ToStartEvent()
13913 {
13914     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13915         /* to optimize, we temporarily turn off analysis mode while we undo
13916          * all the moves. Otherwise we get analysis output after each undo.
13917          */
13918         if (first.analysisSupport) {
13919           SendToProgram("exit\nforce\n", &first);
13920           first.analyzing = FALSE;
13921         }
13922     }
13923
13924     if (gameMode == IcsExamining && !pausing) {
13925         SendToICS(ics_prefix);
13926         SendToICS("backward 999999\n");
13927     } else {
13928         BackwardInner(backwardMostMove);
13929     }
13930
13931     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13932         /* we have fed all the moves, so reactivate analysis mode */
13933         SendToProgram("analyze\n", &first);
13934         first.analyzing = TRUE;
13935         /*first.maybeThinking = TRUE;*/
13936         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13937     }
13938 }
13939
13940 void
13941 ToNrEvent(int to)
13942 {
13943   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13944   if (to >= forwardMostMove) to = forwardMostMove;
13945   if (to <= backwardMostMove) to = backwardMostMove;
13946   if (to < currentMove) {
13947     BackwardInner(to);
13948   } else {
13949     ForwardInner(to);
13950   }
13951 }
13952
13953 void
13954 RevertEvent(Boolean annotate)
13955 {
13956     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13957         return;
13958     }
13959     if (gameMode != IcsExamining) {
13960         DisplayError(_("You are not examining a game"), 0);
13961         return;
13962     }
13963     if (pausing) {
13964         DisplayError(_("You can't revert while pausing"), 0);
13965         return;
13966     }
13967     SendToICS(ics_prefix);
13968     SendToICS("revert\n");
13969 }
13970
13971 void
13972 RetractMoveEvent()
13973 {
13974     switch (gameMode) {
13975       case MachinePlaysWhite:
13976       case MachinePlaysBlack:
13977         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13978             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13979             return;
13980         }
13981         if (forwardMostMove < 2) return;
13982         currentMove = forwardMostMove = forwardMostMove - 2;
13983         whiteTimeRemaining = timeRemaining[0][currentMove];
13984         blackTimeRemaining = timeRemaining[1][currentMove];
13985         DisplayBothClocks();
13986         DisplayMove(currentMove - 1);
13987         ClearHighlights();/*!! could figure this out*/
13988         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13989         SendToProgram("remove\n", &first);
13990         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13991         break;
13992
13993       case BeginningOfGame:
13994       default:
13995         break;
13996
13997       case IcsPlayingWhite:
13998       case IcsPlayingBlack:
13999         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14000             SendToICS(ics_prefix);
14001             SendToICS("takeback 2\n");
14002         } else {
14003             SendToICS(ics_prefix);
14004             SendToICS("takeback 1\n");
14005         }
14006         break;
14007     }
14008 }
14009
14010 void
14011 MoveNowEvent()
14012 {
14013     ChessProgramState *cps;
14014
14015     switch (gameMode) {
14016       case MachinePlaysWhite:
14017         if (!WhiteOnMove(forwardMostMove)) {
14018             DisplayError(_("It is your turn"), 0);
14019             return;
14020         }
14021         cps = &first;
14022         break;
14023       case MachinePlaysBlack:
14024         if (WhiteOnMove(forwardMostMove)) {
14025             DisplayError(_("It is your turn"), 0);
14026             return;
14027         }
14028         cps = &first;
14029         break;
14030       case TwoMachinesPlay:
14031         if (WhiteOnMove(forwardMostMove) ==
14032             (first.twoMachinesColor[0] == 'w')) {
14033             cps = &first;
14034         } else {
14035             cps = &second;
14036         }
14037         break;
14038       case BeginningOfGame:
14039       default:
14040         return;
14041     }
14042     SendToProgram("?\n", cps);
14043 }
14044
14045 void
14046 TruncateGameEvent()
14047 {
14048     EditGameEvent();
14049     if (gameMode != EditGame) return;
14050     TruncateGame();
14051 }
14052
14053 void
14054 TruncateGame()
14055 {
14056     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14057     if (forwardMostMove > currentMove) {
14058         if (gameInfo.resultDetails != NULL) {
14059             free(gameInfo.resultDetails);
14060             gameInfo.resultDetails = NULL;
14061             gameInfo.result = GameUnfinished;
14062         }
14063         forwardMostMove = currentMove;
14064         HistorySet(parseList, backwardMostMove, forwardMostMove,
14065                    currentMove-1);
14066     }
14067 }
14068
14069 void
14070 HintEvent()
14071 {
14072     if (appData.noChessProgram) return;
14073     switch (gameMode) {
14074       case MachinePlaysWhite:
14075         if (WhiteOnMove(forwardMostMove)) {
14076             DisplayError(_("Wait until your turn"), 0);
14077             return;
14078         }
14079         break;
14080       case BeginningOfGame:
14081       case MachinePlaysBlack:
14082         if (!WhiteOnMove(forwardMostMove)) {
14083             DisplayError(_("Wait until your turn"), 0);
14084             return;
14085         }
14086         break;
14087       default:
14088         DisplayError(_("No hint available"), 0);
14089         return;
14090     }
14091     SendToProgram("hint\n", &first);
14092     hintRequested = TRUE;
14093 }
14094
14095 void
14096 BookEvent()
14097 {
14098     if (appData.noChessProgram) return;
14099     switch (gameMode) {
14100       case MachinePlaysWhite:
14101         if (WhiteOnMove(forwardMostMove)) {
14102             DisplayError(_("Wait until your turn"), 0);
14103             return;
14104         }
14105         break;
14106       case BeginningOfGame:
14107       case MachinePlaysBlack:
14108         if (!WhiteOnMove(forwardMostMove)) {
14109             DisplayError(_("Wait until your turn"), 0);
14110             return;
14111         }
14112         break;
14113       case EditPosition:
14114         EditPositionDone(TRUE);
14115         break;
14116       case TwoMachinesPlay:
14117         return;
14118       default:
14119         break;
14120     }
14121     SendToProgram("bk\n", &first);
14122     bookOutput[0] = NULLCHAR;
14123     bookRequested = TRUE;
14124 }
14125
14126 void
14127 AboutGameEvent()
14128 {
14129     char *tags = PGNTags(&gameInfo);
14130     TagsPopUp(tags, CmailMsg());
14131     free(tags);
14132 }
14133
14134 /* end button procedures */
14135
14136 void
14137 PrintPosition(fp, move)
14138      FILE *fp;
14139      int move;
14140 {
14141     int i, j;
14142
14143     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14144         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14145             char c = PieceToChar(boards[move][i][j]);
14146             fputc(c == 'x' ? '.' : c, fp);
14147             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14148         }
14149     }
14150     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14151       fprintf(fp, "white to play\n");
14152     else
14153       fprintf(fp, "black to play\n");
14154 }
14155
14156 void
14157 PrintOpponents(fp)
14158      FILE *fp;
14159 {
14160     if (gameInfo.white != NULL) {
14161         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14162     } else {
14163         fprintf(fp, "\n");
14164     }
14165 }
14166
14167 /* Find last component of program's own name, using some heuristics */
14168 void
14169 TidyProgramName(prog, host, buf)
14170      char *prog, *host, buf[MSG_SIZ];
14171 {
14172     char *p, *q;
14173     int local = (strcmp(host, "localhost") == 0);
14174     while (!local && (p = strchr(prog, ';')) != NULL) {
14175         p++;
14176         while (*p == ' ') p++;
14177         prog = p;
14178     }
14179     if (*prog == '"' || *prog == '\'') {
14180         q = strchr(prog + 1, *prog);
14181     } else {
14182         q = strchr(prog, ' ');
14183     }
14184     if (q == NULL) q = prog + strlen(prog);
14185     p = q;
14186     while (p >= prog && *p != '/' && *p != '\\') p--;
14187     p++;
14188     if(p == prog && *p == '"') p++;
14189     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14190     memcpy(buf, p, q - p);
14191     buf[q - p] = NULLCHAR;
14192     if (!local) {
14193         strcat(buf, "@");
14194         strcat(buf, host);
14195     }
14196 }
14197
14198 char *
14199 TimeControlTagValue()
14200 {
14201     char buf[MSG_SIZ];
14202     if (!appData.clockMode) {
14203       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14204     } else if (movesPerSession > 0) {
14205       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14206     } else if (timeIncrement == 0) {
14207       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14208     } else {
14209       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14210     }
14211     return StrSave(buf);
14212 }
14213
14214 void
14215 SetGameInfo()
14216 {
14217     /* This routine is used only for certain modes */
14218     VariantClass v = gameInfo.variant;
14219     ChessMove r = GameUnfinished;
14220     char *p = NULL;
14221
14222     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14223         r = gameInfo.result;
14224         p = gameInfo.resultDetails;
14225         gameInfo.resultDetails = NULL;
14226     }
14227     ClearGameInfo(&gameInfo);
14228     gameInfo.variant = v;
14229
14230     switch (gameMode) {
14231       case MachinePlaysWhite:
14232         gameInfo.event = StrSave( appData.pgnEventHeader );
14233         gameInfo.site = StrSave(HostName());
14234         gameInfo.date = PGNDate();
14235         gameInfo.round = StrSave("-");
14236         gameInfo.white = StrSave(first.tidy);
14237         gameInfo.black = StrSave(UserName());
14238         gameInfo.timeControl = TimeControlTagValue();
14239         break;
14240
14241       case MachinePlaysBlack:
14242         gameInfo.event = StrSave( appData.pgnEventHeader );
14243         gameInfo.site = StrSave(HostName());
14244         gameInfo.date = PGNDate();
14245         gameInfo.round = StrSave("-");
14246         gameInfo.white = StrSave(UserName());
14247         gameInfo.black = StrSave(first.tidy);
14248         gameInfo.timeControl = TimeControlTagValue();
14249         break;
14250
14251       case TwoMachinesPlay:
14252         gameInfo.event = StrSave( appData.pgnEventHeader );
14253         gameInfo.site = StrSave(HostName());
14254         gameInfo.date = PGNDate();
14255         if (roundNr > 0) {
14256             char buf[MSG_SIZ];
14257             snprintf(buf, MSG_SIZ, "%d", roundNr);
14258             gameInfo.round = StrSave(buf);
14259         } else {
14260             gameInfo.round = StrSave("-");
14261         }
14262         if (first.twoMachinesColor[0] == 'w') {
14263             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14264             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14265         } else {
14266             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14267             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14268         }
14269         gameInfo.timeControl = TimeControlTagValue();
14270         break;
14271
14272       case EditGame:
14273         gameInfo.event = StrSave("Edited game");
14274         gameInfo.site = StrSave(HostName());
14275         gameInfo.date = PGNDate();
14276         gameInfo.round = StrSave("-");
14277         gameInfo.white = StrSave("-");
14278         gameInfo.black = StrSave("-");
14279         gameInfo.result = r;
14280         gameInfo.resultDetails = p;
14281         break;
14282
14283       case EditPosition:
14284         gameInfo.event = StrSave("Edited position");
14285         gameInfo.site = StrSave(HostName());
14286         gameInfo.date = PGNDate();
14287         gameInfo.round = StrSave("-");
14288         gameInfo.white = StrSave("-");
14289         gameInfo.black = StrSave("-");
14290         break;
14291
14292       case IcsPlayingWhite:
14293       case IcsPlayingBlack:
14294       case IcsObserving:
14295       case IcsExamining:
14296         break;
14297
14298       case PlayFromGameFile:
14299         gameInfo.event = StrSave("Game from non-PGN file");
14300         gameInfo.site = StrSave(HostName());
14301         gameInfo.date = PGNDate();
14302         gameInfo.round = StrSave("-");
14303         gameInfo.white = StrSave("?");
14304         gameInfo.black = StrSave("?");
14305         break;
14306
14307       default:
14308         break;
14309     }
14310 }
14311
14312 void
14313 ReplaceComment(index, text)
14314      int index;
14315      char *text;
14316 {
14317     int len;
14318     char *p;
14319     float score;
14320
14321     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14322        pvInfoList[index-1].depth == len &&
14323        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14324        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14325     while (*text == '\n') text++;
14326     len = strlen(text);
14327     while (len > 0 && text[len - 1] == '\n') len--;
14328
14329     if (commentList[index] != NULL)
14330       free(commentList[index]);
14331
14332     if (len == 0) {
14333         commentList[index] = NULL;
14334         return;
14335     }
14336   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14337       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14338       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14339     commentList[index] = (char *) malloc(len + 2);
14340     strncpy(commentList[index], text, len);
14341     commentList[index][len] = '\n';
14342     commentList[index][len + 1] = NULLCHAR;
14343   } else {
14344     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14345     char *p;
14346     commentList[index] = (char *) malloc(len + 7);
14347     safeStrCpy(commentList[index], "{\n", 3);
14348     safeStrCpy(commentList[index]+2, text, len+1);
14349     commentList[index][len+2] = NULLCHAR;
14350     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14351     strcat(commentList[index], "\n}\n");
14352   }
14353 }
14354
14355 void
14356 CrushCRs(text)
14357      char *text;
14358 {
14359   char *p = text;
14360   char *q = text;
14361   char ch;
14362
14363   do {
14364     ch = *p++;
14365     if (ch == '\r') continue;
14366     *q++ = ch;
14367   } while (ch != '\0');
14368 }
14369
14370 void
14371 AppendComment(index, text, addBraces)
14372      int index;
14373      char *text;
14374      Boolean addBraces; // [HGM] braces: tells if we should add {}
14375 {
14376     int oldlen, len;
14377     char *old;
14378
14379 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14380     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14381
14382     CrushCRs(text);
14383     while (*text == '\n') text++;
14384     len = strlen(text);
14385     while (len > 0 && text[len - 1] == '\n') len--;
14386
14387     if (len == 0) return;
14388
14389     if (commentList[index] != NULL) {
14390         old = commentList[index];
14391         oldlen = strlen(old);
14392         while(commentList[index][oldlen-1] ==  '\n')
14393           commentList[index][--oldlen] = NULLCHAR;
14394         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14395         safeStrCpy(commentList[index], old, oldlen + len + 6);
14396         free(old);
14397         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14398         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14399           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14400           while (*text == '\n') { text++; len--; }
14401           commentList[index][--oldlen] = NULLCHAR;
14402       }
14403         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14404         else          strcat(commentList[index], "\n");
14405         strcat(commentList[index], text);
14406         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14407         else          strcat(commentList[index], "\n");
14408     } else {
14409         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14410         if(addBraces)
14411           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14412         else commentList[index][0] = NULLCHAR;
14413         strcat(commentList[index], text);
14414         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14415         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14416     }
14417 }
14418
14419 static char * FindStr( char * text, char * sub_text )
14420 {
14421     char * result = strstr( text, sub_text );
14422
14423     if( result != NULL ) {
14424         result += strlen( sub_text );
14425     }
14426
14427     return result;
14428 }
14429
14430 /* [AS] Try to extract PV info from PGN comment */
14431 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14432 char *GetInfoFromComment( int index, char * text )
14433 {
14434     char * sep = text, *p;
14435
14436     if( text != NULL && index > 0 ) {
14437         int score = 0;
14438         int depth = 0;
14439         int time = -1, sec = 0, deci;
14440         char * s_eval = FindStr( text, "[%eval " );
14441         char * s_emt = FindStr( text, "[%emt " );
14442
14443         if( s_eval != NULL || s_emt != NULL ) {
14444             /* New style */
14445             char delim;
14446
14447             if( s_eval != NULL ) {
14448                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14449                     return text;
14450                 }
14451
14452                 if( delim != ']' ) {
14453                     return text;
14454                 }
14455             }
14456
14457             if( s_emt != NULL ) {
14458             }
14459                 return text;
14460         }
14461         else {
14462             /* We expect something like: [+|-]nnn.nn/dd */
14463             int score_lo = 0;
14464
14465             if(*text != '{') return text; // [HGM] braces: must be normal comment
14466
14467             sep = strchr( text, '/' );
14468             if( sep == NULL || sep < (text+4) ) {
14469                 return text;
14470             }
14471
14472             p = text;
14473             if(p[1] == '(') { // comment starts with PV
14474                p = strchr(p, ')'); // locate end of PV
14475                if(p == NULL || sep < p+5) return text;
14476                // at this point we have something like "{(.*) +0.23/6 ..."
14477                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14478                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14479                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14480             }
14481             time = -1; sec = -1; deci = -1;
14482             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14483                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14484                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14485                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14486                 return text;
14487             }
14488
14489             if( score_lo < 0 || score_lo >= 100 ) {
14490                 return text;
14491             }
14492
14493             if(sec >= 0) time = 600*time + 10*sec; else
14494             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14495
14496             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14497
14498             /* [HGM] PV time: now locate end of PV info */
14499             while( *++sep >= '0' && *sep <= '9'); // strip depth
14500             if(time >= 0)
14501             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14502             if(sec >= 0)
14503             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14504             if(deci >= 0)
14505             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14506             while(*sep == ' ') sep++;
14507         }
14508
14509         if( depth <= 0 ) {
14510             return text;
14511         }
14512
14513         if( time < 0 ) {
14514             time = -1;
14515         }
14516
14517         pvInfoList[index-1].depth = depth;
14518         pvInfoList[index-1].score = score;
14519         pvInfoList[index-1].time  = 10*time; // centi-sec
14520         if(*sep == '}') *sep = 0; else *--sep = '{';
14521         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14522     }
14523     return sep;
14524 }
14525
14526 void
14527 SendToProgram(message, cps)
14528      char *message;
14529      ChessProgramState *cps;
14530 {
14531     int count, outCount, error;
14532     char buf[MSG_SIZ];
14533
14534     if (cps->pr == NULL) return;
14535     Attention(cps);
14536
14537     if (appData.debugMode) {
14538         TimeMark now;
14539         GetTimeMark(&now);
14540         fprintf(debugFP, "%ld >%-6s: %s",
14541                 SubtractTimeMarks(&now, &programStartTime),
14542                 cps->which, message);
14543     }
14544
14545     count = strlen(message);
14546     outCount = OutputToProcess(cps->pr, message, count, &error);
14547     if (outCount < count && !exiting
14548                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14549       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14550       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14551         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14552             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14553                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14554                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14555                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14556             } else {
14557                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14558                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14559                 gameInfo.result = res;
14560             }
14561             gameInfo.resultDetails = StrSave(buf);
14562         }
14563         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14564         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14565     }
14566 }
14567
14568 void
14569 ReceiveFromProgram(isr, closure, message, count, error)
14570      InputSourceRef isr;
14571      VOIDSTAR closure;
14572      char *message;
14573      int count;
14574      int error;
14575 {
14576     char *end_str;
14577     char buf[MSG_SIZ];
14578     ChessProgramState *cps = (ChessProgramState *)closure;
14579
14580     if (isr != cps->isr) return; /* Killed intentionally */
14581     if (count <= 0) {
14582         if (count == 0) {
14583             RemoveInputSource(cps->isr);
14584             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14585             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14586                     _(cps->which), cps->program);
14587         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14588                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14589                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14590                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14591                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14592                 } else {
14593                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14594                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14595                     gameInfo.result = res;
14596                 }
14597                 gameInfo.resultDetails = StrSave(buf);
14598             }
14599             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14600             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14601         } else {
14602             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14603                     _(cps->which), cps->program);
14604             RemoveInputSource(cps->isr);
14605
14606             /* [AS] Program is misbehaving badly... kill it */
14607             if( count == -2 ) {
14608                 DestroyChildProcess( cps->pr, 9 );
14609                 cps->pr = NoProc;
14610             }
14611
14612             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14613         }
14614         return;
14615     }
14616
14617     if ((end_str = strchr(message, '\r')) != NULL)
14618       *end_str = NULLCHAR;
14619     if ((end_str = strchr(message, '\n')) != NULL)
14620       *end_str = NULLCHAR;
14621
14622     if (appData.debugMode) {
14623         TimeMark now; int print = 1;
14624         char *quote = ""; char c; int i;
14625
14626         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14627                 char start = message[0];
14628                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14629                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14630                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14631                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14632                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14633                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14634                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14635                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14636                    sscanf(message, "hint: %c", &c)!=1 && 
14637                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14638                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14639                     print = (appData.engineComments >= 2);
14640                 }
14641                 message[0] = start; // restore original message
14642         }
14643         if(print) {
14644                 GetTimeMark(&now);
14645                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14646                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14647                         quote,
14648                         message);
14649         }
14650     }
14651
14652     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14653     if (appData.icsEngineAnalyze) {
14654         if (strstr(message, "whisper") != NULL ||
14655              strstr(message, "kibitz") != NULL ||
14656             strstr(message, "tellics") != NULL) return;
14657     }
14658
14659     HandleMachineMove(message, cps);
14660 }
14661
14662
14663 void
14664 SendTimeControl(cps, mps, tc, inc, sd, st)
14665      ChessProgramState *cps;
14666      int mps, inc, sd, st;
14667      long tc;
14668 {
14669     char buf[MSG_SIZ];
14670     int seconds;
14671
14672     if( timeControl_2 > 0 ) {
14673         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14674             tc = timeControl_2;
14675         }
14676     }
14677     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14678     inc /= cps->timeOdds;
14679     st  /= cps->timeOdds;
14680
14681     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14682
14683     if (st > 0) {
14684       /* Set exact time per move, normally using st command */
14685       if (cps->stKludge) {
14686         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14687         seconds = st % 60;
14688         if (seconds == 0) {
14689           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14690         } else {
14691           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14692         }
14693       } else {
14694         snprintf(buf, MSG_SIZ, "st %d\n", st);
14695       }
14696     } else {
14697       /* Set conventional or incremental time control, using level command */
14698       if (seconds == 0) {
14699         /* Note old gnuchess bug -- minutes:seconds used to not work.
14700            Fixed in later versions, but still avoid :seconds
14701            when seconds is 0. */
14702         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14703       } else {
14704         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14705                  seconds, inc/1000.);
14706       }
14707     }
14708     SendToProgram(buf, cps);
14709
14710     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14711     /* Orthogonally, limit search to given depth */
14712     if (sd > 0) {
14713       if (cps->sdKludge) {
14714         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14715       } else {
14716         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14717       }
14718       SendToProgram(buf, cps);
14719     }
14720
14721     if(cps->nps >= 0) { /* [HGM] nps */
14722         if(cps->supportsNPS == FALSE)
14723           cps->nps = -1; // don't use if engine explicitly says not supported!
14724         else {
14725           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14726           SendToProgram(buf, cps);
14727         }
14728     }
14729 }
14730
14731 ChessProgramState *WhitePlayer()
14732 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14733 {
14734     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14735        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14736         return &second;
14737     return &first;
14738 }
14739
14740 void
14741 SendTimeRemaining(cps, machineWhite)
14742      ChessProgramState *cps;
14743      int /*boolean*/ machineWhite;
14744 {
14745     char message[MSG_SIZ];
14746     long time, otime;
14747
14748     /* Note: this routine must be called when the clocks are stopped
14749        or when they have *just* been set or switched; otherwise
14750        it will be off by the time since the current tick started.
14751     */
14752     if (machineWhite) {
14753         time = whiteTimeRemaining / 10;
14754         otime = blackTimeRemaining / 10;
14755     } else {
14756         time = blackTimeRemaining / 10;
14757         otime = whiteTimeRemaining / 10;
14758     }
14759     /* [HGM] translate opponent's time by time-odds factor */
14760     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14761     if (appData.debugMode) {
14762         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14763     }
14764
14765     if (time <= 0) time = 1;
14766     if (otime <= 0) otime = 1;
14767
14768     snprintf(message, MSG_SIZ, "time %ld\n", time);
14769     SendToProgram(message, cps);
14770
14771     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14772     SendToProgram(message, cps);
14773 }
14774
14775 int
14776 BoolFeature(p, name, loc, cps)
14777      char **p;
14778      char *name;
14779      int *loc;
14780      ChessProgramState *cps;
14781 {
14782   char buf[MSG_SIZ];
14783   int len = strlen(name);
14784   int val;
14785
14786   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14787     (*p) += len + 1;
14788     sscanf(*p, "%d", &val);
14789     *loc = (val != 0);
14790     while (**p && **p != ' ')
14791       (*p)++;
14792     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14793     SendToProgram(buf, cps);
14794     return TRUE;
14795   }
14796   return FALSE;
14797 }
14798
14799 int
14800 IntFeature(p, name, loc, cps)
14801      char **p;
14802      char *name;
14803      int *loc;
14804      ChessProgramState *cps;
14805 {
14806   char buf[MSG_SIZ];
14807   int len = strlen(name);
14808   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14809     (*p) += len + 1;
14810     sscanf(*p, "%d", loc);
14811     while (**p && **p != ' ') (*p)++;
14812     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14813     SendToProgram(buf, cps);
14814     return TRUE;
14815   }
14816   return FALSE;
14817 }
14818
14819 int
14820 StringFeature(p, name, loc, cps)
14821      char **p;
14822      char *name;
14823      char loc[];
14824      ChessProgramState *cps;
14825 {
14826   char buf[MSG_SIZ];
14827   int len = strlen(name);
14828   if (strncmp((*p), name, len) == 0
14829       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14830     (*p) += len + 2;
14831     sscanf(*p, "%[^\"]", loc);
14832     while (**p && **p != '\"') (*p)++;
14833     if (**p == '\"') (*p)++;
14834     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14835     SendToProgram(buf, cps);
14836     return TRUE;
14837   }
14838   return FALSE;
14839 }
14840
14841 int
14842 ParseOption(Option *opt, ChessProgramState *cps)
14843 // [HGM] options: process the string that defines an engine option, and determine
14844 // name, type, default value, and allowed value range
14845 {
14846         char *p, *q, buf[MSG_SIZ];
14847         int n, min = (-1)<<31, max = 1<<31, def;
14848
14849         if(p = strstr(opt->name, " -spin ")) {
14850             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14851             if(max < min) max = min; // enforce consistency
14852             if(def < min) def = min;
14853             if(def > max) def = max;
14854             opt->value = def;
14855             opt->min = min;
14856             opt->max = max;
14857             opt->type = Spin;
14858         } else if((p = strstr(opt->name, " -slider "))) {
14859             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14860             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14861             if(max < min) max = min; // enforce consistency
14862             if(def < min) def = min;
14863             if(def > max) def = max;
14864             opt->value = def;
14865             opt->min = min;
14866             opt->max = max;
14867             opt->type = Spin; // Slider;
14868         } else if((p = strstr(opt->name, " -string "))) {
14869             opt->textValue = p+9;
14870             opt->type = TextBox;
14871         } else if((p = strstr(opt->name, " -file "))) {
14872             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14873             opt->textValue = p+7;
14874             opt->type = FileName; // FileName;
14875         } else if((p = strstr(opt->name, " -path "))) {
14876             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14877             opt->textValue = p+7;
14878             opt->type = PathName; // PathName;
14879         } else if(p = strstr(opt->name, " -check ")) {
14880             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14881             opt->value = (def != 0);
14882             opt->type = CheckBox;
14883         } else if(p = strstr(opt->name, " -combo ")) {
14884             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14885             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14886             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14887             opt->value = n = 0;
14888             while(q = StrStr(q, " /// ")) {
14889                 n++; *q = 0;    // count choices, and null-terminate each of them
14890                 q += 5;
14891                 if(*q == '*') { // remember default, which is marked with * prefix
14892                     q++;
14893                     opt->value = n;
14894                 }
14895                 cps->comboList[cps->comboCnt++] = q;
14896             }
14897             cps->comboList[cps->comboCnt++] = NULL;
14898             opt->max = n + 1;
14899             opt->type = ComboBox;
14900         } else if(p = strstr(opt->name, " -button")) {
14901             opt->type = Button;
14902         } else if(p = strstr(opt->name, " -save")) {
14903             opt->type = SaveButton;
14904         } else return FALSE;
14905         *p = 0; // terminate option name
14906         // now look if the command-line options define a setting for this engine option.
14907         if(cps->optionSettings && cps->optionSettings[0])
14908             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14909         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14910           snprintf(buf, MSG_SIZ, "option %s", p);
14911                 if(p = strstr(buf, ",")) *p = 0;
14912                 if(q = strchr(buf, '=')) switch(opt->type) {
14913                     case ComboBox:
14914                         for(n=0; n<opt->max; n++)
14915                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14916                         break;
14917                     case TextBox:
14918                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14919                         break;
14920                     case Spin:
14921                     case CheckBox:
14922                         opt->value = atoi(q+1);
14923                     default:
14924                         break;
14925                 }
14926                 strcat(buf, "\n");
14927                 SendToProgram(buf, cps);
14928         }
14929         return TRUE;
14930 }
14931
14932 void
14933 FeatureDone(cps, val)
14934      ChessProgramState* cps;
14935      int val;
14936 {
14937   DelayedEventCallback cb = GetDelayedEvent();
14938   if ((cb == InitBackEnd3 && cps == &first) ||
14939       (cb == SettingsMenuIfReady && cps == &second) ||
14940       (cb == LoadEngine) ||
14941       (cb == TwoMachinesEventIfReady)) {
14942     CancelDelayedEvent();
14943     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14944   }
14945   cps->initDone = val;
14946 }
14947
14948 /* Parse feature command from engine */
14949 void
14950 ParseFeatures(args, cps)
14951      char* args;
14952      ChessProgramState *cps;
14953 {
14954   char *p = args;
14955   char *q;
14956   int val;
14957   char buf[MSG_SIZ];
14958
14959   for (;;) {
14960     while (*p == ' ') p++;
14961     if (*p == NULLCHAR) return;
14962
14963     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14964     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14965     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14966     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14967     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14968     if (BoolFeature(&p, "reuse", &val, cps)) {
14969       /* Engine can disable reuse, but can't enable it if user said no */
14970       if (!val) cps->reuse = FALSE;
14971       continue;
14972     }
14973     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14974     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14975       if (gameMode == TwoMachinesPlay) {
14976         DisplayTwoMachinesTitle();
14977       } else {
14978         DisplayTitle("");
14979       }
14980       continue;
14981     }
14982     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14983     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14984     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14985     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14986     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14987     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14988     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14989     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14990     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14991     if (IntFeature(&p, "done", &val, cps)) {
14992       FeatureDone(cps, val);
14993       continue;
14994     }
14995     /* Added by Tord: */
14996     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14997     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14998     /* End of additions by Tord */
14999
15000     /* [HGM] added features: */
15001     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15002     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15003     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15004     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15005     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15006     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15007     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15008         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15009           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15010             SendToProgram(buf, cps);
15011             continue;
15012         }
15013         if(cps->nrOptions >= MAX_OPTIONS) {
15014             cps->nrOptions--;
15015             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15016             DisplayError(buf, 0);
15017         }
15018         continue;
15019     }
15020     /* End of additions by HGM */
15021
15022     /* unknown feature: complain and skip */
15023     q = p;
15024     while (*q && *q != '=') q++;
15025     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15026     SendToProgram(buf, cps);
15027     p = q;
15028     if (*p == '=') {
15029       p++;
15030       if (*p == '\"') {
15031         p++;
15032         while (*p && *p != '\"') p++;
15033         if (*p == '\"') p++;
15034       } else {
15035         while (*p && *p != ' ') p++;
15036       }
15037     }
15038   }
15039
15040 }
15041
15042 void
15043 PeriodicUpdatesEvent(newState)
15044      int newState;
15045 {
15046     if (newState == appData.periodicUpdates)
15047       return;
15048
15049     appData.periodicUpdates=newState;
15050
15051     /* Display type changes, so update it now */
15052 //    DisplayAnalysis();
15053
15054     /* Get the ball rolling again... */
15055     if (newState) {
15056         AnalysisPeriodicEvent(1);
15057         StartAnalysisClock();
15058     }
15059 }
15060
15061 void
15062 PonderNextMoveEvent(newState)
15063      int newState;
15064 {
15065     if (newState == appData.ponderNextMove) return;
15066     if (gameMode == EditPosition) EditPositionDone(TRUE);
15067     if (newState) {
15068         SendToProgram("hard\n", &first);
15069         if (gameMode == TwoMachinesPlay) {
15070             SendToProgram("hard\n", &second);
15071         }
15072     } else {
15073         SendToProgram("easy\n", &first);
15074         thinkOutput[0] = NULLCHAR;
15075         if (gameMode == TwoMachinesPlay) {
15076             SendToProgram("easy\n", &second);
15077         }
15078     }
15079     appData.ponderNextMove = newState;
15080 }
15081
15082 void
15083 NewSettingEvent(option, feature, command, value)
15084      char *command;
15085      int option, value, *feature;
15086 {
15087     char buf[MSG_SIZ];
15088
15089     if (gameMode == EditPosition) EditPositionDone(TRUE);
15090     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15091     if(feature == NULL || *feature) SendToProgram(buf, &first);
15092     if (gameMode == TwoMachinesPlay) {
15093         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15094     }
15095 }
15096
15097 void
15098 ShowThinkingEvent()
15099 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15100 {
15101     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15102     int newState = appData.showThinking
15103         // [HGM] thinking: other features now need thinking output as well
15104         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15105
15106     if (oldState == newState) return;
15107     oldState = newState;
15108     if (gameMode == EditPosition) EditPositionDone(TRUE);
15109     if (oldState) {
15110         SendToProgram("post\n", &first);
15111         if (gameMode == TwoMachinesPlay) {
15112             SendToProgram("post\n", &second);
15113         }
15114     } else {
15115         SendToProgram("nopost\n", &first);
15116         thinkOutput[0] = NULLCHAR;
15117         if (gameMode == TwoMachinesPlay) {
15118             SendToProgram("nopost\n", &second);
15119         }
15120     }
15121 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15122 }
15123
15124 void
15125 AskQuestionEvent(title, question, replyPrefix, which)
15126      char *title; char *question; char *replyPrefix; char *which;
15127 {
15128   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15129   if (pr == NoProc) return;
15130   AskQuestion(title, question, replyPrefix, pr);
15131 }
15132
15133 void
15134 TypeInEvent(char firstChar)
15135 {
15136     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15137         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15138         gameMode == AnalyzeMode || gameMode == EditGame || 
15139         gameMode == EditPosition || gameMode == IcsExamining ||
15140         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15141         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15142                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15143                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15144         gameMode == Training) PopUpMoveDialog(firstChar);
15145 }
15146
15147 void
15148 TypeInDoneEvent(char *move)
15149 {
15150         Board board;
15151         int n, fromX, fromY, toX, toY;
15152         char promoChar;
15153         ChessMove moveType;
15154
15155         // [HGM] FENedit
15156         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15157                 EditPositionPasteFEN(move);
15158                 return;
15159         }
15160         // [HGM] movenum: allow move number to be typed in any mode
15161         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15162           ToNrEvent(2*n-1);
15163           return;
15164         }
15165
15166       if (gameMode != EditGame && currentMove != forwardMostMove && 
15167         gameMode != Training) {
15168         DisplayMoveError(_("Displayed move is not current"));
15169       } else {
15170         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15171           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15172         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15173         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15174           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15175           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15176         } else {
15177           DisplayMoveError(_("Could not parse move"));
15178         }
15179       }
15180 }
15181
15182 void
15183 DisplayMove(moveNumber)
15184      int moveNumber;
15185 {
15186     char message[MSG_SIZ];
15187     char res[MSG_SIZ];
15188     char cpThinkOutput[MSG_SIZ];
15189
15190     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15191
15192     if (moveNumber == forwardMostMove - 1 ||
15193         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15194
15195         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15196
15197         if (strchr(cpThinkOutput, '\n')) {
15198             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15199         }
15200     } else {
15201         *cpThinkOutput = NULLCHAR;
15202     }
15203
15204     /* [AS] Hide thinking from human user */
15205     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15206         *cpThinkOutput = NULLCHAR;
15207         if( thinkOutput[0] != NULLCHAR ) {
15208             int i;
15209
15210             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15211                 cpThinkOutput[i] = '.';
15212             }
15213             cpThinkOutput[i] = NULLCHAR;
15214             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15215         }
15216     }
15217
15218     if (moveNumber == forwardMostMove - 1 &&
15219         gameInfo.resultDetails != NULL) {
15220         if (gameInfo.resultDetails[0] == NULLCHAR) {
15221           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15222         } else {
15223           snprintf(res, MSG_SIZ, " {%s} %s",
15224                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15225         }
15226     } else {
15227         res[0] = NULLCHAR;
15228     }
15229
15230     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15231         DisplayMessage(res, cpThinkOutput);
15232     } else {
15233       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15234                 WhiteOnMove(moveNumber) ? " " : ".. ",
15235                 parseList[moveNumber], res);
15236         DisplayMessage(message, cpThinkOutput);
15237     }
15238 }
15239
15240 void
15241 DisplayComment(moveNumber, text)
15242      int moveNumber;
15243      char *text;
15244 {
15245     char title[MSG_SIZ];
15246
15247     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15248       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15249     } else {
15250       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15251               WhiteOnMove(moveNumber) ? " " : ".. ",
15252               parseList[moveNumber]);
15253     }
15254     if (text != NULL && (appData.autoDisplayComment || commentUp))
15255         CommentPopUp(title, text);
15256 }
15257
15258 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15259  * might be busy thinking or pondering.  It can be omitted if your
15260  * gnuchess is configured to stop thinking immediately on any user
15261  * input.  However, that gnuchess feature depends on the FIONREAD
15262  * ioctl, which does not work properly on some flavors of Unix.
15263  */
15264 void
15265 Attention(cps)
15266      ChessProgramState *cps;
15267 {
15268 #if ATTENTION
15269     if (!cps->useSigint) return;
15270     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15271     switch (gameMode) {
15272       case MachinePlaysWhite:
15273       case MachinePlaysBlack:
15274       case TwoMachinesPlay:
15275       case IcsPlayingWhite:
15276       case IcsPlayingBlack:
15277       case AnalyzeMode:
15278       case AnalyzeFile:
15279         /* Skip if we know it isn't thinking */
15280         if (!cps->maybeThinking) return;
15281         if (appData.debugMode)
15282           fprintf(debugFP, "Interrupting %s\n", cps->which);
15283         InterruptChildProcess(cps->pr);
15284         cps->maybeThinking = FALSE;
15285         break;
15286       default:
15287         break;
15288     }
15289 #endif /*ATTENTION*/
15290 }
15291
15292 int
15293 CheckFlags()
15294 {
15295     if (whiteTimeRemaining <= 0) {
15296         if (!whiteFlag) {
15297             whiteFlag = TRUE;
15298             if (appData.icsActive) {
15299                 if (appData.autoCallFlag &&
15300                     gameMode == IcsPlayingBlack && !blackFlag) {
15301                   SendToICS(ics_prefix);
15302                   SendToICS("flag\n");
15303                 }
15304             } else {
15305                 if (blackFlag) {
15306                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15307                 } else {
15308                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15309                     if (appData.autoCallFlag) {
15310                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15311                         return TRUE;
15312                     }
15313                 }
15314             }
15315         }
15316     }
15317     if (blackTimeRemaining <= 0) {
15318         if (!blackFlag) {
15319             blackFlag = TRUE;
15320             if (appData.icsActive) {
15321                 if (appData.autoCallFlag &&
15322                     gameMode == IcsPlayingWhite && !whiteFlag) {
15323                   SendToICS(ics_prefix);
15324                   SendToICS("flag\n");
15325                 }
15326             } else {
15327                 if (whiteFlag) {
15328                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15329                 } else {
15330                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15331                     if (appData.autoCallFlag) {
15332                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15333                         return TRUE;
15334                     }
15335                 }
15336             }
15337         }
15338     }
15339     return FALSE;
15340 }
15341
15342 void
15343 CheckTimeControl()
15344 {
15345     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15346         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15347
15348     /*
15349      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15350      */
15351     if ( !WhiteOnMove(forwardMostMove) ) {
15352         /* White made time control */
15353         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15354         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15355         /* [HGM] time odds: correct new time quota for time odds! */
15356                                             / WhitePlayer()->timeOdds;
15357         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15358     } else {
15359         lastBlack -= blackTimeRemaining;
15360         /* Black made time control */
15361         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15362                                             / WhitePlayer()->other->timeOdds;
15363         lastWhite = whiteTimeRemaining;
15364     }
15365 }
15366
15367 void
15368 DisplayBothClocks()
15369 {
15370     int wom = gameMode == EditPosition ?
15371       !blackPlaysFirst : WhiteOnMove(currentMove);
15372     DisplayWhiteClock(whiteTimeRemaining, wom);
15373     DisplayBlackClock(blackTimeRemaining, !wom);
15374 }
15375
15376
15377 /* Timekeeping seems to be a portability nightmare.  I think everyone
15378    has ftime(), but I'm really not sure, so I'm including some ifdefs
15379    to use other calls if you don't.  Clocks will be less accurate if
15380    you have neither ftime nor gettimeofday.
15381 */
15382
15383 /* VS 2008 requires the #include outside of the function */
15384 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15385 #include <sys/timeb.h>
15386 #endif
15387
15388 /* Get the current time as a TimeMark */
15389 void
15390 GetTimeMark(tm)
15391      TimeMark *tm;
15392 {
15393 #if HAVE_GETTIMEOFDAY
15394
15395     struct timeval timeVal;
15396     struct timezone timeZone;
15397
15398     gettimeofday(&timeVal, &timeZone);
15399     tm->sec = (long) timeVal.tv_sec;
15400     tm->ms = (int) (timeVal.tv_usec / 1000L);
15401
15402 #else /*!HAVE_GETTIMEOFDAY*/
15403 #if HAVE_FTIME
15404
15405 // include <sys/timeb.h> / moved to just above start of function
15406     struct timeb timeB;
15407
15408     ftime(&timeB);
15409     tm->sec = (long) timeB.time;
15410     tm->ms = (int) timeB.millitm;
15411
15412 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15413     tm->sec = (long) time(NULL);
15414     tm->ms = 0;
15415 #endif
15416 #endif
15417 }
15418
15419 /* Return the difference in milliseconds between two
15420    time marks.  We assume the difference will fit in a long!
15421 */
15422 long
15423 SubtractTimeMarks(tm2, tm1)
15424      TimeMark *tm2, *tm1;
15425 {
15426     return 1000L*(tm2->sec - tm1->sec) +
15427            (long) (tm2->ms - tm1->ms);
15428 }
15429
15430
15431 /*
15432  * Code to manage the game clocks.
15433  *
15434  * In tournament play, black starts the clock and then white makes a move.
15435  * We give the human user a slight advantage if he is playing white---the
15436  * clocks don't run until he makes his first move, so it takes zero time.
15437  * Also, we don't account for network lag, so we could get out of sync
15438  * with GNU Chess's clock -- but then, referees are always right.
15439  */
15440
15441 static TimeMark tickStartTM;
15442 static long intendedTickLength;
15443
15444 long
15445 NextTickLength(timeRemaining)
15446      long timeRemaining;
15447 {
15448     long nominalTickLength, nextTickLength;
15449
15450     if (timeRemaining > 0L && timeRemaining <= 10000L)
15451       nominalTickLength = 100L;
15452     else
15453       nominalTickLength = 1000L;
15454     nextTickLength = timeRemaining % nominalTickLength;
15455     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15456
15457     return nextTickLength;
15458 }
15459
15460 /* Adjust clock one minute up or down */
15461 void
15462 AdjustClock(Boolean which, int dir)
15463 {
15464     if(which) blackTimeRemaining += 60000*dir;
15465     else      whiteTimeRemaining += 60000*dir;
15466     DisplayBothClocks();
15467 }
15468
15469 /* Stop clocks and reset to a fresh time control */
15470 void
15471 ResetClocks()
15472 {
15473     (void) StopClockTimer();
15474     if (appData.icsActive) {
15475         whiteTimeRemaining = blackTimeRemaining = 0;
15476     } else if (searchTime) {
15477         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15478         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15479     } else { /* [HGM] correct new time quote for time odds */
15480         whiteTC = blackTC = fullTimeControlString;
15481         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15482         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15483     }
15484     if (whiteFlag || blackFlag) {
15485         DisplayTitle("");
15486         whiteFlag = blackFlag = FALSE;
15487     }
15488     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15489     DisplayBothClocks();
15490 }
15491
15492 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15493
15494 /* Decrement running clock by amount of time that has passed */
15495 void
15496 DecrementClocks()
15497 {
15498     long timeRemaining;
15499     long lastTickLength, fudge;
15500     TimeMark now;
15501
15502     if (!appData.clockMode) return;
15503     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15504
15505     GetTimeMark(&now);
15506
15507     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15508
15509     /* Fudge if we woke up a little too soon */
15510     fudge = intendedTickLength - lastTickLength;
15511     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15512
15513     if (WhiteOnMove(forwardMostMove)) {
15514         if(whiteNPS >= 0) lastTickLength = 0;
15515         timeRemaining = whiteTimeRemaining -= lastTickLength;
15516         if(timeRemaining < 0 && !appData.icsActive) {
15517             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15518             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15519                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15520                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15521             }
15522         }
15523         DisplayWhiteClock(whiteTimeRemaining - fudge,
15524                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15525     } else {
15526         if(blackNPS >= 0) lastTickLength = 0;
15527         timeRemaining = blackTimeRemaining -= lastTickLength;
15528         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15529             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15530             if(suddenDeath) {
15531                 blackStartMove = forwardMostMove;
15532                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15533             }
15534         }
15535         DisplayBlackClock(blackTimeRemaining - fudge,
15536                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15537     }
15538     if (CheckFlags()) return;
15539
15540     tickStartTM = now;
15541     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15542     StartClockTimer(intendedTickLength);
15543
15544     /* if the time remaining has fallen below the alarm threshold, sound the
15545      * alarm. if the alarm has sounded and (due to a takeback or time control
15546      * with increment) the time remaining has increased to a level above the
15547      * threshold, reset the alarm so it can sound again.
15548      */
15549
15550     if (appData.icsActive && appData.icsAlarm) {
15551
15552         /* make sure we are dealing with the user's clock */
15553         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15554                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15555            )) return;
15556
15557         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15558             alarmSounded = FALSE;
15559         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15560             PlayAlarmSound();
15561             alarmSounded = TRUE;
15562         }
15563     }
15564 }
15565
15566
15567 /* A player has just moved, so stop the previously running
15568    clock and (if in clock mode) start the other one.
15569    We redisplay both clocks in case we're in ICS mode, because
15570    ICS gives us an update to both clocks after every move.
15571    Note that this routine is called *after* forwardMostMove
15572    is updated, so the last fractional tick must be subtracted
15573    from the color that is *not* on move now.
15574 */
15575 void
15576 SwitchClocks(int newMoveNr)
15577 {
15578     long lastTickLength;
15579     TimeMark now;
15580     int flagged = FALSE;
15581
15582     GetTimeMark(&now);
15583
15584     if (StopClockTimer() && appData.clockMode) {
15585         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15586         if (!WhiteOnMove(forwardMostMove)) {
15587             if(blackNPS >= 0) lastTickLength = 0;
15588             blackTimeRemaining -= lastTickLength;
15589            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15590 //         if(pvInfoList[forwardMostMove].time == -1)
15591                  pvInfoList[forwardMostMove].time =               // use GUI time
15592                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15593         } else {
15594            if(whiteNPS >= 0) lastTickLength = 0;
15595            whiteTimeRemaining -= lastTickLength;
15596            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15597 //         if(pvInfoList[forwardMostMove].time == -1)
15598                  pvInfoList[forwardMostMove].time =
15599                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15600         }
15601         flagged = CheckFlags();
15602     }
15603     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15604     CheckTimeControl();
15605
15606     if (flagged || !appData.clockMode) return;
15607
15608     switch (gameMode) {
15609       case MachinePlaysBlack:
15610       case MachinePlaysWhite:
15611       case BeginningOfGame:
15612         if (pausing) return;
15613         break;
15614
15615       case EditGame:
15616       case PlayFromGameFile:
15617       case IcsExamining:
15618         return;
15619
15620       default:
15621         break;
15622     }
15623
15624     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15625         if(WhiteOnMove(forwardMostMove))
15626              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15627         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15628     }
15629
15630     tickStartTM = now;
15631     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15632       whiteTimeRemaining : blackTimeRemaining);
15633     StartClockTimer(intendedTickLength);
15634 }
15635
15636
15637 /* Stop both clocks */
15638 void
15639 StopClocks()
15640 {
15641     long lastTickLength;
15642     TimeMark now;
15643
15644     if (!StopClockTimer()) return;
15645     if (!appData.clockMode) return;
15646
15647     GetTimeMark(&now);
15648
15649     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15650     if (WhiteOnMove(forwardMostMove)) {
15651         if(whiteNPS >= 0) lastTickLength = 0;
15652         whiteTimeRemaining -= lastTickLength;
15653         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15654     } else {
15655         if(blackNPS >= 0) lastTickLength = 0;
15656         blackTimeRemaining -= lastTickLength;
15657         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15658     }
15659     CheckFlags();
15660 }
15661
15662 /* Start clock of player on move.  Time may have been reset, so
15663    if clock is already running, stop and restart it. */
15664 void
15665 StartClocks()
15666 {
15667     (void) StopClockTimer(); /* in case it was running already */
15668     DisplayBothClocks();
15669     if (CheckFlags()) return;
15670
15671     if (!appData.clockMode) return;
15672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15673
15674     GetTimeMark(&tickStartTM);
15675     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15676       whiteTimeRemaining : blackTimeRemaining);
15677
15678    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15679     whiteNPS = blackNPS = -1;
15680     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15681        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15682         whiteNPS = first.nps;
15683     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15684        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15685         blackNPS = first.nps;
15686     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15687         whiteNPS = second.nps;
15688     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15689         blackNPS = second.nps;
15690     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15691
15692     StartClockTimer(intendedTickLength);
15693 }
15694
15695 char *
15696 TimeString(ms)
15697      long ms;
15698 {
15699     long second, minute, hour, day;
15700     char *sign = "";
15701     static char buf[32];
15702
15703     if (ms > 0 && ms <= 9900) {
15704       /* convert milliseconds to tenths, rounding up */
15705       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15706
15707       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15708       return buf;
15709     }
15710
15711     /* convert milliseconds to seconds, rounding up */
15712     /* use floating point to avoid strangeness of integer division
15713        with negative dividends on many machines */
15714     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15715
15716     if (second < 0) {
15717         sign = "-";
15718         second = -second;
15719     }
15720
15721     day = second / (60 * 60 * 24);
15722     second = second % (60 * 60 * 24);
15723     hour = second / (60 * 60);
15724     second = second % (60 * 60);
15725     minute = second / 60;
15726     second = second % 60;
15727
15728     if (day > 0)
15729       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15730               sign, day, hour, minute, second);
15731     else if (hour > 0)
15732       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15733     else
15734       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15735
15736     return buf;
15737 }
15738
15739
15740 /*
15741  * This is necessary because some C libraries aren't ANSI C compliant yet.
15742  */
15743 char *
15744 StrStr(string, match)
15745      char *string, *match;
15746 {
15747     int i, length;
15748
15749     length = strlen(match);
15750
15751     for (i = strlen(string) - length; i >= 0; i--, string++)
15752       if (!strncmp(match, string, length))
15753         return string;
15754
15755     return NULL;
15756 }
15757
15758 char *
15759 StrCaseStr(string, match)
15760      char *string, *match;
15761 {
15762     int i, j, length;
15763
15764     length = strlen(match);
15765
15766     for (i = strlen(string) - length; i >= 0; i--, string++) {
15767         for (j = 0; j < length; j++) {
15768             if (ToLower(match[j]) != ToLower(string[j]))
15769               break;
15770         }
15771         if (j == length) return string;
15772     }
15773
15774     return NULL;
15775 }
15776
15777 #ifndef _amigados
15778 int
15779 StrCaseCmp(s1, s2)
15780      char *s1, *s2;
15781 {
15782     char c1, c2;
15783
15784     for (;;) {
15785         c1 = ToLower(*s1++);
15786         c2 = ToLower(*s2++);
15787         if (c1 > c2) return 1;
15788         if (c1 < c2) return -1;
15789         if (c1 == NULLCHAR) return 0;
15790     }
15791 }
15792
15793
15794 int
15795 ToLower(c)
15796      int c;
15797 {
15798     return isupper(c) ? tolower(c) : c;
15799 }
15800
15801
15802 int
15803 ToUpper(c)
15804      int c;
15805 {
15806     return islower(c) ? toupper(c) : c;
15807 }
15808 #endif /* !_amigados    */
15809
15810 char *
15811 StrSave(s)
15812      char *s;
15813 {
15814   char *ret;
15815
15816   if ((ret = (char *) malloc(strlen(s) + 1)))
15817     {
15818       safeStrCpy(ret, s, strlen(s)+1);
15819     }
15820   return ret;
15821 }
15822
15823 char *
15824 StrSavePtr(s, savePtr)
15825      char *s, **savePtr;
15826 {
15827     if (*savePtr) {
15828         free(*savePtr);
15829     }
15830     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15831       safeStrCpy(*savePtr, s, strlen(s)+1);
15832     }
15833     return(*savePtr);
15834 }
15835
15836 char *
15837 PGNDate()
15838 {
15839     time_t clock;
15840     struct tm *tm;
15841     char buf[MSG_SIZ];
15842
15843     clock = time((time_t *)NULL);
15844     tm = localtime(&clock);
15845     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15846             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15847     return StrSave(buf);
15848 }
15849
15850
15851 char *
15852 PositionToFEN(move, overrideCastling)
15853      int move;
15854      char *overrideCastling;
15855 {
15856     int i, j, fromX, fromY, toX, toY;
15857     int whiteToPlay;
15858     char buf[MSG_SIZ];
15859     char *p, *q;
15860     int emptycount;
15861     ChessSquare piece;
15862
15863     whiteToPlay = (gameMode == EditPosition) ?
15864       !blackPlaysFirst : (move % 2 == 0);
15865     p = buf;
15866
15867     /* Piece placement data */
15868     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15869         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15870         emptycount = 0;
15871         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15872             if (boards[move][i][j] == EmptySquare) {
15873                 emptycount++;
15874             } else { ChessSquare piece = boards[move][i][j];
15875                 if (emptycount > 0) {
15876                     if(emptycount<10) /* [HGM] can be >= 10 */
15877                         *p++ = '0' + emptycount;
15878                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15879                     emptycount = 0;
15880                 }
15881                 if(PieceToChar(piece) == '+') {
15882                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15883                     *p++ = '+';
15884                     piece = (ChessSquare)(DEMOTED piece);
15885                 }
15886                 *p++ = PieceToChar(piece);
15887                 if(p[-1] == '~') {
15888                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15889                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15890                     *p++ = '~';
15891                 }
15892             }
15893         }
15894         if (emptycount > 0) {
15895             if(emptycount<10) /* [HGM] can be >= 10 */
15896                 *p++ = '0' + emptycount;
15897             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15898             emptycount = 0;
15899         }
15900         *p++ = '/';
15901     }
15902     *(p - 1) = ' ';
15903
15904     /* [HGM] print Crazyhouse or Shogi holdings */
15905     if( gameInfo.holdingsWidth ) {
15906         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15907         q = p;
15908         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15909             piece = boards[move][i][BOARD_WIDTH-1];
15910             if( piece != EmptySquare )
15911               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15912                   *p++ = PieceToChar(piece);
15913         }
15914         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15915             piece = boards[move][BOARD_HEIGHT-i-1][0];
15916             if( piece != EmptySquare )
15917               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15918                   *p++ = PieceToChar(piece);
15919         }
15920
15921         if( q == p ) *p++ = '-';
15922         *p++ = ']';
15923         *p++ = ' ';
15924     }
15925
15926     /* Active color */
15927     *p++ = whiteToPlay ? 'w' : 'b';
15928     *p++ = ' ';
15929
15930   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15931     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15932   } else {
15933   if(nrCastlingRights) {
15934      q = p;
15935      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15936        /* [HGM] write directly from rights */
15937            if(boards[move][CASTLING][2] != NoRights &&
15938               boards[move][CASTLING][0] != NoRights   )
15939                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15940            if(boards[move][CASTLING][2] != NoRights &&
15941               boards[move][CASTLING][1] != NoRights   )
15942                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15943            if(boards[move][CASTLING][5] != NoRights &&
15944               boards[move][CASTLING][3] != NoRights   )
15945                 *p++ = boards[move][CASTLING][3] + AAA;
15946            if(boards[move][CASTLING][5] != NoRights &&
15947               boards[move][CASTLING][4] != NoRights   )
15948                 *p++ = boards[move][CASTLING][4] + AAA;
15949      } else {
15950
15951         /* [HGM] write true castling rights */
15952         if( nrCastlingRights == 6 ) {
15953             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15954                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15955             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15956                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15957             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15958                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15959             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15960                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15961         }
15962      }
15963      if (q == p) *p++ = '-'; /* No castling rights */
15964      *p++ = ' ';
15965   }
15966
15967   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15968      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15969     /* En passant target square */
15970     if (move > backwardMostMove) {
15971         fromX = moveList[move - 1][0] - AAA;
15972         fromY = moveList[move - 1][1] - ONE;
15973         toX = moveList[move - 1][2] - AAA;
15974         toY = moveList[move - 1][3] - ONE;
15975         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15976             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15977             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15978             fromX == toX) {
15979             /* 2-square pawn move just happened */
15980             *p++ = toX + AAA;
15981             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15982         } else {
15983             *p++ = '-';
15984         }
15985     } else if(move == backwardMostMove) {
15986         // [HGM] perhaps we should always do it like this, and forget the above?
15987         if((signed char)boards[move][EP_STATUS] >= 0) {
15988             *p++ = boards[move][EP_STATUS] + AAA;
15989             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15990         } else {
15991             *p++ = '-';
15992         }
15993     } else {
15994         *p++ = '-';
15995     }
15996     *p++ = ' ';
15997   }
15998   }
15999
16000     /* [HGM] find reversible plies */
16001     {   int i = 0, j=move;
16002
16003         if (appData.debugMode) { int k;
16004             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16005             for(k=backwardMostMove; k<=forwardMostMove; k++)
16006                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16007
16008         }
16009
16010         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16011         if( j == backwardMostMove ) i += initialRulePlies;
16012         sprintf(p, "%d ", i);
16013         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16014     }
16015     /* Fullmove number */
16016     sprintf(p, "%d", (move / 2) + 1);
16017
16018     return StrSave(buf);
16019 }
16020
16021 Boolean
16022 ParseFEN(board, blackPlaysFirst, fen)
16023     Board board;
16024      int *blackPlaysFirst;
16025      char *fen;
16026 {
16027     int i, j;
16028     char *p, c;
16029     int emptycount;
16030     ChessSquare piece;
16031
16032     p = fen;
16033
16034     /* [HGM] by default clear Crazyhouse holdings, if present */
16035     if(gameInfo.holdingsWidth) {
16036        for(i=0; i<BOARD_HEIGHT; i++) {
16037            board[i][0]             = EmptySquare; /* black holdings */
16038            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16039            board[i][1]             = (ChessSquare) 0; /* black counts */
16040            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16041        }
16042     }
16043
16044     /* Piece placement data */
16045     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16046         j = 0;
16047         for (;;) {
16048             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16049                 if (*p == '/') p++;
16050                 emptycount = gameInfo.boardWidth - j;
16051                 while (emptycount--)
16052                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16053                 break;
16054 #if(BOARD_FILES >= 10)
16055             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16056                 p++; emptycount=10;
16057                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16058                 while (emptycount--)
16059                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16060 #endif
16061             } else if (isdigit(*p)) {
16062                 emptycount = *p++ - '0';
16063                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16064                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16065                 while (emptycount--)
16066                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16067             } else if (*p == '+' || isalpha(*p)) {
16068                 if (j >= gameInfo.boardWidth) return FALSE;
16069                 if(*p=='+') {
16070                     piece = CharToPiece(*++p);
16071                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16072                     piece = (ChessSquare) (PROMOTED piece ); p++;
16073                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16074                 } else piece = CharToPiece(*p++);
16075
16076                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16077                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16078                     piece = (ChessSquare) (PROMOTED piece);
16079                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16080                     p++;
16081                 }
16082                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16083             } else {
16084                 return FALSE;
16085             }
16086         }
16087     }
16088     while (*p == '/' || *p == ' ') p++;
16089
16090     /* [HGM] look for Crazyhouse holdings here */
16091     while(*p==' ') p++;
16092     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16093         if(*p == '[') p++;
16094         if(*p == '-' ) p++; /* empty holdings */ else {
16095             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16096             /* if we would allow FEN reading to set board size, we would   */
16097             /* have to add holdings and shift the board read so far here   */
16098             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16099                 p++;
16100                 if((int) piece >= (int) BlackPawn ) {
16101                     i = (int)piece - (int)BlackPawn;
16102                     i = PieceToNumber((ChessSquare)i);
16103                     if( i >= gameInfo.holdingsSize ) return FALSE;
16104                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16105                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16106                 } else {
16107                     i = (int)piece - (int)WhitePawn;
16108                     i = PieceToNumber((ChessSquare)i);
16109                     if( i >= gameInfo.holdingsSize ) return FALSE;
16110                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16111                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16112                 }
16113             }
16114         }
16115         if(*p == ']') p++;
16116     }
16117
16118     while(*p == ' ') p++;
16119
16120     /* Active color */
16121     c = *p++;
16122     if(appData.colorNickNames) {
16123       if( c == appData.colorNickNames[0] ) c = 'w'; else
16124       if( c == appData.colorNickNames[1] ) c = 'b';
16125     }
16126     switch (c) {
16127       case 'w':
16128         *blackPlaysFirst = FALSE;
16129         break;
16130       case 'b':
16131         *blackPlaysFirst = TRUE;
16132         break;
16133       default:
16134         return FALSE;
16135     }
16136
16137     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16138     /* return the extra info in global variiables             */
16139
16140     /* set defaults in case FEN is incomplete */
16141     board[EP_STATUS] = EP_UNKNOWN;
16142     for(i=0; i<nrCastlingRights; i++ ) {
16143         board[CASTLING][i] =
16144             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16145     }   /* assume possible unless obviously impossible */
16146     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16147     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16148     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16149                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16150     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16151     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16152     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16153                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16154     FENrulePlies = 0;
16155
16156     while(*p==' ') p++;
16157     if(nrCastlingRights) {
16158       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16159           /* castling indicator present, so default becomes no castlings */
16160           for(i=0; i<nrCastlingRights; i++ ) {
16161                  board[CASTLING][i] = NoRights;
16162           }
16163       }
16164       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16165              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16166              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16167              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16168         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16169
16170         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16171             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16172             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16173         }
16174         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16175             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16176         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16177                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16178         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16179                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16180         switch(c) {
16181           case'K':
16182               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16183               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16184               board[CASTLING][2] = whiteKingFile;
16185               break;
16186           case'Q':
16187               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16188               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16189               board[CASTLING][2] = whiteKingFile;
16190               break;
16191           case'k':
16192               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16193               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16194               board[CASTLING][5] = blackKingFile;
16195               break;
16196           case'q':
16197               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16198               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16199               board[CASTLING][5] = blackKingFile;
16200           case '-':
16201               break;
16202           default: /* FRC castlings */
16203               if(c >= 'a') { /* black rights */
16204                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16205                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16206                   if(i == BOARD_RGHT) break;
16207                   board[CASTLING][5] = i;
16208                   c -= AAA;
16209                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16210                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16211                   if(c > i)
16212                       board[CASTLING][3] = c;
16213                   else
16214                       board[CASTLING][4] = c;
16215               } else { /* white rights */
16216                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16217                     if(board[0][i] == WhiteKing) break;
16218                   if(i == BOARD_RGHT) break;
16219                   board[CASTLING][2] = i;
16220                   c -= AAA - 'a' + 'A';
16221                   if(board[0][c] >= WhiteKing) break;
16222                   if(c > i)
16223                       board[CASTLING][0] = c;
16224                   else
16225                       board[CASTLING][1] = c;
16226               }
16227         }
16228       }
16229       for(i=0; i<nrCastlingRights; i++)
16230         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16231     if (appData.debugMode) {
16232         fprintf(debugFP, "FEN castling rights:");
16233         for(i=0; i<nrCastlingRights; i++)
16234         fprintf(debugFP, " %d", board[CASTLING][i]);
16235         fprintf(debugFP, "\n");
16236     }
16237
16238       while(*p==' ') p++;
16239     }
16240
16241     /* read e.p. field in games that know e.p. capture */
16242     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16243        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16244       if(*p=='-') {
16245         p++; board[EP_STATUS] = EP_NONE;
16246       } else {
16247          char c = *p++ - AAA;
16248
16249          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16250          if(*p >= '0' && *p <='9') p++;
16251          board[EP_STATUS] = c;
16252       }
16253     }
16254
16255
16256     if(sscanf(p, "%d", &i) == 1) {
16257         FENrulePlies = i; /* 50-move ply counter */
16258         /* (The move number is still ignored)    */
16259     }
16260
16261     return TRUE;
16262 }
16263
16264 void
16265 EditPositionPasteFEN(char *fen)
16266 {
16267   if (fen != NULL) {
16268     Board initial_position;
16269
16270     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16271       DisplayError(_("Bad FEN position in clipboard"), 0);
16272       return ;
16273     } else {
16274       int savedBlackPlaysFirst = blackPlaysFirst;
16275       EditPositionEvent();
16276       blackPlaysFirst = savedBlackPlaysFirst;
16277       CopyBoard(boards[0], initial_position);
16278       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16279       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16280       DisplayBothClocks();
16281       DrawPosition(FALSE, boards[currentMove]);
16282     }
16283   }
16284 }
16285
16286 static char cseq[12] = "\\   ";
16287
16288 Boolean set_cont_sequence(char *new_seq)
16289 {
16290     int len;
16291     Boolean ret;
16292
16293     // handle bad attempts to set the sequence
16294         if (!new_seq)
16295                 return 0; // acceptable error - no debug
16296
16297     len = strlen(new_seq);
16298     ret = (len > 0) && (len < sizeof(cseq));
16299     if (ret)
16300       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16301     else if (appData.debugMode)
16302       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16303     return ret;
16304 }
16305
16306 /*
16307     reformat a source message so words don't cross the width boundary.  internal
16308     newlines are not removed.  returns the wrapped size (no null character unless
16309     included in source message).  If dest is NULL, only calculate the size required
16310     for the dest buffer.  lp argument indicats line position upon entry, and it's
16311     passed back upon exit.
16312 */
16313 int wrap(char *dest, char *src, int count, int width, int *lp)
16314 {
16315     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16316
16317     cseq_len = strlen(cseq);
16318     old_line = line = *lp;
16319     ansi = len = clen = 0;
16320
16321     for (i=0; i < count; i++)
16322     {
16323         if (src[i] == '\033')
16324             ansi = 1;
16325
16326         // if we hit the width, back up
16327         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16328         {
16329             // store i & len in case the word is too long
16330             old_i = i, old_len = len;
16331
16332             // find the end of the last word
16333             while (i && src[i] != ' ' && src[i] != '\n')
16334             {
16335                 i--;
16336                 len--;
16337             }
16338
16339             // word too long?  restore i & len before splitting it
16340             if ((old_i-i+clen) >= width)
16341             {
16342                 i = old_i;
16343                 len = old_len;
16344             }
16345
16346             // extra space?
16347             if (i && src[i-1] == ' ')
16348                 len--;
16349
16350             if (src[i] != ' ' && src[i] != '\n')
16351             {
16352                 i--;
16353                 if (len)
16354                     len--;
16355             }
16356
16357             // now append the newline and continuation sequence
16358             if (dest)
16359                 dest[len] = '\n';
16360             len++;
16361             if (dest)
16362                 strncpy(dest+len, cseq, cseq_len);
16363             len += cseq_len;
16364             line = cseq_len;
16365             clen = cseq_len;
16366             continue;
16367         }
16368
16369         if (dest)
16370             dest[len] = src[i];
16371         len++;
16372         if (!ansi)
16373             line++;
16374         if (src[i] == '\n')
16375             line = 0;
16376         if (src[i] == 'm')
16377             ansi = 0;
16378     }
16379     if (dest && appData.debugMode)
16380     {
16381         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16382             count, width, line, len, *lp);
16383         show_bytes(debugFP, src, count);
16384         fprintf(debugFP, "\ndest: ");
16385         show_bytes(debugFP, dest, len);
16386         fprintf(debugFP, "\n");
16387     }
16388     *lp = dest ? line : old_line;
16389
16390     return len;
16391 }
16392
16393 // [HGM] vari: routines for shelving variations
16394
16395 void
16396 PushInner(int firstMove, int lastMove)
16397 {
16398         int i, j, nrMoves = lastMove - firstMove;
16399
16400         // push current tail of game on stack
16401         savedResult[storedGames] = gameInfo.result;
16402         savedDetails[storedGames] = gameInfo.resultDetails;
16403         gameInfo.resultDetails = NULL;
16404         savedFirst[storedGames] = firstMove;
16405         savedLast [storedGames] = lastMove;
16406         savedFramePtr[storedGames] = framePtr;
16407         framePtr -= nrMoves; // reserve space for the boards
16408         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16409             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16410             for(j=0; j<MOVE_LEN; j++)
16411                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16412             for(j=0; j<2*MOVE_LEN; j++)
16413                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16414             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16415             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16416             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16417             pvInfoList[firstMove+i-1].depth = 0;
16418             commentList[framePtr+i] = commentList[firstMove+i];
16419             commentList[firstMove+i] = NULL;
16420         }
16421
16422         storedGames++;
16423         forwardMostMove = firstMove; // truncate game so we can start variation
16424 }
16425
16426 void
16427 PushTail(int firstMove, int lastMove)
16428 {
16429         if(appData.icsActive) { // only in local mode
16430                 forwardMostMove = currentMove; // mimic old ICS behavior
16431                 return;
16432         }
16433         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16434
16435         PushInner(firstMove, lastMove);
16436         if(storedGames == 1) GreyRevert(FALSE);
16437 }
16438
16439 void
16440 PopInner(Boolean annotate)
16441 {
16442         int i, j, nrMoves;
16443         char buf[8000], moveBuf[20];
16444
16445         storedGames--;
16446         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16447         nrMoves = savedLast[storedGames] - currentMove;
16448         if(annotate) {
16449                 int cnt = 10;
16450                 if(!WhiteOnMove(currentMove))
16451                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16452                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16453                 for(i=currentMove; i<forwardMostMove; i++) {
16454                         if(WhiteOnMove(i))
16455                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16456                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16457                         strcat(buf, moveBuf);
16458                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16459                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16460                 }
16461                 strcat(buf, ")");
16462         }
16463         for(i=1; i<=nrMoves; i++) { // copy last variation back
16464             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16465             for(j=0; j<MOVE_LEN; j++)
16466                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16467             for(j=0; j<2*MOVE_LEN; j++)
16468                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16469             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16470             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16471             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16472             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16473             commentList[currentMove+i] = commentList[framePtr+i];
16474             commentList[framePtr+i] = NULL;
16475         }
16476         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16477         framePtr = savedFramePtr[storedGames];
16478         gameInfo.result = savedResult[storedGames];
16479         if(gameInfo.resultDetails != NULL) {
16480             free(gameInfo.resultDetails);
16481       }
16482         gameInfo.resultDetails = savedDetails[storedGames];
16483         forwardMostMove = currentMove + nrMoves;
16484 }
16485
16486 Boolean
16487 PopTail(Boolean annotate)
16488 {
16489         if(appData.icsActive) return FALSE; // only in local mode
16490         if(!storedGames) return FALSE; // sanity
16491         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16492
16493         PopInner(annotate);
16494
16495         if(storedGames == 0) GreyRevert(TRUE);
16496         return TRUE;
16497 }
16498
16499 void
16500 CleanupTail()
16501 {       // remove all shelved variations
16502         int i;
16503         for(i=0; i<storedGames; i++) {
16504             if(savedDetails[i])
16505                 free(savedDetails[i]);
16506             savedDetails[i] = NULL;
16507         }
16508         for(i=framePtr; i<MAX_MOVES; i++) {
16509                 if(commentList[i]) free(commentList[i]);
16510                 commentList[i] = NULL;
16511         }
16512         framePtr = MAX_MOVES-1;
16513         storedGames = 0;
16514 }
16515
16516 void
16517 LoadVariation(int index, char *text)
16518 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16519         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16520         int level = 0, move;
16521
16522         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16523         // first find outermost bracketing variation
16524         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16525             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16526                 if(*p == '{') wait = '}'; else
16527                 if(*p == '[') wait = ']'; else
16528                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16529                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16530             }
16531             if(*p == wait) wait = NULLCHAR; // closing ]} found
16532             p++;
16533         }
16534         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16535         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16536         end[1] = NULLCHAR; // clip off comment beyond variation
16537         ToNrEvent(currentMove-1);
16538         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16539         // kludge: use ParsePV() to append variation to game
16540         move = currentMove;
16541         ParsePV(start, TRUE, TRUE);
16542         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16543         ClearPremoveHighlights();
16544         CommentPopDown();
16545         ToNrEvent(currentMove+1);
16546 }
16547