Updated copyright notice to 2011
[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 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <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
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #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"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 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);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6192
6193   if (gameMode == BeginningOfGame) {
6194     if (appData.noChessProgram) {
6195       gameMode = EditGame;
6196       SetGameInfo();
6197     } else {
6198       char buf[MSG_SIZ];
6199       gameMode = MachinePlaysBlack;
6200       StartClocks();
6201       SetGameInfo();
6202       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6203       DisplayTitle(buf);
6204       if (first.sendName) {
6205         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6206         SendToProgram(buf, &first);
6207       }
6208       StartClocks();
6209     }
6210     ModeHighlight();
6211   }
6212
6213   /* Relay move to ICS or chess engine */
6214   if (appData.icsActive) {
6215     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6216         gameMode == IcsExamining) {
6217       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6218         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6219         SendToICS("draw ");
6220         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6221       }
6222       // also send plain move, in case ICS does not understand atomic claims
6223       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       ics_user_moved = 1;
6225     }
6226   } else {
6227     if (first.sendTime && (gameMode == BeginningOfGame ||
6228                            gameMode == MachinePlaysWhite ||
6229                            gameMode == MachinePlaysBlack)) {
6230       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6231     }
6232     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6233          // [HGM] book: if program might be playing, let it use book
6234         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6235         first.maybeThinking = TRUE;
6236     } else SendMoveToProgram(forwardMostMove-1, &first);
6237     if (currentMove == cmailOldMove + 1) {
6238       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6239     }
6240   }
6241
6242   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243
6244   switch (gameMode) {
6245   case EditGame:
6246     if(appData.testLegality)
6247     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6248     case MT_NONE:
6249     case MT_CHECK:
6250       break;
6251     case MT_CHECKMATE:
6252     case MT_STAINMATE:
6253       if (WhiteOnMove(currentMove)) {
6254         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6255       } else {
6256         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6257       }
6258       break;
6259     case MT_STALEMATE:
6260       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6261       break;
6262     }
6263     break;
6264
6265   case MachinePlaysBlack:
6266   case MachinePlaysWhite:
6267     /* disable certain menu options while machine is thinking */
6268     SetMachineThinkingEnables();
6269     break;
6270
6271   default:
6272     break;
6273   }
6274
6275   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6276
6277   if(bookHit) { // [HGM] book: simulate book reply
6278         static char bookMove[MSG_SIZ]; // a bit generous?
6279
6280         programStats.nodes = programStats.depth = programStats.time =
6281         programStats.score = programStats.got_only_move = 0;
6282         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6283
6284         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6285         strcat(bookMove, bookHit);
6286         HandleMachineMove(bookMove, &first);
6287   }
6288   return 1;
6289 }
6290
6291 void
6292 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6293      Board board;
6294      int flags;
6295      ChessMove kind;
6296      int rf, ff, rt, ft;
6297      VOIDSTAR closure;
6298 {
6299     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6300     Markers *m = (Markers *) closure;
6301     if(rf == fromY && ff == fromX)
6302         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6303                          || kind == WhiteCapturesEnPassant
6304                          || kind == BlackCapturesEnPassant);
6305     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6306 }
6307
6308 void
6309 MarkTargetSquares(int clear)
6310 {
6311   int x, y;
6312   if(!appData.markers || !appData.highlightDragging ||
6313      !appData.testLegality || gameMode == EditPosition) return;
6314   if(clear) {
6315     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6316   } else {
6317     int capt = 0;
6318     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6319     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6321       if(capt)
6322       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6323     }
6324   }
6325   DrawPosition(TRUE, NULL);
6326 }
6327
6328 int
6329 Explode(Board board, int fromX, int fromY, int toX, int toY)
6330 {
6331     if(gameInfo.variant == VariantAtomic &&
6332        (board[toY][toX] != EmptySquare ||                     // capture?
6333         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6334                          board[fromY][fromX] == BlackPawn   )
6335       )) {
6336         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6337         return TRUE;
6338     }
6339     return FALSE;
6340 }
6341
6342 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6343
6344 void LeftClick(ClickType clickType, int xPix, int yPix)
6345 {
6346     int x, y;
6347     Boolean saveAnimate;
6348     static int second = 0, promotionChoice = 0, dragging = 0;
6349     char promoChoice = NULLCHAR;
6350
6351     if(appData.seekGraph && appData.icsActive && loggedOn &&
6352         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6353         SeekGraphClick(clickType, xPix, yPix, 0);
6354         return;
6355     }
6356
6357     if (clickType == Press) ErrorPopDown();
6358     MarkTargetSquares(1);
6359
6360     x = EventToSquare(xPix, BOARD_WIDTH);
6361     y = EventToSquare(yPix, BOARD_HEIGHT);
6362     if (!flipView && y >= 0) {
6363         y = BOARD_HEIGHT - 1 - y;
6364     }
6365     if (flipView && x >= 0) {
6366         x = BOARD_WIDTH - 1 - x;
6367     }
6368
6369     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6370         if(clickType == Release) return; // ignore upclick of click-click destination
6371         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6372         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6373         if(gameInfo.holdingsWidth &&
6374                 (WhiteOnMove(currentMove)
6375                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6376                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6377             // click in right holdings, for determining promotion piece
6378             ChessSquare p = boards[currentMove][y][x];
6379             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6380             if(p != EmptySquare) {
6381                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6382                 fromX = fromY = -1;
6383                 return;
6384             }
6385         }
6386         DrawPosition(FALSE, boards[currentMove]);
6387         return;
6388     }
6389
6390     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6391     if(clickType == Press
6392             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6393               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6394               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6395         return;
6396
6397     autoQueen = appData.alwaysPromoteToQueen;
6398
6399     if (fromX == -1) {
6400       gatingPiece = EmptySquare;
6401       if (clickType != Press) {
6402         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6403             DragPieceEnd(xPix, yPix); dragging = 0;
6404             DrawPosition(FALSE, NULL);
6405         }
6406         return;
6407       }
6408       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6409             /* First square */
6410             if (OKToStartUserMove(x, y)) {
6411                 fromX = x;
6412                 fromY = y;
6413                 second = 0;
6414                 MarkTargetSquares(0);
6415                 DragPieceBegin(xPix, yPix); dragging = 1;
6416                 if (appData.highlightDragging) {
6417                     SetHighlights(x, y, -1, -1);
6418                 }
6419             }
6420             return;
6421         }
6422     }
6423
6424     /* fromX != -1 */
6425     if (clickType == Press && gameMode != EditPosition) {
6426         ChessSquare fromP;
6427         ChessSquare toP;
6428         int frc;
6429
6430         // ignore off-board to clicks
6431         if(y < 0 || x < 0) return;
6432
6433         /* Check if clicking again on the same color piece */
6434         fromP = boards[currentMove][fromY][fromX];
6435         toP = boards[currentMove][y][x];
6436         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6437         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6438              WhitePawn <= toP && toP <= WhiteKing &&
6439              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6440              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6441             (BlackPawn <= fromP && fromP <= BlackKing &&
6442              BlackPawn <= toP && toP <= BlackKing &&
6443              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6444              !(fromP == BlackKing && toP == BlackRook && frc))) {
6445             /* Clicked again on same color piece -- changed his mind */
6446             second = (x == fromX && y == fromY);
6447            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6448             if (appData.highlightDragging) {
6449                 SetHighlights(x, y, -1, -1);
6450             } else {
6451                 ClearHighlights();
6452             }
6453             if (OKToStartUserMove(x, y)) {
6454                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6455                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6456                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6457                  gatingPiece = boards[currentMove][fromY][fromX];
6458                 else gatingPiece = EmptySquare;
6459                 fromX = x;
6460                 fromY = y; dragging = 1;
6461                 MarkTargetSquares(0);
6462                 DragPieceBegin(xPix, yPix);
6463             }
6464            }
6465            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6466            second = FALSE; 
6467         }
6468         // ignore clicks on holdings
6469         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6470     }
6471
6472     if (clickType == Release && x == fromX && y == fromY) {
6473         DragPieceEnd(xPix, yPix); dragging = 0;
6474         if (appData.animateDragging) {
6475             /* Undo animation damage if any */
6476             DrawPosition(FALSE, NULL);
6477         }
6478         if (second) {
6479             /* Second up/down in same square; just abort move */
6480             second = 0;
6481             fromX = fromY = -1;
6482             gatingPiece = EmptySquare;
6483             ClearHighlights();
6484             gotPremove = 0;
6485             ClearPremoveHighlights();
6486         } else {
6487             /* First upclick in same square; start click-click mode */
6488             SetHighlights(x, y, -1, -1);
6489         }
6490         return;
6491     }
6492
6493     /* we now have a different from- and (possibly off-board) to-square */
6494     /* Completed move */
6495     toX = x;
6496     toY = y;
6497     saveAnimate = appData.animate;
6498     if (clickType == Press) {
6499         /* Finish clickclick move */
6500         if (appData.animate || appData.highlightLastMove) {
6501             SetHighlights(fromX, fromY, toX, toY);
6502         } else {
6503             ClearHighlights();
6504         }
6505     } else {
6506         /* Finish drag move */
6507         if (appData.highlightLastMove) {
6508             SetHighlights(fromX, fromY, toX, toY);
6509         } else {
6510             ClearHighlights();
6511         }
6512         DragPieceEnd(xPix, yPix); dragging = 0;
6513         /* Don't animate move and drag both */
6514         appData.animate = FALSE;
6515     }
6516
6517     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6518     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6519         ChessSquare piece = boards[currentMove][fromY][fromX];
6520         if(gameMode == EditPosition && piece != EmptySquare &&
6521            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6522             int n;
6523
6524             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6525                 n = PieceToNumber(piece - (int)BlackPawn);
6526                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6527                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6528                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6529             } else
6530             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6531                 n = PieceToNumber(piece);
6532                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6533                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6534                 boards[currentMove][n][BOARD_WIDTH-2]++;
6535             }
6536             boards[currentMove][fromY][fromX] = EmptySquare;
6537         }
6538         ClearHighlights();
6539         fromX = fromY = -1;
6540         DrawPosition(TRUE, boards[currentMove]);
6541         return;
6542     }
6543
6544     // off-board moves should not be highlighted
6545     if(x < 0 || y < 0) ClearHighlights();
6546
6547     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6548
6549     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6550         SetHighlights(fromX, fromY, toX, toY);
6551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6552             // [HGM] super: promotion to captured piece selected from holdings
6553             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6554             promotionChoice = TRUE;
6555             // kludge follows to temporarily execute move on display, without promoting yet
6556             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6557             boards[currentMove][toY][toX] = p;
6558             DrawPosition(FALSE, boards[currentMove]);
6559             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6560             boards[currentMove][toY][toX] = q;
6561             DisplayMessage("Click in holdings to choose piece", "");
6562             return;
6563         }
6564         PromotionPopUp();
6565     } else {
6566         int oldMove = currentMove;
6567         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6568         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6569         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6570         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6571            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6572             DrawPosition(TRUE, boards[currentMove]);
6573         fromX = fromY = -1;
6574     }
6575     appData.animate = saveAnimate;
6576     if (appData.animate || appData.animateDragging) {
6577         /* Undo animation damage if needed */
6578         DrawPosition(FALSE, NULL);
6579     }
6580 }
6581
6582 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6583 {   // front-end-free part taken out of PieceMenuPopup
6584     int whichMenu; int xSqr, ySqr;
6585
6586     if(seekGraphUp) { // [HGM] seekgraph
6587         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6588         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6589         return -2;
6590     }
6591
6592     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6593          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6594         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6595         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6596         if(action == Press)   {
6597             originalFlip = flipView;
6598             flipView = !flipView; // temporarily flip board to see game from partners perspective
6599             DrawPosition(TRUE, partnerBoard);
6600             DisplayMessage(partnerStatus, "");
6601             partnerUp = TRUE;
6602         } else if(action == Release) {
6603             flipView = originalFlip;
6604             DrawPosition(TRUE, boards[currentMove]);
6605             partnerUp = FALSE;
6606         }
6607         return -2;
6608     }
6609
6610     xSqr = EventToSquare(x, BOARD_WIDTH);
6611     ySqr = EventToSquare(y, BOARD_HEIGHT);
6612     if (action == Release) UnLoadPV(); // [HGM] pv
6613     if (action != Press) return -2; // return code to be ignored
6614     switch (gameMode) {
6615       case IcsExamining:
6616         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6617       case EditPosition:
6618         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6619         if (xSqr < 0 || ySqr < 0) return -1;\r
6620         whichMenu = 0; // edit-position menu
6621         break;
6622       case IcsObserving:
6623         if(!appData.icsEngineAnalyze) return -1;
6624       case IcsPlayingWhite:
6625       case IcsPlayingBlack:
6626         if(!appData.zippyPlay) goto noZip;
6627       case AnalyzeMode:
6628       case AnalyzeFile:
6629       case MachinePlaysWhite:
6630       case MachinePlaysBlack:
6631       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6632         if (!appData.dropMenu) {
6633           LoadPV(x, y);
6634           return 2; // flag front-end to grab mouse events
6635         }
6636         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6637            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6638       case EditGame:
6639       noZip:
6640         if (xSqr < 0 || ySqr < 0) return -1;
6641         if (!appData.dropMenu || appData.testLegality &&
6642             gameInfo.variant != VariantBughouse &&
6643             gameInfo.variant != VariantCrazyhouse) return -1;
6644         whichMenu = 1; // drop menu
6645         break;
6646       default:
6647         return -1;
6648     }
6649
6650     if (((*fromX = xSqr) < 0) ||
6651         ((*fromY = ySqr) < 0)) {
6652         *fromX = *fromY = -1;
6653         return -1;
6654     }
6655     if (flipView)
6656       *fromX = BOARD_WIDTH - 1 - *fromX;
6657     else
6658       *fromY = BOARD_HEIGHT - 1 - *fromY;
6659
6660     return whichMenu;
6661 }
6662
6663 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6664 {
6665 //    char * hint = lastHint;
6666     FrontEndProgramStats stats;
6667
6668     stats.which = cps == &first ? 0 : 1;
6669     stats.depth = cpstats->depth;
6670     stats.nodes = cpstats->nodes;
6671     stats.score = cpstats->score;
6672     stats.time = cpstats->time;
6673     stats.pv = cpstats->movelist;
6674     stats.hint = lastHint;
6675     stats.an_move_index = 0;
6676     stats.an_move_count = 0;
6677
6678     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6679         stats.hint = cpstats->move_name;
6680         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6681         stats.an_move_count = cpstats->nr_moves;
6682     }
6683
6684     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
6685
6686     SetProgramStats( &stats );
6687 }
6688
6689 void
6690 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6691 {       // count all piece types
6692         int p, f, r;
6693         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6694         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6695         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6696                 p = board[r][f];
6697                 pCnt[p]++;
6698                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6699                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6700                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6701                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6702                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6703                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6704         }
6705 }
6706
6707 int
6708 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6709 {
6710         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6711         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6712
6713         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6714         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6715         if(myPawns == 2 && nMine == 3) // KPP
6716             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6717         if(myPawns == 1 && nMine == 2) // KP
6718             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6719         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6720             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6721         if(myPawns) return FALSE;
6722         if(pCnt[WhiteRook+side])
6723             return pCnt[BlackRook-side] ||
6724                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6725                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6726                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6727         if(pCnt[WhiteCannon+side]) {
6728             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6729             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6730         }
6731         if(pCnt[WhiteKnight+side])
6732             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6733         return FALSE;
6734 }
6735
6736 int
6737 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6738 {
6739         VariantClass v = gameInfo.variant;
6740
6741         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6742         if(v == VariantShatranj) return TRUE; // always winnable through baring
6743         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6744         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6745
6746         if(v == VariantXiangqi) {
6747                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6748
6749                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6750                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6751                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6752                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6753                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6754                 if(stale) // we have at least one last-rank P plus perhaps C
6755                     return majors // KPKX
6756                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6757                 else // KCA*E*
6758                     return pCnt[WhiteFerz+side] // KCAK
6759                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6760                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6761                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6762
6763         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6764                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6765
6766                 if(nMine == 1) return FALSE; // bare King
6767                 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
6768                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6769                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6770                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6771                 if(pCnt[WhiteKnight+side])
6772                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6773                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6774                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6775                 if(nBishops)
6776                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6777                 if(pCnt[WhiteAlfil+side])
6778                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6779                 if(pCnt[WhiteWazir+side])
6780                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6781         }
6782
6783         return TRUE;
6784 }
6785
6786 int
6787 Adjudicate(ChessProgramState *cps)
6788 {       // [HGM] some adjudications useful with buggy engines
6789         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6790         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6791         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6792         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6793         int k, count = 0; static int bare = 1;
6794         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6795         Boolean canAdjudicate = !appData.icsActive;
6796
6797         // most tests only when we understand the game, i.e. legality-checking on
6798             if( appData.testLegality )
6799             {   /* [HGM] Some more adjudications for obstinate engines */
6800                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6801                 static int moveCount = 6;
6802                 ChessMove result;
6803                 char *reason = NULL;
6804
6805                 /* Count what is on board. */
6806                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6807
6808                 /* Some material-based adjudications that have to be made before stalemate test */
6809                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6810                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6811                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6812                      if(canAdjudicate && appData.checkMates) {
6813                          if(engineOpponent)
6814                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6815                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6816                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6817                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6818                          return 1;
6819                      }
6820                 }
6821
6822                 /* Bare King in Shatranj (loses) or Losers (wins) */
6823                 if( nrW == 1 || nrB == 1) {
6824                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6825                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6826                      if(canAdjudicate && appData.checkMates) {
6827                          if(engineOpponent)
6828                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6829                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6830                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6831                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6832                          return 1;
6833                      }
6834                   } else
6835                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6836                   {    /* bare King */
6837                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6838                         if(canAdjudicate && appData.checkMates) {
6839                             /* but only adjudicate if adjudication enabled */
6840                             if(engineOpponent)
6841                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6842                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6843                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6844                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6845                             return 1;
6846                         }
6847                   }
6848                 } else bare = 1;
6849
6850
6851             // don't wait for engine to announce game end if we can judge ourselves
6852             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6853               case MT_CHECK:
6854                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6855                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6856                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6857                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6858                             checkCnt++;
6859                         if(checkCnt >= 2) {
6860                             reason = "Xboard adjudication: 3rd check";
6861                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6862                             break;
6863                         }
6864                     }
6865                 }
6866               case MT_NONE:
6867               default:
6868                 break;
6869               case MT_STALEMATE:
6870               case MT_STAINMATE:
6871                 reason = "Xboard adjudication: Stalemate";
6872                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6873                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6874                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6875                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6876                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6877                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6878                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6879                                                                         EP_CHECKMATE : EP_WINS);
6880                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6881                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6882                 }
6883                 break;
6884               case MT_CHECKMATE:
6885                 reason = "Xboard adjudication: Checkmate";
6886                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6887                 break;
6888             }
6889
6890                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6891                     case EP_STALEMATE:
6892                         result = GameIsDrawn; break;
6893                     case EP_CHECKMATE:
6894                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6895                     case EP_WINS:
6896                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6897                     default:
6898                         result = EndOfFile;
6899                 }
6900                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6901                     if(engineOpponent)
6902                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904                     GameEnds( result, reason, GE_XBOARD );
6905                     return 1;
6906                 }
6907
6908                 /* Next absolutely insufficient mating material. */
6909                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6910                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6911                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6912
6913                      /* always flag draws, for judging claims */
6914                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6915
6916                      if(canAdjudicate && appData.materialDraws) {
6917                          /* but only adjudicate them if adjudication enabled */
6918                          if(engineOpponent) {
6919                            SendToProgram("force\n", engineOpponent); // suppress reply
6920                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6921                          }
6922                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6923                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6924                          return 1;
6925                      }
6926                 }
6927
6928                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6929                 if(gameInfo.variant == VariantXiangqi ?
6930                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6931                  : nrW + nrB == 4 &&
6932                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6933                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6934                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6935                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6936                    ) ) {
6937                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6938                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6939                           if(engineOpponent) {
6940                             SendToProgram("force\n", engineOpponent); // suppress reply
6941                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6942                           }
6943                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6944                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6945                           return 1;
6946                      }
6947                 } else moveCount = 6;
6948             }
6949         if (appData.debugMode) { int i;
6950             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6951                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6952                     appData.drawRepeats);
6953             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6954               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6955
6956         }
6957
6958         // Repetition draws and 50-move rule can be applied independently of legality testing
6959
6960                 /* Check for rep-draws */
6961                 count = 0;
6962                 for(k = forwardMostMove-2;
6963                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6964                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6965                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6966                     k-=2)
6967                 {   int rights=0;
6968                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6969                         /* compare castling rights */
6970                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6971                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6972                                 rights++; /* King lost rights, while rook still had them */
6973                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6974                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6975                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6976                                    rights++; /* but at least one rook lost them */
6977                         }
6978                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6979                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6980                                 rights++;
6981                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6982                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6983                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6984                                    rights++;
6985                         }
6986                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6987                             && appData.drawRepeats > 1) {
6988                              /* adjudicate after user-specified nr of repeats */
6989                              int result = GameIsDrawn;
6990                              char *details = "XBoard adjudication: repetition draw";
6991                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6992                                 // [HGM] xiangqi: check for forbidden perpetuals
6993                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6994                                 for(m=forwardMostMove; m>k; m-=2) {
6995                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6996                                         ourPerpetual = 0; // the current mover did not always check
6997                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6998                                         hisPerpetual = 0; // the opponent did not always check
6999                                 }
7000                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7001                                                                         ourPerpetual, hisPerpetual);
7002                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7003                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7004                                     details = "Xboard adjudication: perpetual checking";
7005                                 } else
7006                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7007                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7008                                 } else
7009                                 // Now check for perpetual chases
7010                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7011                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7012                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7013                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7014                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7015                                         details = "Xboard adjudication: perpetual chasing";
7016                                     } else
7017                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7018                                         break; // Abort repetition-checking loop.
7019                                 }
7020                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7021                              }
7022                              if(engineOpponent) {
7023                                SendToProgram("force\n", engineOpponent); // suppress reply
7024                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7025                              }
7026                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7027                              GameEnds( result, details, GE_XBOARD );
7028                              return 1;
7029                         }
7030                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7031                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7032                     }
7033                 }
7034
7035                 /* Now we test for 50-move draws. Determine ply count */
7036                 count = forwardMostMove;
7037                 /* look for last irreversble move */
7038                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7039                     count--;
7040                 /* if we hit starting position, add initial plies */
7041                 if( count == backwardMostMove )
7042                     count -= initialRulePlies;
7043                 count = forwardMostMove - count;
7044                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7045                         // adjust reversible move counter for checks in Xiangqi
7046                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7047                         if(i < backwardMostMove) i = backwardMostMove;
7048                         while(i <= forwardMostMove) {
7049                                 lastCheck = inCheck; // check evasion does not count
7050                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7051                                 if(inCheck || lastCheck) count--; // check does not count
7052                                 i++;
7053                         }
7054                 }
7055                 if( count >= 100)
7056                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7057                          /* this is used to judge if draw claims are legal */
7058                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7059                          if(engineOpponent) {
7060                            SendToProgram("force\n", engineOpponent); // suppress reply
7061                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7062                          }
7063                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7064                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7065                          return 1;
7066                 }
7067
7068                 /* if draw offer is pending, treat it as a draw claim
7069                  * when draw condition present, to allow engines a way to
7070                  * claim draws before making their move to avoid a race
7071                  * condition occurring after their move
7072                  */
7073                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7074                          char *p = NULL;
7075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7076                              p = "Draw claim: 50-move rule";
7077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7078                              p = "Draw claim: 3-fold repetition";
7079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7080                              p = "Draw claim: insufficient mating material";
7081                          if( p != NULL && canAdjudicate) {
7082                              if(engineOpponent) {
7083                                SendToProgram("force\n", engineOpponent); // suppress reply
7084                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7085                              }
7086                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7087                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7088                              return 1;
7089                          }
7090                 }
7091
7092                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7093                     if(engineOpponent) {
7094                       SendToProgram("force\n", engineOpponent); // suppress reply
7095                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7096                     }
7097                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7098                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7099                     return 1;
7100                 }
7101         return 0;
7102 }
7103
7104 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7105 {   // [HGM] book: this routine intercepts moves to simulate book replies
7106     char *bookHit = NULL;
7107
7108     //first determine if the incoming move brings opponent into his book
7109     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7110         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7111     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7112     if(bookHit != NULL && !cps->bookSuspend) {
7113         // make sure opponent is not going to reply after receiving move to book position
7114         SendToProgram("force\n", cps);
7115         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7116     }
7117     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7118     // now arrange restart after book miss
7119     if(bookHit) {
7120         // after a book hit we never send 'go', and the code after the call to this routine
7121         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7122         char buf[MSG_SIZ];
7123         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7124         SendToProgram(buf, cps);
7125         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7126     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7127         SendToProgram("go\n", cps);
7128         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7129     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7130         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7131             SendToProgram("go\n", cps);
7132         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7133     }
7134     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7135 }
7136
7137 char *savedMessage;
7138 ChessProgramState *savedState;
7139 void DeferredBookMove(void)
7140 {
7141         if(savedState->lastPing != savedState->lastPong)
7142                     ScheduleDelayedEvent(DeferredBookMove, 10);
7143         else
7144         HandleMachineMove(savedMessage, savedState);
7145 }
7146
7147 void
7148 HandleMachineMove(message, cps)
7149      char *message;
7150      ChessProgramState *cps;
7151 {
7152     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7153     char realname[MSG_SIZ];
7154     int fromX, fromY, toX, toY;
7155     ChessMove moveType;
7156     char promoChar;
7157     char *p;
7158     int machineWhite;
7159     char *bookHit;
7160
7161     cps->userError = 0;
7162
7163 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7164     /*
7165      * Kludge to ignore BEL characters
7166      */
7167     while (*message == '\007') message++;
7168
7169     /*
7170      * [HGM] engine debug message: ignore lines starting with '#' character
7171      */
7172     if(cps->debug && *message == '#') return;
7173
7174     /*
7175      * Look for book output
7176      */
7177     if (cps == &first && bookRequested) {
7178         if (message[0] == '\t' || message[0] == ' ') {
7179             /* Part of the book output is here; append it */
7180             strcat(bookOutput, message);
7181             strcat(bookOutput, "  \n");
7182             return;
7183         } else if (bookOutput[0] != NULLCHAR) {
7184             /* All of book output has arrived; display it */
7185             char *p = bookOutput;
7186             while (*p != NULLCHAR) {
7187                 if (*p == '\t') *p = ' ';
7188                 p++;
7189             }
7190             DisplayInformation(bookOutput);
7191             bookRequested = FALSE;
7192             /* Fall through to parse the current output */
7193         }
7194     }
7195
7196     /*
7197      * Look for machine move.
7198      */
7199     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7200         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7201     {
7202         /* This method is only useful on engines that support ping */
7203         if (cps->lastPing != cps->lastPong) {
7204           if (gameMode == BeginningOfGame) {
7205             /* Extra move from before last new; ignore */
7206             if (appData.debugMode) {
7207                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7208             }
7209           } else {
7210             if (appData.debugMode) {
7211                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7212                         cps->which, gameMode);
7213             }
7214
7215             SendToProgram("undo\n", cps);
7216           }
7217           return;
7218         }
7219
7220         switch (gameMode) {
7221           case BeginningOfGame:
7222             /* Extra move from before last reset; ignore */
7223             if (appData.debugMode) {
7224                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7225             }
7226             return;
7227
7228           case EndOfGame:
7229           case IcsIdle:
7230           default:
7231             /* Extra move after we tried to stop.  The mode test is
7232                not a reliable way of detecting this problem, but it's
7233                the best we can do on engines that don't support ping.
7234             */
7235             if (appData.debugMode) {
7236                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7237                         cps->which, gameMode);
7238             }
7239             SendToProgram("undo\n", cps);
7240             return;
7241
7242           case MachinePlaysWhite:
7243           case IcsPlayingWhite:
7244             machineWhite = TRUE;
7245             break;
7246
7247           case MachinePlaysBlack:
7248           case IcsPlayingBlack:
7249             machineWhite = FALSE;
7250             break;
7251
7252           case TwoMachinesPlay:
7253             machineWhite = (cps->twoMachinesColor[0] == 'w');
7254             break;
7255         }
7256         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7257             if (appData.debugMode) {
7258                 fprintf(debugFP,
7259                         "Ignoring move out of turn by %s, gameMode %d"
7260                         ", forwardMost %d\n",
7261                         cps->which, gameMode, forwardMostMove);
7262             }
7263             return;
7264         }
7265
7266     if (appData.debugMode) { int f = forwardMostMove;
7267         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7268                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7269                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7270     }
7271         if(cps->alphaRank) AlphaRank(machineMove, 4);
7272         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7273                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7274             /* Machine move could not be parsed; ignore it. */
7275           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7276                     machineMove, cps->which);
7277             DisplayError(buf1, 0);
7278             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7279                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7280             if (gameMode == TwoMachinesPlay) {
7281               GameEnds(machineWhite ? BlackWins : WhiteWins,
7282                        buf1, GE_XBOARD);
7283             }
7284             return;
7285         }
7286
7287         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7288         /* So we have to redo legality test with true e.p. status here,  */
7289         /* to make sure an illegal e.p. capture does not slip through,   */
7290         /* to cause a forfeit on a justified illegal-move complaint      */
7291         /* of the opponent.                                              */
7292         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7293            ChessMove moveType;
7294            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7295                              fromY, fromX, toY, toX, promoChar);
7296             if (appData.debugMode) {
7297                 int i;
7298                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7299                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7300                 fprintf(debugFP, "castling rights\n");
7301             }
7302             if(moveType == IllegalMove) {
7303               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7304                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7305                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7306                            buf1, GE_XBOARD);
7307                 return;
7308            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7309            /* [HGM] Kludge to handle engines that send FRC-style castling
7310               when they shouldn't (like TSCP-Gothic) */
7311            switch(moveType) {
7312              case WhiteASideCastleFR:
7313              case BlackASideCastleFR:
7314                toX+=2;
7315                currentMoveString[2]++;
7316                break;
7317              case WhiteHSideCastleFR:
7318              case BlackHSideCastleFR:
7319                toX--;
7320                currentMoveString[2]--;
7321                break;
7322              default: ; // nothing to do, but suppresses warning of pedantic compilers
7323            }
7324         }
7325         hintRequested = FALSE;
7326         lastHint[0] = NULLCHAR;
7327         bookRequested = FALSE;
7328         /* Program may be pondering now */
7329         cps->maybeThinking = TRUE;
7330         if (cps->sendTime == 2) cps->sendTime = 1;
7331         if (cps->offeredDraw) cps->offeredDraw--;
7332
7333         /* [AS] Save move info*/
7334         pvInfoList[ forwardMostMove ].score = programStats.score;
7335         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7336         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7337
7338         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7339
7340         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7341         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7342             int count = 0;
7343
7344             while( count < adjudicateLossPlies ) {
7345                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7346
7347                 if( count & 1 ) {
7348                     score = -score; /* Flip score for winning side */
7349                 }
7350
7351                 if( score > adjudicateLossThreshold ) {
7352                     break;
7353                 }
7354
7355                 count++;
7356             }
7357
7358             if( count >= adjudicateLossPlies ) {
7359                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7360
7361                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7362                     "Xboard adjudication",
7363                     GE_XBOARD );
7364
7365                 return;
7366             }
7367         }
7368
7369         if(Adjudicate(cps)) {
7370             DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
7371             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7372             return; // [HGM] adjudicate: for all automatic game ends
7373         }
7374
7375 #if ZIPPY
7376         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7377             first.initDone) {
7378           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7379                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7380                 SendToICS("draw ");
7381                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7382           }
7383           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7384           ics_user_moved = 1;
7385           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7386                 char buf[3*MSG_SIZ];
7387
7388                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7389                         programStats.score / 100.,
7390                         programStats.depth,
7391                         programStats.time / 100.,
7392                         (unsigned int)programStats.nodes,
7393                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7394                         programStats.movelist);
7395                 SendToICS(buf);
7396 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7397           }
7398         }
7399 #endif
7400
7401         /* [AS] Clear stats for next move */
7402         ClearProgramStats();
7403         thinkOutput[0] = NULLCHAR;
7404         hiddenThinkOutputState = 0;
7405
7406         bookHit = NULL;
7407         if (gameMode == TwoMachinesPlay) {
7408             /* [HGM] relaying draw offers moved to after reception of move */
7409             /* and interpreting offer as claim if it brings draw condition */
7410             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7411                 SendToProgram("draw\n", cps->other);
7412             }
7413             if (cps->other->sendTime) {
7414                 SendTimeRemaining(cps->other,
7415                                   cps->other->twoMachinesColor[0] == 'w');
7416             }
7417             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7418             if (firstMove && !bookHit) {
7419                 firstMove = FALSE;
7420                 if (cps->other->useColors) {
7421                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7422                 }
7423                 SendToProgram("go\n", cps->other);
7424             }
7425             cps->other->maybeThinking = TRUE;
7426         }
7427
7428         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7429
7430         if (!pausing && appData.ringBellAfterMoves) {
7431             RingBell();
7432         }
7433
7434         /*
7435          * Reenable menu items that were disabled while
7436          * machine was thinking
7437          */
7438         if (gameMode != TwoMachinesPlay)
7439             SetUserThinkingEnables();
7440
7441         // [HGM] book: after book hit opponent has received move and is now in force mode
7442         // force the book reply into it, and then fake that it outputted this move by jumping
7443         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7444         if(bookHit) {
7445                 static char bookMove[MSG_SIZ]; // a bit generous?
7446
7447                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7448                 strcat(bookMove, bookHit);
7449                 message = bookMove;
7450                 cps = cps->other;
7451                 programStats.nodes = programStats.depth = programStats.time =
7452                 programStats.score = programStats.got_only_move = 0;
7453                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7454
7455                 if(cps->lastPing != cps->lastPong) {
7456                     savedMessage = message; // args for deferred call
7457                     savedState = cps;
7458                     ScheduleDelayedEvent(DeferredBookMove, 10);
7459                     return;
7460                 }
7461                 goto FakeBookMove;
7462         }
7463
7464         return;
7465     }
7466
7467     /* Set special modes for chess engines.  Later something general
7468      *  could be added here; for now there is just one kludge feature,
7469      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7470      *  when "xboard" is given as an interactive command.
7471      */
7472     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7473         cps->useSigint = FALSE;
7474         cps->useSigterm = FALSE;
7475     }
7476     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7477       ParseFeatures(message+8, cps);
7478       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7479     }
7480
7481     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7482       int dummy, s=6; char buf[MSG_SIZ];
7483       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7484       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7485       ParseFEN(boards[0], &dummy, message+s);
7486       DrawPosition(TRUE, boards[0]);
7487       startedFromSetupPosition = TRUE;
7488       return;
7489     }
7490     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7491      * want this, I was asked to put it in, and obliged.
7492      */
7493     if (!strncmp(message, "setboard ", 9)) {
7494         Board initial_position;
7495
7496         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7497
7498         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7499             DisplayError(_("Bad FEN received from engine"), 0);
7500             return ;
7501         } else {
7502            Reset(TRUE, FALSE);
7503            CopyBoard(boards[0], initial_position);
7504            initialRulePlies = FENrulePlies;
7505            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7506            else gameMode = MachinePlaysBlack;
7507            DrawPosition(FALSE, boards[currentMove]);
7508         }
7509         return;
7510     }
7511
7512     /*
7513      * Look for communication commands
7514      */
7515     if (!strncmp(message, "telluser ", 9)) {
7516         if(message[9] == '\\' && message[10] == '\\')
7517             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7518         DisplayNote(message + 9);
7519         return;
7520     }
7521     if (!strncmp(message, "tellusererror ", 14)) {
7522         cps->userError = 1;
7523         if(message[14] == '\\' && message[15] == '\\')
7524             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7525         DisplayError(message + 14, 0);
7526         return;
7527     }
7528     if (!strncmp(message, "tellopponent ", 13)) {
7529       if (appData.icsActive) {
7530         if (loggedOn) {
7531           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7532           SendToICS(buf1);
7533         }
7534       } else {
7535         DisplayNote(message + 13);
7536       }
7537       return;
7538     }
7539     if (!strncmp(message, "tellothers ", 11)) {
7540       if (appData.icsActive) {
7541         if (loggedOn) {
7542           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7543           SendToICS(buf1);
7544         }
7545       }
7546       return;
7547     }
7548     if (!strncmp(message, "tellall ", 8)) {
7549       if (appData.icsActive) {
7550         if (loggedOn) {
7551           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7552           SendToICS(buf1);
7553         }
7554       } else {
7555         DisplayNote(message + 8);
7556       }
7557       return;
7558     }
7559     if (strncmp(message, "warning", 7) == 0) {
7560         /* Undocumented feature, use tellusererror in new code */
7561         DisplayError(message, 0);
7562         return;
7563     }
7564     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7565         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7566         strcat(realname, " query");
7567         AskQuestion(realname, buf2, buf1, cps->pr);
7568         return;
7569     }
7570     /* Commands from the engine directly to ICS.  We don't allow these to be
7571      *  sent until we are logged on. Crafty kibitzes have been known to
7572      *  interfere with the login process.
7573      */
7574     if (loggedOn) {
7575         if (!strncmp(message, "tellics ", 8)) {
7576             SendToICS(message + 8);
7577             SendToICS("\n");
7578             return;
7579         }
7580         if (!strncmp(message, "tellicsnoalias ", 15)) {
7581             SendToICS(ics_prefix);
7582             SendToICS(message + 15);
7583             SendToICS("\n");
7584             return;
7585         }
7586         /* The following are for backward compatibility only */
7587         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7588             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7589             SendToICS(ics_prefix);
7590             SendToICS(message);
7591             SendToICS("\n");
7592             return;
7593         }
7594     }
7595     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7596         return;
7597     }
7598     /*
7599      * If the move is illegal, cancel it and redraw the board.
7600      * Also deal with other error cases.  Matching is rather loose
7601      * here to accommodate engines written before the spec.
7602      */
7603     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7604         strncmp(message, "Error", 5) == 0) {
7605         if (StrStr(message, "name") ||
7606             StrStr(message, "rating") || StrStr(message, "?") ||
7607             StrStr(message, "result") || StrStr(message, "board") ||
7608             StrStr(message, "bk") || StrStr(message, "computer") ||
7609             StrStr(message, "variant") || StrStr(message, "hint") ||
7610             StrStr(message, "random") || StrStr(message, "depth") ||
7611             StrStr(message, "accepted")) {
7612             return;
7613         }
7614         if (StrStr(message, "protover")) {
7615           /* Program is responding to input, so it's apparently done
7616              initializing, and this error message indicates it is
7617              protocol version 1.  So we don't need to wait any longer
7618              for it to initialize and send feature commands. */
7619           FeatureDone(cps, 1);
7620           cps->protocolVersion = 1;
7621           return;
7622         }
7623         cps->maybeThinking = FALSE;
7624
7625         if (StrStr(message, "draw")) {
7626             /* Program doesn't have "draw" command */
7627             cps->sendDrawOffers = 0;
7628             return;
7629         }
7630         if (cps->sendTime != 1 &&
7631             (StrStr(message, "time") || StrStr(message, "otim"))) {
7632           /* Program apparently doesn't have "time" or "otim" command */
7633           cps->sendTime = 0;
7634           return;
7635         }
7636         if (StrStr(message, "analyze")) {
7637             cps->analysisSupport = FALSE;
7638             cps->analyzing = FALSE;
7639             Reset(FALSE, TRUE);
7640             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7641             DisplayError(buf2, 0);
7642             return;
7643         }
7644         if (StrStr(message, "(no matching move)st")) {
7645           /* Special kludge for GNU Chess 4 only */
7646           cps->stKludge = TRUE;
7647           SendTimeControl(cps, movesPerSession, timeControl,
7648                           timeIncrement, appData.searchDepth,
7649                           searchTime);
7650           return;
7651         }
7652         if (StrStr(message, "(no matching move)sd")) {
7653           /* Special kludge for GNU Chess 4 only */
7654           cps->sdKludge = TRUE;
7655           SendTimeControl(cps, movesPerSession, timeControl,
7656                           timeIncrement, appData.searchDepth,
7657                           searchTime);
7658           return;
7659         }
7660         if (!StrStr(message, "llegal")) {
7661             return;
7662         }
7663         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7664             gameMode == IcsIdle) return;
7665         if (forwardMostMove <= backwardMostMove) return;
7666         if (pausing) PauseEvent();
7667       if(appData.forceIllegal) {
7668             // [HGM] illegal: machine refused move; force position after move into it
7669           SendToProgram("force\n", cps);
7670           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7671                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7672                 // when black is to move, while there might be nothing on a2 or black
7673                 // might already have the move. So send the board as if white has the move.
7674                 // But first we must change the stm of the engine, as it refused the last move
7675                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7676                 if(WhiteOnMove(forwardMostMove)) {
7677                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7678                     SendBoard(cps, forwardMostMove); // kludgeless board
7679                 } else {
7680                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7681                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7682                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7683                 }
7684           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7685             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7686                  gameMode == TwoMachinesPlay)
7687               SendToProgram("go\n", cps);
7688             return;
7689       } else
7690         if (gameMode == PlayFromGameFile) {
7691             /* Stop reading this game file */
7692             gameMode = EditGame;
7693             ModeHighlight();
7694         }
7695         /* [HGM] illegal-move claim should forfeit game when Xboard */
7696         /* only passes fully legal moves                            */
7697         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7698             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7699                                 "False illegal-move claim", GE_XBOARD );
7700             return; // do not take back move we tested as valid
7701         }
7702         currentMove = forwardMostMove-1;
7703         DisplayMove(currentMove-1); /* before DisplayMoveError */
7704         SwitchClocks(forwardMostMove-1); // [HGM] race
7705         DisplayBothClocks();
7706         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7707                 parseList[currentMove], cps->which);
7708         DisplayMoveError(buf1);
7709         DrawPosition(FALSE, boards[currentMove]);
7710         return;
7711     }
7712     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7713         /* Program has a broken "time" command that
7714            outputs a string not ending in newline.
7715            Don't use it. */
7716         cps->sendTime = 0;
7717     }
7718
7719     /*
7720      * If chess program startup fails, exit with an error message.
7721      * Attempts to recover here are futile.
7722      */
7723     if ((StrStr(message, "unknown host") != NULL)
7724         || (StrStr(message, "No remote directory") != NULL)
7725         || (StrStr(message, "not found") != NULL)
7726         || (StrStr(message, "No such file") != NULL)
7727         || (StrStr(message, "can't alloc") != NULL)
7728         || (StrStr(message, "Permission denied") != NULL)) {
7729
7730         cps->maybeThinking = FALSE;
7731         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7732                 cps->which, cps->program, cps->host, message);
7733         RemoveInputSource(cps->isr);
7734         DisplayFatalError(buf1, 0, 1);
7735         return;
7736     }
7737
7738     /*
7739      * Look for hint output
7740      */
7741     if (sscanf(message, "Hint: %s", buf1) == 1) {
7742         if (cps == &first && hintRequested) {
7743             hintRequested = FALSE;
7744             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7745                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7746                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7747                                     PosFlags(forwardMostMove),
7748                                     fromY, fromX, toY, toX, promoChar, buf1);
7749                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7750                 DisplayInformation(buf2);
7751             } else {
7752                 /* Hint move could not be parsed!? */
7753               snprintf(buf2, sizeof(buf2),
7754                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7755                         buf1, cps->which);
7756                 DisplayError(buf2, 0);
7757             }
7758         } else {
7759           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7760         }
7761         return;
7762     }
7763
7764     /*
7765      * Ignore other messages if game is not in progress
7766      */
7767     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7768         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7769
7770     /*
7771      * look for win, lose, draw, or draw offer
7772      */
7773     if (strncmp(message, "1-0", 3) == 0) {
7774         char *p, *q, *r = "";
7775         p = strchr(message, '{');
7776         if (p) {
7777             q = strchr(p, '}');
7778             if (q) {
7779                 *q = NULLCHAR;
7780                 r = p + 1;
7781             }
7782         }
7783         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7784         return;
7785     } else if (strncmp(message, "0-1", 3) == 0) {
7786         char *p, *q, *r = "";
7787         p = strchr(message, '{');
7788         if (p) {
7789             q = strchr(p, '}');
7790             if (q) {
7791                 *q = NULLCHAR;
7792                 r = p + 1;
7793             }
7794         }
7795         /* Kludge for Arasan 4.1 bug */
7796         if (strcmp(r, "Black resigns") == 0) {
7797             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7798             return;
7799         }
7800         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7801         return;
7802     } else if (strncmp(message, "1/2", 3) == 0) {
7803         char *p, *q, *r = "";
7804         p = strchr(message, '{');
7805         if (p) {
7806             q = strchr(p, '}');
7807             if (q) {
7808                 *q = NULLCHAR;
7809                 r = p + 1;
7810             }
7811         }
7812
7813         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7814         return;
7815
7816     } else if (strncmp(message, "White resign", 12) == 0) {
7817         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7818         return;
7819     } else if (strncmp(message, "Black resign", 12) == 0) {
7820         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7821         return;
7822     } else if (strncmp(message, "White matches", 13) == 0 ||
7823                strncmp(message, "Black matches", 13) == 0   ) {
7824         /* [HGM] ignore GNUShogi noises */
7825         return;
7826     } else if (strncmp(message, "White", 5) == 0 &&
7827                message[5] != '(' &&
7828                StrStr(message, "Black") == NULL) {
7829         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7830         return;
7831     } else if (strncmp(message, "Black", 5) == 0 &&
7832                message[5] != '(') {
7833         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7834         return;
7835     } else if (strcmp(message, "resign") == 0 ||
7836                strcmp(message, "computer resigns") == 0) {
7837         switch (gameMode) {
7838           case MachinePlaysBlack:
7839           case IcsPlayingBlack:
7840             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7841             break;
7842           case MachinePlaysWhite:
7843           case IcsPlayingWhite:
7844             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7845             break;
7846           case TwoMachinesPlay:
7847             if (cps->twoMachinesColor[0] == 'w')
7848               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7849             else
7850               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7851             break;
7852           default:
7853             /* can't happen */
7854             break;
7855         }
7856         return;
7857     } else if (strncmp(message, "opponent mates", 14) == 0) {
7858         switch (gameMode) {
7859           case MachinePlaysBlack:
7860           case IcsPlayingBlack:
7861             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7862             break;
7863           case MachinePlaysWhite:
7864           case IcsPlayingWhite:
7865             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7866             break;
7867           case TwoMachinesPlay:
7868             if (cps->twoMachinesColor[0] == 'w')
7869               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7870             else
7871               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7872             break;
7873           default:
7874             /* can't happen */
7875             break;
7876         }
7877         return;
7878     } else if (strncmp(message, "computer mates", 14) == 0) {
7879         switch (gameMode) {
7880           case MachinePlaysBlack:
7881           case IcsPlayingBlack:
7882             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7883             break;
7884           case MachinePlaysWhite:
7885           case IcsPlayingWhite:
7886             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7887             break;
7888           case TwoMachinesPlay:
7889             if (cps->twoMachinesColor[0] == 'w')
7890               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7891             else
7892               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7893             break;
7894           default:
7895             /* can't happen */
7896             break;
7897         }
7898         return;
7899     } else if (strncmp(message, "checkmate", 9) == 0) {
7900         if (WhiteOnMove(forwardMostMove)) {
7901             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7902         } else {
7903             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7904         }
7905         return;
7906     } else if (strstr(message, "Draw") != NULL ||
7907                strstr(message, "game is a draw") != NULL) {
7908         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7909         return;
7910     } else if (strstr(message, "offer") != NULL &&
7911                strstr(message, "draw") != NULL) {
7912 #if ZIPPY
7913         if (appData.zippyPlay && first.initDone) {
7914             /* Relay offer to ICS */
7915             SendToICS(ics_prefix);
7916             SendToICS("draw\n");
7917         }
7918 #endif
7919         cps->offeredDraw = 2; /* valid until this engine moves twice */
7920         if (gameMode == TwoMachinesPlay) {
7921             if (cps->other->offeredDraw) {
7922                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7923             /* [HGM] in two-machine mode we delay relaying draw offer      */
7924             /* until after we also have move, to see if it is really claim */
7925             }
7926         } else if (gameMode == MachinePlaysWhite ||
7927                    gameMode == MachinePlaysBlack) {
7928           if (userOfferedDraw) {
7929             DisplayInformation(_("Machine accepts your draw offer"));
7930             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7931           } else {
7932             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7933           }
7934         }
7935     }
7936
7937
7938     /*
7939      * Look for thinking output
7940      */
7941     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7942           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7943                                 ) {
7944         int plylev, mvleft, mvtot, curscore, time;
7945         char mvname[MOVE_LEN];
7946         u64 nodes; // [DM]
7947         char plyext;
7948         int ignore = FALSE;
7949         int prefixHint = FALSE;
7950         mvname[0] = NULLCHAR;
7951
7952         switch (gameMode) {
7953           case MachinePlaysBlack:
7954           case IcsPlayingBlack:
7955             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7956             break;
7957           case MachinePlaysWhite:
7958           case IcsPlayingWhite:
7959             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7960             break;
7961           case AnalyzeMode:
7962           case AnalyzeFile:
7963             break;
7964           case IcsObserving: /* [DM] icsEngineAnalyze */
7965             if (!appData.icsEngineAnalyze) ignore = TRUE;
7966             break;
7967           case TwoMachinesPlay:
7968             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7969                 ignore = TRUE;
7970             }
7971             break;
7972           default:
7973             ignore = TRUE;
7974             break;
7975         }
7976
7977         if (!ignore) {
7978             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7979             buf1[0] = NULLCHAR;
7980             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7981                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7982
7983                 if (plyext != ' ' && plyext != '\t') {
7984                     time *= 100;
7985                 }
7986
7987                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7988                 if( cps->scoreIsAbsolute &&
7989                     ( gameMode == MachinePlaysBlack ||
7990                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7991                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7992                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7993                      !WhiteOnMove(currentMove)
7994                     ) )
7995                 {
7996                     curscore = -curscore;
7997                 }
7998
7999
8000                 tempStats.depth = plylev;
8001                 tempStats.nodes = nodes;
8002                 tempStats.time = time;
8003                 tempStats.score = curscore;
8004                 tempStats.got_only_move = 0;
8005
8006                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8007                         int ticklen;
8008
8009                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8010                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8011                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8012                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8013                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8014                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8015                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8016                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8017                 }
8018
8019                 /* Buffer overflow protection */
8020                 if (buf1[0] != NULLCHAR) {
8021                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8022                         && appData.debugMode) {
8023                         fprintf(debugFP,
8024                                 "PV is too long; using the first %u bytes.\n",
8025                                 (unsigned) sizeof(tempStats.movelist) - 1);
8026                     }
8027
8028                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8029                 } else {
8030                     sprintf(tempStats.movelist, " no PV\n");
8031                 }
8032
8033                 if (tempStats.seen_stat) {
8034                     tempStats.ok_to_send = 1;
8035                 }
8036
8037                 if (strchr(tempStats.movelist, '(') != NULL) {
8038                     tempStats.line_is_book = 1;
8039                     tempStats.nr_moves = 0;
8040                     tempStats.moves_left = 0;
8041                 } else {
8042                     tempStats.line_is_book = 0;
8043                 }
8044
8045                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8046                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8047
8048                 SendProgramStatsToFrontend( cps, &tempStats );
8049
8050                 /*
8051                     [AS] Protect the thinkOutput buffer from overflow... this
8052                     is only useful if buf1 hasn't overflowed first!
8053                 */
8054                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8055                          plylev,
8056                          (gameMode == TwoMachinesPlay ?
8057                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8058                          ((double) curscore) / 100.0,
8059                          prefixHint ? lastHint : "",
8060                          prefixHint ? " " : "" );
8061
8062                 if( buf1[0] != NULLCHAR ) {
8063                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8064
8065                     if( strlen(buf1) > max_len ) {
8066                         if( appData.debugMode) {
8067                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8068                         }
8069                         buf1[max_len+1] = '\0';
8070                     }
8071
8072                     strcat( thinkOutput, buf1 );
8073                 }
8074
8075                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8076                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8077                     DisplayMove(currentMove - 1);
8078                 }
8079                 return;
8080
8081             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8082                 /* crafty (9.25+) says "(only move) <move>"
8083                  * if there is only 1 legal move
8084                  */
8085                 sscanf(p, "(only move) %s", buf1);
8086                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8087                 sprintf(programStats.movelist, "%s (only move)", buf1);
8088                 programStats.depth = 1;
8089                 programStats.nr_moves = 1;
8090                 programStats.moves_left = 1;
8091                 programStats.nodes = 1;
8092                 programStats.time = 1;
8093                 programStats.got_only_move = 1;
8094
8095                 /* Not really, but we also use this member to
8096                    mean "line isn't going to change" (Crafty
8097                    isn't searching, so stats won't change) */
8098                 programStats.line_is_book = 1;
8099
8100                 SendProgramStatsToFrontend( cps, &programStats );
8101
8102                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8103                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8104                     DisplayMove(currentMove - 1);
8105                 }
8106                 return;
8107             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8108                               &time, &nodes, &plylev, &mvleft,
8109                               &mvtot, mvname) >= 5) {
8110                 /* The stat01: line is from Crafty (9.29+) in response
8111                    to the "." command */
8112                 programStats.seen_stat = 1;
8113                 cps->maybeThinking = TRUE;
8114
8115                 if (programStats.got_only_move || !appData.periodicUpdates)
8116                   return;
8117
8118                 programStats.depth = plylev;
8119                 programStats.time = time;
8120                 programStats.nodes = nodes;
8121                 programStats.moves_left = mvleft;
8122                 programStats.nr_moves = mvtot;
8123                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8124                 programStats.ok_to_send = 1;
8125                 programStats.movelist[0] = '\0';
8126
8127                 SendProgramStatsToFrontend( cps, &programStats );
8128
8129                 return;
8130
8131             } else if (strncmp(message,"++",2) == 0) {
8132                 /* Crafty 9.29+ outputs this */
8133                 programStats.got_fail = 2;
8134                 return;
8135
8136             } else if (strncmp(message,"--",2) == 0) {
8137                 /* Crafty 9.29+ outputs this */
8138                 programStats.got_fail = 1;
8139                 return;
8140
8141             } else if (thinkOutput[0] != NULLCHAR &&
8142                        strncmp(message, "    ", 4) == 0) {
8143                 unsigned message_len;
8144
8145                 p = message;
8146                 while (*p && *p == ' ') p++;
8147
8148                 message_len = strlen( p );
8149
8150                 /* [AS] Avoid buffer overflow */
8151                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8152                     strcat(thinkOutput, " ");
8153                     strcat(thinkOutput, p);
8154                 }
8155
8156                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8157                     strcat(programStats.movelist, " ");
8158                     strcat(programStats.movelist, p);
8159                 }
8160
8161                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8162                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8163                     DisplayMove(currentMove - 1);
8164                 }
8165                 return;
8166             }
8167         }
8168         else {
8169             buf1[0] = NULLCHAR;
8170
8171             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8172                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8173             {
8174                 ChessProgramStats cpstats;
8175
8176                 if (plyext != ' ' && plyext != '\t') {
8177                     time *= 100;
8178                 }
8179
8180                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8181                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8182                     curscore = -curscore;
8183                 }
8184
8185                 cpstats.depth = plylev;
8186                 cpstats.nodes = nodes;
8187                 cpstats.time = time;
8188                 cpstats.score = curscore;
8189                 cpstats.got_only_move = 0;
8190                 cpstats.movelist[0] = '\0';
8191
8192                 if (buf1[0] != NULLCHAR) {
8193                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8194                 }
8195
8196                 cpstats.ok_to_send = 0;
8197                 cpstats.line_is_book = 0;
8198                 cpstats.nr_moves = 0;
8199                 cpstats.moves_left = 0;
8200
8201                 SendProgramStatsToFrontend( cps, &cpstats );
8202             }
8203         }
8204     }
8205 }
8206
8207
8208 /* Parse a game score from the character string "game", and
8209    record it as the history of the current game.  The game
8210    score is NOT assumed to start from the standard position.
8211    The display is not updated in any way.
8212    */
8213 void
8214 ParseGameHistory(game)
8215      char *game;
8216 {
8217     ChessMove moveType;
8218     int fromX, fromY, toX, toY, boardIndex;
8219     char promoChar;
8220     char *p, *q;
8221     char buf[MSG_SIZ];
8222
8223     if (appData.debugMode)
8224       fprintf(debugFP, "Parsing game history: %s\n", game);
8225
8226     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8227     gameInfo.site = StrSave(appData.icsHost);
8228     gameInfo.date = PGNDate();
8229     gameInfo.round = StrSave("-");
8230
8231     /* Parse out names of players */
8232     while (*game == ' ') game++;
8233     p = buf;
8234     while (*game != ' ') *p++ = *game++;
8235     *p = NULLCHAR;
8236     gameInfo.white = StrSave(buf);
8237     while (*game == ' ') game++;
8238     p = buf;
8239     while (*game != ' ' && *game != '\n') *p++ = *game++;
8240     *p = NULLCHAR;
8241     gameInfo.black = StrSave(buf);
8242
8243     /* Parse moves */
8244     boardIndex = blackPlaysFirst ? 1 : 0;
8245     yynewstr(game);
8246     for (;;) {
8247         yyboardindex = boardIndex;
8248         moveType = (ChessMove) Myylex();
8249         switch (moveType) {
8250           case IllegalMove:             /* maybe suicide chess, etc. */
8251   if (appData.debugMode) {
8252     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8253     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8254     setbuf(debugFP, NULL);
8255   }
8256           case WhitePromotion:
8257           case BlackPromotion:
8258           case WhiteNonPromotion:
8259           case BlackNonPromotion:
8260           case NormalMove:
8261           case WhiteCapturesEnPassant:
8262           case BlackCapturesEnPassant:
8263           case WhiteKingSideCastle:
8264           case WhiteQueenSideCastle:
8265           case BlackKingSideCastle:
8266           case BlackQueenSideCastle:
8267           case WhiteKingSideCastleWild:
8268           case WhiteQueenSideCastleWild:
8269           case BlackKingSideCastleWild:
8270           case BlackQueenSideCastleWild:
8271           /* PUSH Fabien */
8272           case WhiteHSideCastleFR:
8273           case WhiteASideCastleFR:
8274           case BlackHSideCastleFR:
8275           case BlackASideCastleFR:
8276           /* POP Fabien */
8277             fromX = currentMoveString[0] - AAA;
8278             fromY = currentMoveString[1] - ONE;
8279             toX = currentMoveString[2] - AAA;
8280             toY = currentMoveString[3] - ONE;
8281             promoChar = currentMoveString[4];
8282             break;
8283           case WhiteDrop:
8284           case BlackDrop:
8285             fromX = moveType == WhiteDrop ?
8286               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8287             (int) CharToPiece(ToLower(currentMoveString[0]));
8288             fromY = DROP_RANK;
8289             toX = currentMoveString[2] - AAA;
8290             toY = currentMoveString[3] - ONE;
8291             promoChar = NULLCHAR;
8292             break;
8293           case AmbiguousMove:
8294             /* bug? */
8295             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8296   if (appData.debugMode) {
8297     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8298     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8299     setbuf(debugFP, NULL);
8300   }
8301             DisplayError(buf, 0);
8302             return;
8303           case ImpossibleMove:
8304             /* bug? */
8305             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8306   if (appData.debugMode) {
8307     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8308     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8309     setbuf(debugFP, NULL);
8310   }
8311             DisplayError(buf, 0);
8312             return;
8313           case EndOfFile:
8314             if (boardIndex < backwardMostMove) {
8315                 /* Oops, gap.  How did that happen? */
8316                 DisplayError(_("Gap in move list"), 0);
8317                 return;
8318             }
8319             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8320             if (boardIndex > forwardMostMove) {
8321                 forwardMostMove = boardIndex;
8322             }
8323             return;
8324           case ElapsedTime:
8325             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8326                 strcat(parseList[boardIndex-1], " ");
8327                 strcat(parseList[boardIndex-1], yy_text);
8328             }
8329             continue;
8330           case Comment:
8331           case PGNTag:
8332           case NAG:
8333           default:
8334             /* ignore */
8335             continue;
8336           case WhiteWins:
8337           case BlackWins:
8338           case GameIsDrawn:
8339           case GameUnfinished:
8340             if (gameMode == IcsExamining) {
8341                 if (boardIndex < backwardMostMove) {
8342                     /* Oops, gap.  How did that happen? */
8343                     return;
8344                 }
8345                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8346                 return;
8347             }
8348             gameInfo.result = moveType;
8349             p = strchr(yy_text, '{');
8350             if (p == NULL) p = strchr(yy_text, '(');
8351             if (p == NULL) {
8352                 p = yy_text;
8353                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8354             } else {
8355                 q = strchr(p, *p == '{' ? '}' : ')');
8356                 if (q != NULL) *q = NULLCHAR;
8357                 p++;
8358             }
8359             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8360             gameInfo.resultDetails = StrSave(p);
8361             continue;
8362         }
8363         if (boardIndex >= forwardMostMove &&
8364             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8365             backwardMostMove = blackPlaysFirst ? 1 : 0;
8366             return;
8367         }
8368         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8369                                  fromY, fromX, toY, toX, promoChar,
8370                                  parseList[boardIndex]);
8371         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8372         /* currentMoveString is set as a side-effect of yylex */
8373         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8374         strcat(moveList[boardIndex], "\n");
8375         boardIndex++;
8376         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8377         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8378           case MT_NONE:
8379           case MT_STALEMATE:
8380           default:
8381             break;
8382           case MT_CHECK:
8383             if(gameInfo.variant != VariantShogi)
8384                 strcat(parseList[boardIndex - 1], "+");
8385             break;
8386           case MT_CHECKMATE:
8387           case MT_STAINMATE:
8388             strcat(parseList[boardIndex - 1], "#");
8389             break;
8390         }
8391     }
8392 }
8393
8394
8395 /* Apply a move to the given board  */
8396 void
8397 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8398      int fromX, fromY, toX, toY;
8399      int promoChar;
8400      Board board;
8401 {
8402   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8403   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8404
8405     /* [HGM] compute & store e.p. status and castling rights for new position */
8406     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8407
8408       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8409       oldEP = (signed char)board[EP_STATUS];
8410       board[EP_STATUS] = EP_NONE;
8411
8412       if( board[toY][toX] != EmptySquare )
8413            board[EP_STATUS] = EP_CAPTURE;
8414
8415   if (fromY == DROP_RANK) {
8416         /* must be first */
8417         piece = board[toY][toX] = (ChessSquare) fromX;
8418   } else {
8419       int i;
8420
8421       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8422            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8423                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8424       } else
8425       if( board[fromY][fromX] == WhitePawn ) {
8426            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8427                board[EP_STATUS] = EP_PAWN_MOVE;
8428            if( toY-fromY==2) {
8429                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8430                         gameInfo.variant != VariantBerolina || toX < fromX)
8431                       board[EP_STATUS] = toX | berolina;
8432                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8433                         gameInfo.variant != VariantBerolina || toX > fromX)
8434                       board[EP_STATUS] = toX;
8435            }
8436       } else
8437       if( board[fromY][fromX] == BlackPawn ) {
8438            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8439                board[EP_STATUS] = EP_PAWN_MOVE;
8440            if( toY-fromY== -2) {
8441                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8442                         gameInfo.variant != VariantBerolina || toX < fromX)
8443                       board[EP_STATUS] = toX | berolina;
8444                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8445                         gameInfo.variant != VariantBerolina || toX > fromX)
8446                       board[EP_STATUS] = toX;
8447            }
8448        }
8449
8450        for(i=0; i<nrCastlingRights; i++) {
8451            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8452               board[CASTLING][i] == toX   && castlingRank[i] == toY
8453              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8454        }
8455
8456      if (fromX == toX && fromY == toY) return;
8457
8458      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8459      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8460      if(gameInfo.variant == VariantKnightmate)
8461          king += (int) WhiteUnicorn - (int) WhiteKing;
8462
8463     /* Code added by Tord: */
8464     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8465     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8466         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8467       board[fromY][fromX] = EmptySquare;
8468       board[toY][toX] = EmptySquare;
8469       if((toX > fromX) != (piece == WhiteRook)) {
8470         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8471       } else {
8472         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8473       }
8474     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8475                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8476       board[fromY][fromX] = EmptySquare;
8477       board[toY][toX] = EmptySquare;
8478       if((toX > fromX) != (piece == BlackRook)) {
8479         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8480       } else {
8481         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8482       }
8483     /* End of code added by Tord */
8484
8485     } else if (board[fromY][fromX] == king
8486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8487         && toY == fromY && toX > fromX+1) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = king;
8490         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8491         board[fromY][BOARD_RGHT-1] = EmptySquare;
8492     } else if (board[fromY][fromX] == king
8493         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8494                && toY == fromY && toX < fromX-1) {
8495         board[fromY][fromX] = EmptySquare;
8496         board[toY][toX] = king;
8497         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8498         board[fromY][BOARD_LEFT] = EmptySquare;
8499     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8500                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8501                && toY >= BOARD_HEIGHT-promoRank
8502                ) {
8503         /* white pawn promotion */
8504         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8505         if (board[toY][toX] == EmptySquare) {
8506             board[toY][toX] = WhiteQueen;
8507         }
8508         if(gameInfo.variant==VariantBughouse ||
8509            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8510             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8511         board[fromY][fromX] = EmptySquare;
8512     } else if ((fromY == BOARD_HEIGHT-4)
8513                && (toX != fromX)
8514                && gameInfo.variant != VariantXiangqi
8515                && gameInfo.variant != VariantBerolina
8516                && (board[fromY][fromX] == WhitePawn)
8517                && (board[toY][toX] == EmptySquare)) {
8518         board[fromY][fromX] = EmptySquare;
8519         board[toY][toX] = WhitePawn;
8520         captured = board[toY - 1][toX];
8521         board[toY - 1][toX] = EmptySquare;
8522     } else if ((fromY == BOARD_HEIGHT-4)
8523                && (toX == fromX)
8524                && gameInfo.variant == VariantBerolina
8525                && (board[fromY][fromX] == WhitePawn)
8526                && (board[toY][toX] == EmptySquare)) {
8527         board[fromY][fromX] = EmptySquare;
8528         board[toY][toX] = WhitePawn;
8529         if(oldEP & EP_BEROLIN_A) {
8530                 captured = board[fromY][fromX-1];
8531                 board[fromY][fromX-1] = EmptySquare;
8532         }else{  captured = board[fromY][fromX+1];
8533                 board[fromY][fromX+1] = EmptySquare;
8534         }
8535     } else if (board[fromY][fromX] == king
8536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8537                && toY == fromY && toX > fromX+1) {
8538         board[fromY][fromX] = EmptySquare;
8539         board[toY][toX] = king;
8540         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8541         board[fromY][BOARD_RGHT-1] = EmptySquare;
8542     } else if (board[fromY][fromX] == king
8543         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8544                && toY == fromY && toX < fromX-1) {
8545         board[fromY][fromX] = EmptySquare;
8546         board[toY][toX] = king;
8547         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8548         board[fromY][BOARD_LEFT] = EmptySquare;
8549     } else if (fromY == 7 && fromX == 3
8550                && board[fromY][fromX] == BlackKing
8551                && toY == 7 && toX == 5) {
8552         board[fromY][fromX] = EmptySquare;
8553         board[toY][toX] = BlackKing;
8554         board[fromY][7] = EmptySquare;
8555         board[toY][4] = BlackRook;
8556     } else if (fromY == 7 && fromX == 3
8557                && board[fromY][fromX] == BlackKing
8558                && toY == 7 && toX == 1) {
8559         board[fromY][fromX] = EmptySquare;
8560         board[toY][toX] = BlackKing;
8561         board[fromY][0] = EmptySquare;
8562         board[toY][2] = BlackRook;
8563     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8564                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8565                && toY < promoRank
8566                ) {
8567         /* black pawn promotion */
8568         board[toY][toX] = CharToPiece(ToLower(promoChar));
8569         if (board[toY][toX] == EmptySquare) {
8570             board[toY][toX] = BlackQueen;
8571         }
8572         if(gameInfo.variant==VariantBughouse ||
8573            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8574             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8575         board[fromY][fromX] = EmptySquare;
8576     } else if ((fromY == 3)
8577                && (toX != fromX)
8578                && gameInfo.variant != VariantXiangqi
8579                && gameInfo.variant != VariantBerolina
8580                && (board[fromY][fromX] == BlackPawn)
8581                && (board[toY][toX] == EmptySquare)) {
8582         board[fromY][fromX] = EmptySquare;
8583         board[toY][toX] = BlackPawn;
8584         captured = board[toY + 1][toX];
8585         board[toY + 1][toX] = EmptySquare;
8586     } else if ((fromY == 3)
8587                && (toX == fromX)
8588                && gameInfo.variant == VariantBerolina
8589                && (board[fromY][fromX] == BlackPawn)
8590                && (board[toY][toX] == EmptySquare)) {
8591         board[fromY][fromX] = EmptySquare;
8592         board[toY][toX] = BlackPawn;
8593         if(oldEP & EP_BEROLIN_A) {
8594                 captured = board[fromY][fromX-1];
8595                 board[fromY][fromX-1] = EmptySquare;
8596         }else{  captured = board[fromY][fromX+1];
8597                 board[fromY][fromX+1] = EmptySquare;
8598         }
8599     } else {
8600         board[toY][toX] = board[fromY][fromX];
8601         board[fromY][fromX] = EmptySquare;
8602     }
8603   }
8604
8605     if (gameInfo.holdingsWidth != 0) {
8606
8607       /* !!A lot more code needs to be written to support holdings  */
8608       /* [HGM] OK, so I have written it. Holdings are stored in the */
8609       /* penultimate board files, so they are automaticlly stored   */
8610       /* in the game history.                                       */
8611       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8612                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8613         /* Delete from holdings, by decreasing count */
8614         /* and erasing image if necessary            */
8615         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8616         if(p < (int) BlackPawn) { /* white drop */
8617              p -= (int)WhitePawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[p][BOARD_WIDTH-2] <= 0)
8621                   board[p][BOARD_WIDTH-1] = EmptySquare;
8622              if((int)board[p][BOARD_WIDTH-2] < 0)
8623                         board[p][BOARD_WIDTH-2] = 0;
8624         } else {                  /* black drop */
8625              p -= (int)BlackPawn;
8626                  p = PieceToNumber((ChessSquare)p);
8627              if(p >= gameInfo.holdingsSize) p = 0;
8628              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8629                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8630              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8631                         board[BOARD_HEIGHT-1-p][1] = 0;
8632         }
8633       }
8634       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8635           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8636         /* [HGM] holdings: Add to holdings, if holdings exist */
8637         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8638                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8639                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8640         }
8641         p = (int) captured;
8642         if (p >= (int) BlackPawn) {
8643           p -= (int)BlackPawn;
8644           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8645                   /* in Shogi restore piece to its original  first */
8646                   captured = (ChessSquare) (DEMOTED captured);
8647                   p = DEMOTED p;
8648           }
8649           p = PieceToNumber((ChessSquare)p);
8650           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8651           board[p][BOARD_WIDTH-2]++;
8652           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8653         } else {
8654           p -= (int)WhitePawn;
8655           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8656                   captured = (ChessSquare) (DEMOTED captured);
8657                   p = DEMOTED p;
8658           }
8659           p = PieceToNumber((ChessSquare)p);
8660           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8661           board[BOARD_HEIGHT-1-p][1]++;
8662           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8663         }
8664       }
8665     } else if (gameInfo.variant == VariantAtomic) {
8666       if (captured != EmptySquare) {
8667         int y, x;
8668         for (y = toY-1; y <= toY+1; y++) {
8669           for (x = toX-1; x <= toX+1; x++) {
8670             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8671                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8672               board[y][x] = EmptySquare;
8673             }
8674           }
8675         }
8676         board[toY][toX] = EmptySquare;
8677       }
8678     }
8679     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8680         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8681     } else
8682     if(promoChar == '+') {
8683         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8684         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8685     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8686         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8687     }
8688     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8689                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8690         // [HGM] superchess: take promotion piece out of holdings
8691         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8692         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8693             if(!--board[k][BOARD_WIDTH-2])
8694                 board[k][BOARD_WIDTH-1] = EmptySquare;
8695         } else {
8696             if(!--board[BOARD_HEIGHT-1-k][1])
8697                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8698         }
8699     }
8700
8701 }
8702
8703 /* Updates forwardMostMove */
8704 void
8705 MakeMove(fromX, fromY, toX, toY, promoChar)
8706      int fromX, fromY, toX, toY;
8707      int promoChar;
8708 {
8709 //    forwardMostMove++; // [HGM] bare: moved downstream
8710
8711     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8712         int timeLeft; static int lastLoadFlag=0; int king, piece;
8713         piece = boards[forwardMostMove][fromY][fromX];
8714         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8715         if(gameInfo.variant == VariantKnightmate)
8716             king += (int) WhiteUnicorn - (int) WhiteKing;
8717         if(forwardMostMove == 0) {
8718             if(blackPlaysFirst)
8719                 fprintf(serverMoves, "%s;", second.tidy);
8720             fprintf(serverMoves, "%s;", first.tidy);
8721             if(!blackPlaysFirst)
8722                 fprintf(serverMoves, "%s;", second.tidy);
8723         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8724         lastLoadFlag = loadFlag;
8725         // print base move
8726         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8727         // print castling suffix
8728         if( toY == fromY && piece == king ) {
8729             if(toX-fromX > 1)
8730                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8731             if(fromX-toX >1)
8732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8733         }
8734         // e.p. suffix
8735         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8736              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8737              boards[forwardMostMove][toY][toX] == EmptySquare
8738              && fromX != toX && fromY != toY)
8739                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8740         // promotion suffix
8741         if(promoChar != NULLCHAR)
8742                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8743         if(!loadFlag) {
8744             fprintf(serverMoves, "/%d/%d",
8745                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8746             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8747             else                      timeLeft = blackTimeRemaining/1000;
8748             fprintf(serverMoves, "/%d", timeLeft);
8749         }
8750         fflush(serverMoves);
8751     }
8752
8753     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8754       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8755                         0, 1);
8756       return;
8757     }
8758     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8759     if (commentList[forwardMostMove+1] != NULL) {
8760         free(commentList[forwardMostMove+1]);
8761         commentList[forwardMostMove+1] = NULL;
8762     }
8763     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8764     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8765     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8766     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8767     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8768     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8769     gameInfo.result = GameUnfinished;
8770     if (gameInfo.resultDetails != NULL) {
8771         free(gameInfo.resultDetails);
8772         gameInfo.resultDetails = NULL;
8773     }
8774     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8775                               moveList[forwardMostMove - 1]);
8776     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8777                              PosFlags(forwardMostMove - 1),
8778                              fromY, fromX, toY, toX, promoChar,
8779                              parseList[forwardMostMove - 1]);
8780     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8781       case MT_NONE:
8782       case MT_STALEMATE:
8783       default:
8784         break;
8785       case MT_CHECK:
8786         if(gameInfo.variant != VariantShogi)
8787             strcat(parseList[forwardMostMove - 1], "+");
8788         break;
8789       case MT_CHECKMATE:
8790       case MT_STAINMATE:
8791         strcat(parseList[forwardMostMove - 1], "#");
8792         break;
8793     }
8794     if (appData.debugMode) {
8795         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8796     }
8797
8798 }
8799
8800 /* Updates currentMove if not pausing */
8801 void
8802 ShowMove(fromX, fromY, toX, toY)
8803 {
8804     int instant = (gameMode == PlayFromGameFile) ?
8805         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8806     if(appData.noGUI) return;
8807     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8808         if (!instant) {
8809             if (forwardMostMove == currentMove + 1) {
8810                 AnimateMove(boards[forwardMostMove - 1],
8811                             fromX, fromY, toX, toY);
8812             }
8813             if (appData.highlightLastMove) {
8814                 SetHighlights(fromX, fromY, toX, toY);
8815             }
8816         }
8817         currentMove = forwardMostMove;
8818     }
8819
8820     if (instant) return;
8821
8822     DisplayMove(currentMove - 1);
8823     DrawPosition(FALSE, boards[currentMove]);
8824     DisplayBothClocks();
8825     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8826 }
8827
8828 void SendEgtPath(ChessProgramState *cps)
8829 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8830         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8831
8832         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8833
8834         while(*p) {
8835             char c, *q = name+1, *r, *s;
8836
8837             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8838             while(*p && *p != ',') *q++ = *p++;
8839             *q++ = ':'; *q = 0;
8840             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8841                 strcmp(name, ",nalimov:") == 0 ) {
8842                 // take nalimov path from the menu-changeable option first, if it is defined
8843               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8844                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8845             } else
8846             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8847                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8848                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8849                 s = r = StrStr(s, ":") + 1; // beginning of path info
8850                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8851                 c = *r; *r = 0;             // temporarily null-terminate path info
8852                     *--q = 0;               // strip of trailig ':' from name
8853                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8854                 *r = c;
8855                 SendToProgram(buf,cps);     // send egtbpath command for this format
8856             }
8857             if(*p == ',') p++; // read away comma to position for next format name
8858         }
8859 }
8860
8861 void
8862 InitChessProgram(cps, setup)
8863      ChessProgramState *cps;
8864      int setup; /* [HGM] needed to setup FRC opening position */
8865 {
8866     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8867     if (appData.noChessProgram) return;
8868     hintRequested = FALSE;
8869     bookRequested = FALSE;
8870
8871     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8872     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8873     if(cps->memSize) { /* [HGM] memory */
8874       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8875         SendToProgram(buf, cps);
8876     }
8877     SendEgtPath(cps); /* [HGM] EGT */
8878     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8879       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8880         SendToProgram(buf, cps);
8881     }
8882
8883     SendToProgram(cps->initString, cps);
8884     if (gameInfo.variant != VariantNormal &&
8885         gameInfo.variant != VariantLoadable
8886         /* [HGM] also send variant if board size non-standard */
8887         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8888                                             ) {
8889       char *v = VariantName(gameInfo.variant);
8890       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8891         /* [HGM] in protocol 1 we have to assume all variants valid */
8892         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8893         DisplayFatalError(buf, 0, 1);
8894         return;
8895       }
8896
8897       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8898       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8899       if( gameInfo.variant == VariantXiangqi )
8900            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8901       if( gameInfo.variant == VariantShogi )
8902            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8903       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8904            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8905       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8906                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8907            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8908       if( gameInfo.variant == VariantCourier )
8909            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8910       if( gameInfo.variant == VariantSuper )
8911            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8912       if( gameInfo.variant == VariantGreat )
8913            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8914       if( gameInfo.variant == VariantSChess )
8915            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8916
8917       if(overruled) {
8918         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8919                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8920            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8921            if(StrStr(cps->variants, b) == NULL) {
8922                // specific sized variant not known, check if general sizing allowed
8923                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8924                    if(StrStr(cps->variants, "boardsize") == NULL) {
8925                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8926                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8927                        DisplayFatalError(buf, 0, 1);
8928                        return;
8929                    }
8930                    /* [HGM] here we really should compare with the maximum supported board size */
8931                }
8932            }
8933       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8934       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8935       SendToProgram(buf, cps);
8936     }
8937     currentlyInitializedVariant = gameInfo.variant;
8938
8939     /* [HGM] send opening position in FRC to first engine */
8940     if(setup) {
8941           SendToProgram("force\n", cps);
8942           SendBoard(cps, 0);
8943           /* engine is now in force mode! Set flag to wake it up after first move. */
8944           setboardSpoiledMachineBlack = 1;
8945     }
8946
8947     if (cps->sendICS) {
8948       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8949       SendToProgram(buf, cps);
8950     }
8951     cps->maybeThinking = FALSE;
8952     cps->offeredDraw = 0;
8953     if (!appData.icsActive) {
8954         SendTimeControl(cps, movesPerSession, timeControl,
8955                         timeIncrement, appData.searchDepth,
8956                         searchTime);
8957     }
8958     if (appData.showThinking
8959         // [HGM] thinking: four options require thinking output to be sent
8960         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8961                                 ) {
8962         SendToProgram("post\n", cps);
8963     }
8964     SendToProgram("hard\n", cps);
8965     if (!appData.ponderNextMove) {
8966         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8967            it without being sure what state we are in first.  "hard"
8968            is not a toggle, so that one is OK.
8969          */
8970         SendToProgram("easy\n", cps);
8971     }
8972     if (cps->usePing) {
8973       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8974       SendToProgram(buf, cps);
8975     }
8976     cps->initDone = TRUE;
8977 }
8978
8979
8980 void
8981 StartChessProgram(cps)
8982      ChessProgramState *cps;
8983 {
8984     char buf[MSG_SIZ];
8985     int err;
8986
8987     if (appData.noChessProgram) return;
8988     cps->initDone = FALSE;
8989
8990     if (strcmp(cps->host, "localhost") == 0) {
8991         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8992     } else if (*appData.remoteShell == NULLCHAR) {
8993         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8994     } else {
8995         if (*appData.remoteUser == NULLCHAR) {
8996           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8997                     cps->program);
8998         } else {
8999           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9000                     cps->host, appData.remoteUser, cps->program);
9001         }
9002         err = StartChildProcess(buf, "", &cps->pr);
9003     }
9004
9005     if (err != 0) {
9006       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9007         DisplayFatalError(buf, err, 1);
9008         cps->pr = NoProc;
9009         cps->isr = NULL;
9010         return;
9011     }
9012
9013     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9014     if (cps->protocolVersion > 1) {
9015       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9016       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9017       cps->comboCnt = 0;  //                and values of combo boxes
9018       SendToProgram(buf, cps);
9019     } else {
9020       SendToProgram("xboard\n", cps);
9021     }
9022 }
9023
9024
9025 void
9026 TwoMachinesEventIfReady P((void))
9027 {
9028   if (first.lastPing != first.lastPong) {
9029     DisplayMessage("", _("Waiting for first chess program"));
9030     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9031     return;
9032   }
9033   if (second.lastPing != second.lastPong) {
9034     DisplayMessage("", _("Waiting for second chess program"));
9035     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9036     return;
9037   }
9038   ThawUI();
9039   TwoMachinesEvent();
9040 }
9041
9042 void
9043 NextMatchGame P((void))
9044 {
9045     int index; /* [HGM] autoinc: step load index during match */
9046     Reset(FALSE, TRUE);
9047     if (*appData.loadGameFile != NULLCHAR) {
9048         index = appData.loadGameIndex;
9049         if(index < 0) { // [HGM] autoinc
9050             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9051             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9052         }
9053         LoadGameFromFile(appData.loadGameFile,
9054                          index,
9055                          appData.loadGameFile, FALSE);
9056     } else if (*appData.loadPositionFile != NULLCHAR) {
9057         index = appData.loadPositionIndex;
9058         if(index < 0) { // [HGM] autoinc
9059             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9060             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9061         }
9062         LoadPositionFromFile(appData.loadPositionFile,
9063                              index,
9064                              appData.loadPositionFile);
9065     }
9066     TwoMachinesEventIfReady();
9067 }
9068
9069 void UserAdjudicationEvent( int result )
9070 {
9071     ChessMove gameResult = GameIsDrawn;
9072
9073     if( result > 0 ) {
9074         gameResult = WhiteWins;
9075     }
9076     else if( result < 0 ) {
9077         gameResult = BlackWins;
9078     }
9079
9080     if( gameMode == TwoMachinesPlay ) {
9081         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9082     }
9083 }
9084
9085
9086 // [HGM] save: calculate checksum of game to make games easily identifiable
9087 int StringCheckSum(char *s)
9088 {
9089         int i = 0;
9090         if(s==NULL) return 0;
9091         while(*s) i = i*259 + *s++;
9092         return i;
9093 }
9094
9095 int GameCheckSum()
9096 {
9097         int i, sum=0;
9098         for(i=backwardMostMove; i<forwardMostMove; i++) {
9099                 sum += pvInfoList[i].depth;
9100                 sum += StringCheckSum(parseList[i]);
9101                 sum += StringCheckSum(commentList[i]);
9102                 sum *= 261;
9103         }
9104         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9105         return sum + StringCheckSum(commentList[i]);
9106 } // end of save patch
9107
9108 void
9109 GameEnds(result, resultDetails, whosays)
9110      ChessMove result;
9111      char *resultDetails;
9112      int whosays;
9113 {
9114     GameMode nextGameMode;
9115     int isIcsGame;
9116     char buf[MSG_SIZ], popupRequested = 0;
9117
9118     if(endingGame) return; /* [HGM] crash: forbid recursion */
9119     endingGame = 1;
9120     if(twoBoards) { // [HGM] dual: switch back to one board
9121         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9122         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9123     }
9124     if (appData.debugMode) {
9125       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9126               result, resultDetails ? resultDetails : "(null)", whosays);
9127     }
9128
9129     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9130
9131     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9132         /* If we are playing on ICS, the server decides when the
9133            game is over, but the engine can offer to draw, claim
9134            a draw, or resign.
9135          */
9136 #if ZIPPY
9137         if (appData.zippyPlay && first.initDone) {
9138             if (result == GameIsDrawn) {
9139                 /* In case draw still needs to be claimed */
9140                 SendToICS(ics_prefix);
9141                 SendToICS("draw\n");
9142             } else if (StrCaseStr(resultDetails, "resign")) {
9143                 SendToICS(ics_prefix);
9144                 SendToICS("resign\n");
9145             }
9146         }
9147 #endif
9148         endingGame = 0; /* [HGM] crash */
9149         return;
9150     }
9151
9152     /* If we're loading the game from a file, stop */
9153     if (whosays == GE_FILE) {
9154       (void) StopLoadGameTimer();
9155       gameFileFP = NULL;
9156     }
9157
9158     /* Cancel draw offers */
9159     first.offeredDraw = second.offeredDraw = 0;
9160
9161     /* If this is an ICS game, only ICS can really say it's done;
9162        if not, anyone can. */
9163     isIcsGame = (gameMode == IcsPlayingWhite ||
9164                  gameMode == IcsPlayingBlack ||
9165                  gameMode == IcsObserving    ||
9166                  gameMode == IcsExamining);
9167
9168     if (!isIcsGame || whosays == GE_ICS) {
9169         /* OK -- not an ICS game, or ICS said it was done */
9170         StopClocks();
9171         if (!isIcsGame && !appData.noChessProgram)
9172           SetUserThinkingEnables();
9173
9174         /* [HGM] if a machine claims the game end we verify this claim */
9175         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9176             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9177                 char claimer;
9178                 ChessMove trueResult = (ChessMove) -1;
9179
9180                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9181                                             first.twoMachinesColor[0] :
9182                                             second.twoMachinesColor[0] ;
9183
9184                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9186                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9187                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9188                 } else
9189                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9191                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9192                 } else
9193                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9194                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9195                 }
9196
9197                 // now verify win claims, but not in drop games, as we don't understand those yet
9198                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9199                                                  || gameInfo.variant == VariantGreat) &&
9200                     (result == WhiteWins && claimer == 'w' ||
9201                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9202                       if (appData.debugMode) {
9203                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9204                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9205                       }
9206                       if(result != trueResult) {
9207                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9208                               result = claimer == 'w' ? BlackWins : WhiteWins;
9209                               resultDetails = buf;
9210                       }
9211                 } else
9212                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9213                     && (forwardMostMove <= backwardMostMove ||
9214                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9215                         (claimer=='b')==(forwardMostMove&1))
9216                                                                                   ) {
9217                       /* [HGM] verify: draws that were not flagged are false claims */
9218                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9219                       result = claimer == 'w' ? BlackWins : WhiteWins;
9220                       resultDetails = buf;
9221                 }
9222                 /* (Claiming a loss is accepted no questions asked!) */
9223             }
9224             /* [HGM] bare: don't allow bare King to win */
9225             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9226                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9227                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9228                && result != GameIsDrawn)
9229             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9230                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9231                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9232                         if(p >= 0 && p <= (int)WhiteKing) k++;
9233                 }
9234                 if (appData.debugMode) {
9235                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9236                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9237                 }
9238                 if(k <= 1) {
9239                         result = GameIsDrawn;
9240                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9241                         resultDetails = buf;
9242                 }
9243             }
9244         }
9245
9246
9247         if(serverMoves != NULL && !loadFlag) { char c = '=';
9248             if(result==WhiteWins) c = '+';
9249             if(result==BlackWins) c = '-';
9250             if(resultDetails != NULL)
9251                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9252         }
9253         if (resultDetails != NULL) {
9254             gameInfo.result = result;
9255             gameInfo.resultDetails = StrSave(resultDetails);
9256
9257             /* display last move only if game was not loaded from file */
9258             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9259                 DisplayMove(currentMove - 1);
9260
9261             if (forwardMostMove != 0) {
9262                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9263                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9264                                                                 ) {
9265                     if (*appData.saveGameFile != NULLCHAR) {
9266                         SaveGameToFile(appData.saveGameFile, TRUE);
9267                     } else if (appData.autoSaveGames) {
9268                         AutoSaveGame();
9269                     }
9270                     if (*appData.savePositionFile != NULLCHAR) {
9271                         SavePositionToFile(appData.savePositionFile);
9272                     }
9273                 }
9274             }
9275
9276             /* Tell program how game ended in case it is learning */
9277             /* [HGM] Moved this to after saving the PGN, just in case */
9278             /* engine died and we got here through time loss. In that */
9279             /* case we will get a fatal error writing the pipe, which */
9280             /* would otherwise lose us the PGN.                       */
9281             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9282             /* output during GameEnds should never be fatal anymore   */
9283             if (gameMode == MachinePlaysWhite ||
9284                 gameMode == MachinePlaysBlack ||
9285                 gameMode == TwoMachinesPlay ||
9286                 gameMode == IcsPlayingWhite ||
9287                 gameMode == IcsPlayingBlack ||
9288                 gameMode == BeginningOfGame) {
9289                 char buf[MSG_SIZ];
9290                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9291                         resultDetails);
9292                 if (first.pr != NoProc) {
9293                     SendToProgram(buf, &first);
9294                 }
9295                 if (second.pr != NoProc &&
9296                     gameMode == TwoMachinesPlay) {
9297                     SendToProgram(buf, &second);
9298                 }
9299             }
9300         }
9301
9302         if (appData.icsActive) {
9303             if (appData.quietPlay &&
9304                 (gameMode == IcsPlayingWhite ||
9305                  gameMode == IcsPlayingBlack)) {
9306                 SendToICS(ics_prefix);
9307                 SendToICS("set shout 1\n");
9308             }
9309             nextGameMode = IcsIdle;
9310             ics_user_moved = FALSE;
9311             /* clean up premove.  It's ugly when the game has ended and the
9312              * premove highlights are still on the board.
9313              */
9314             if (gotPremove) {
9315               gotPremove = FALSE;
9316               ClearPremoveHighlights();
9317               DrawPosition(FALSE, boards[currentMove]);
9318             }
9319             if (whosays == GE_ICS) {
9320                 switch (result) {
9321                 case WhiteWins:
9322                     if (gameMode == IcsPlayingWhite)
9323                         PlayIcsWinSound();
9324                     else if(gameMode == IcsPlayingBlack)
9325                         PlayIcsLossSound();
9326                     break;
9327                 case BlackWins:
9328                     if (gameMode == IcsPlayingBlack)
9329                         PlayIcsWinSound();
9330                     else if(gameMode == IcsPlayingWhite)
9331                         PlayIcsLossSound();
9332                     break;
9333                 case GameIsDrawn:
9334                     PlayIcsDrawSound();
9335                     break;
9336                 default:
9337                     PlayIcsUnfinishedSound();
9338                 }
9339             }
9340         } else if (gameMode == EditGame ||
9341                    gameMode == PlayFromGameFile ||
9342                    gameMode == AnalyzeMode ||
9343                    gameMode == AnalyzeFile) {
9344             nextGameMode = gameMode;
9345         } else {
9346             nextGameMode = EndOfGame;
9347         }
9348         pausing = FALSE;
9349         ModeHighlight();
9350     } else {
9351         nextGameMode = gameMode;
9352     }
9353
9354     if (appData.noChessProgram) {
9355         gameMode = nextGameMode;
9356         ModeHighlight();
9357         endingGame = 0; /* [HGM] crash */
9358         return;
9359     }
9360
9361     if (first.reuse) {
9362         /* Put first chess program into idle state */
9363         if (first.pr != NoProc &&
9364             (gameMode == MachinePlaysWhite ||
9365              gameMode == MachinePlaysBlack ||
9366              gameMode == TwoMachinesPlay ||
9367              gameMode == IcsPlayingWhite ||
9368              gameMode == IcsPlayingBlack ||
9369              gameMode == BeginningOfGame)) {
9370             SendToProgram("force\n", &first);
9371             if (first.usePing) {
9372               char buf[MSG_SIZ];
9373               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9374               SendToProgram(buf, &first);
9375             }
9376         }
9377     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9378         /* Kill off first chess program */
9379         if (first.isr != NULL)
9380           RemoveInputSource(first.isr);
9381         first.isr = NULL;
9382
9383         if (first.pr != NoProc) {
9384             ExitAnalyzeMode();
9385             DoSleep( appData.delayBeforeQuit );
9386             SendToProgram("quit\n", &first);
9387             DoSleep( appData.delayAfterQuit );
9388             DestroyChildProcess(first.pr, first.useSigterm);
9389         }
9390         first.pr = NoProc;
9391     }
9392     if (second.reuse) {
9393         /* Put second chess program into idle state */
9394         if (second.pr != NoProc &&
9395             gameMode == TwoMachinesPlay) {
9396             SendToProgram("force\n", &second);
9397             if (second.usePing) {
9398               char buf[MSG_SIZ];
9399               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9400               SendToProgram(buf, &second);
9401             }
9402         }
9403     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9404         /* Kill off second chess program */
9405         if (second.isr != NULL)
9406           RemoveInputSource(second.isr);
9407         second.isr = NULL;
9408
9409         if (second.pr != NoProc) {
9410             DoSleep( appData.delayBeforeQuit );
9411             SendToProgram("quit\n", &second);
9412             DoSleep( appData.delayAfterQuit );
9413             DestroyChildProcess(second.pr, second.useSigterm);
9414         }
9415         second.pr = NoProc;
9416     }
9417
9418     if (matchMode && gameMode == TwoMachinesPlay) {
9419         switch (result) {
9420         case WhiteWins:
9421           if (first.twoMachinesColor[0] == 'w') {
9422             first.matchWins++;
9423           } else {
9424             second.matchWins++;
9425           }
9426           break;
9427         case BlackWins:
9428           if (first.twoMachinesColor[0] == 'b') {
9429             first.matchWins++;
9430           } else {
9431             second.matchWins++;
9432           }
9433           break;
9434         default:
9435           break;
9436         }
9437         if (matchGame < appData.matchGames) {
9438             char *tmp;
9439             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9440                 tmp = first.twoMachinesColor;
9441                 first.twoMachinesColor = second.twoMachinesColor;
9442                 second.twoMachinesColor = tmp;
9443             }
9444             gameMode = nextGameMode;
9445             matchGame++;
9446             if(appData.matchPause>10000 || appData.matchPause<10)
9447                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9448             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9449             endingGame = 0; /* [HGM] crash */
9450             return;
9451         } else {
9452             gameMode = nextGameMode;
9453             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9454                      first.tidy, second.tidy,
9455                      first.matchWins, second.matchWins,
9456                      appData.matchGames - (first.matchWins + second.matchWins));
9457             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9458             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9459                 first.twoMachinesColor = "black\n";
9460                 second.twoMachinesColor = "white\n";
9461             } else {
9462                 first.twoMachinesColor = "white\n";
9463                 second.twoMachinesColor = "black\n";
9464             }
9465         }
9466     }
9467     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9468         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9469       ExitAnalyzeMode();
9470     gameMode = nextGameMode;
9471     ModeHighlight();
9472     endingGame = 0;  /* [HGM] crash */
9473     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9474       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9475         matchMode = FALSE; appData.matchGames = matchGame = 0;
9476         DisplayNote(buf);
9477       }
9478     }
9479 }
9480
9481 /* Assumes program was just initialized (initString sent).
9482    Leaves program in force mode. */
9483 void
9484 FeedMovesToProgram(cps, upto)
9485      ChessProgramState *cps;
9486      int upto;
9487 {
9488     int i;
9489
9490     if (appData.debugMode)
9491       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9492               startedFromSetupPosition ? "position and " : "",
9493               backwardMostMove, upto, cps->which);
9494     if(currentlyInitializedVariant != gameInfo.variant) {
9495       char buf[MSG_SIZ];
9496         // [HGM] variantswitch: make engine aware of new variant
9497         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9498                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9499         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9500         SendToProgram(buf, cps);
9501         currentlyInitializedVariant = gameInfo.variant;
9502     }
9503     SendToProgram("force\n", cps);
9504     if (startedFromSetupPosition) {
9505         SendBoard(cps, backwardMostMove);
9506     if (appData.debugMode) {
9507         fprintf(debugFP, "feedMoves\n");
9508     }
9509     }
9510     for (i = backwardMostMove; i < upto; i++) {
9511         SendMoveToProgram(i, cps);
9512     }
9513 }
9514
9515
9516 void
9517 ResurrectChessProgram()
9518 {
9519      /* The chess program may have exited.
9520         If so, restart it and feed it all the moves made so far. */
9521
9522     if (appData.noChessProgram || first.pr != NoProc) return;
9523
9524     StartChessProgram(&first);
9525     InitChessProgram(&first, FALSE);
9526     FeedMovesToProgram(&first, currentMove);
9527
9528     if (!first.sendTime) {
9529         /* can't tell gnuchess what its clock should read,
9530            so we bow to its notion. */
9531         ResetClocks();
9532         timeRemaining[0][currentMove] = whiteTimeRemaining;
9533         timeRemaining[1][currentMove] = blackTimeRemaining;
9534     }
9535
9536     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9537                 appData.icsEngineAnalyze) && first.analysisSupport) {
9538       SendToProgram("analyze\n", &first);
9539       first.analyzing = TRUE;
9540     }
9541 }
9542
9543 /*
9544  * Button procedures
9545  */
9546 void
9547 Reset(redraw, init)
9548      int redraw, init;
9549 {
9550     int i;
9551
9552     if (appData.debugMode) {
9553         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9554                 redraw, init, gameMode);
9555     }
9556     CleanupTail(); // [HGM] vari: delete any stored variations
9557     pausing = pauseExamInvalid = FALSE;
9558     startedFromSetupPosition = blackPlaysFirst = FALSE;
9559     firstMove = TRUE;
9560     whiteFlag = blackFlag = FALSE;
9561     userOfferedDraw = FALSE;
9562     hintRequested = bookRequested = FALSE;
9563     first.maybeThinking = FALSE;
9564     second.maybeThinking = FALSE;
9565     first.bookSuspend = FALSE; // [HGM] book
9566     second.bookSuspend = FALSE;
9567     thinkOutput[0] = NULLCHAR;
9568     lastHint[0] = NULLCHAR;
9569     ClearGameInfo(&gameInfo);
9570     gameInfo.variant = StringToVariant(appData.variant);
9571     ics_user_moved = ics_clock_paused = FALSE;
9572     ics_getting_history = H_FALSE;
9573     ics_gamenum = -1;
9574     white_holding[0] = black_holding[0] = NULLCHAR;
9575     ClearProgramStats();
9576     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9577
9578     ResetFrontEnd();
9579     ClearHighlights();
9580     flipView = appData.flipView;
9581     ClearPremoveHighlights();
9582     gotPremove = FALSE;
9583     alarmSounded = FALSE;
9584
9585     GameEnds(EndOfFile, NULL, GE_PLAYER);
9586     if(appData.serverMovesName != NULL) {
9587         /* [HGM] prepare to make moves file for broadcasting */
9588         clock_t t = clock();
9589         if(serverMoves != NULL) fclose(serverMoves);
9590         serverMoves = fopen(appData.serverMovesName, "r");
9591         if(serverMoves != NULL) {
9592             fclose(serverMoves);
9593             /* delay 15 sec before overwriting, so all clients can see end */
9594             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9595         }
9596         serverMoves = fopen(appData.serverMovesName, "w");
9597     }
9598
9599     ExitAnalyzeMode();
9600     gameMode = BeginningOfGame;
9601     ModeHighlight();
9602     if(appData.icsActive) gameInfo.variant = VariantNormal;
9603     currentMove = forwardMostMove = backwardMostMove = 0;
9604     InitPosition(redraw);
9605     for (i = 0; i < MAX_MOVES; i++) {
9606         if (commentList[i] != NULL) {
9607             free(commentList[i]);
9608             commentList[i] = NULL;
9609         }
9610     }
9611     ResetClocks();
9612     timeRemaining[0][0] = whiteTimeRemaining;
9613     timeRemaining[1][0] = blackTimeRemaining;
9614     if (first.pr == NULL) {
9615         StartChessProgram(&first);
9616     }
9617     if (init) {
9618             InitChessProgram(&first, startedFromSetupPosition);
9619     }
9620     DisplayTitle("");
9621     DisplayMessage("", "");
9622     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9623     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9624 }
9625
9626 void
9627 AutoPlayGameLoop()
9628 {
9629     for (;;) {
9630         if (!AutoPlayOneMove())
9631           return;
9632         if (matchMode || appData.timeDelay == 0)
9633           continue;
9634         if (appData.timeDelay < 0)
9635           return;
9636         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9637         break;
9638     }
9639 }
9640
9641
9642 int
9643 AutoPlayOneMove()
9644 {
9645     int fromX, fromY, toX, toY;
9646
9647     if (appData.debugMode) {
9648       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9649     }
9650
9651     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9652       return FALSE;
9653
9654     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9655       pvInfoList[currentMove].depth = programStats.depth;
9656       pvInfoList[currentMove].score = programStats.score;
9657       pvInfoList[currentMove].time  = 0;
9658       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9659     }
9660
9661     if (currentMove >= forwardMostMove) {
9662       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9663       gameMode = EditGame;
9664       ModeHighlight();
9665
9666       /* [AS] Clear current move marker at the end of a game */
9667       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9668
9669       return FALSE;
9670     }
9671
9672     toX = moveList[currentMove][2] - AAA;
9673     toY = moveList[currentMove][3] - ONE;
9674
9675     if (moveList[currentMove][1] == '@') {
9676         if (appData.highlightLastMove) {
9677             SetHighlights(-1, -1, toX, toY);
9678         }
9679     } else {
9680         fromX = moveList[currentMove][0] - AAA;
9681         fromY = moveList[currentMove][1] - ONE;
9682
9683         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9684
9685         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9686
9687         if (appData.highlightLastMove) {
9688             SetHighlights(fromX, fromY, toX, toY);
9689         }
9690     }
9691     DisplayMove(currentMove);
9692     SendMoveToProgram(currentMove++, &first);
9693     DisplayBothClocks();
9694     DrawPosition(FALSE, boards[currentMove]);
9695     // [HGM] PV info: always display, routine tests if empty
9696     DisplayComment(currentMove - 1, commentList[currentMove]);
9697     return TRUE;
9698 }
9699
9700
9701 int
9702 LoadGameOneMove(readAhead)
9703      ChessMove readAhead;
9704 {
9705     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9706     char promoChar = NULLCHAR;
9707     ChessMove moveType;
9708     char move[MSG_SIZ];
9709     char *p, *q;
9710
9711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9712         gameMode != AnalyzeMode && gameMode != Training) {
9713         gameFileFP = NULL;
9714         return FALSE;
9715     }
9716
9717     yyboardindex = forwardMostMove;
9718     if (readAhead != EndOfFile) {
9719       moveType = readAhead;
9720     } else {
9721       if (gameFileFP == NULL)
9722           return FALSE;
9723       moveType = (ChessMove) Myylex();
9724     }
9725
9726     done = FALSE;
9727     switch (moveType) {
9728       case Comment:
9729         if (appData.debugMode)
9730           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9731         p = yy_text;
9732
9733         /* append the comment but don't display it */
9734         AppendComment(currentMove, p, FALSE);
9735         return TRUE;
9736
9737       case WhiteCapturesEnPassant:
9738       case BlackCapturesEnPassant:
9739       case WhitePromotion:
9740       case BlackPromotion:
9741       case WhiteNonPromotion:
9742       case BlackNonPromotion:
9743       case NormalMove:
9744       case WhiteKingSideCastle:
9745       case WhiteQueenSideCastle:
9746       case BlackKingSideCastle:
9747       case BlackQueenSideCastle:
9748       case WhiteKingSideCastleWild:
9749       case WhiteQueenSideCastleWild:
9750       case BlackKingSideCastleWild:
9751       case BlackQueenSideCastleWild:
9752       /* PUSH Fabien */
9753       case WhiteHSideCastleFR:
9754       case WhiteASideCastleFR:
9755       case BlackHSideCastleFR:
9756       case BlackASideCastleFR:
9757       /* POP Fabien */
9758         if (appData.debugMode)
9759           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9760         fromX = currentMoveString[0] - AAA;
9761         fromY = currentMoveString[1] - ONE;
9762         toX = currentMoveString[2] - AAA;
9763         toY = currentMoveString[3] - ONE;
9764         promoChar = currentMoveString[4];
9765         break;
9766
9767       case WhiteDrop:
9768       case BlackDrop:
9769         if (appData.debugMode)
9770           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9771         fromX = moveType == WhiteDrop ?
9772           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9773         (int) CharToPiece(ToLower(currentMoveString[0]));
9774         fromY = DROP_RANK;
9775         toX = currentMoveString[2] - AAA;
9776         toY = currentMoveString[3] - ONE;
9777         break;
9778
9779       case WhiteWins:
9780       case BlackWins:
9781       case GameIsDrawn:
9782       case GameUnfinished:
9783         if (appData.debugMode)
9784           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9785         p = strchr(yy_text, '{');
9786         if (p == NULL) p = strchr(yy_text, '(');
9787         if (p == NULL) {
9788             p = yy_text;
9789             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9790         } else {
9791             q = strchr(p, *p == '{' ? '}' : ')');
9792             if (q != NULL) *q = NULLCHAR;
9793             p++;
9794         }
9795         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9796         GameEnds(moveType, p, GE_FILE);
9797         done = TRUE;
9798         if (cmailMsgLoaded) {
9799             ClearHighlights();
9800             flipView = WhiteOnMove(currentMove);
9801             if (moveType == GameUnfinished) flipView = !flipView;
9802             if (appData.debugMode)
9803               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9804         }
9805         break;
9806
9807       case EndOfFile:
9808         if (appData.debugMode)
9809           fprintf(debugFP, "Parser hit end of file\n");
9810         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9811           case MT_NONE:
9812           case MT_CHECK:
9813             break;
9814           case MT_CHECKMATE:
9815           case MT_STAINMATE:
9816             if (WhiteOnMove(currentMove)) {
9817                 GameEnds(BlackWins, "Black mates", GE_FILE);
9818             } else {
9819                 GameEnds(WhiteWins, "White mates", GE_FILE);
9820             }
9821             break;
9822           case MT_STALEMATE:
9823             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9824             break;
9825         }
9826         done = TRUE;
9827         break;
9828
9829       case MoveNumberOne:
9830         if (lastLoadGameStart == GNUChessGame) {
9831             /* GNUChessGames have numbers, but they aren't move numbers */
9832             if (appData.debugMode)
9833               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9834                       yy_text, (int) moveType);
9835             return LoadGameOneMove(EndOfFile); /* tail recursion */
9836         }
9837         /* else fall thru */
9838
9839       case XBoardGame:
9840       case GNUChessGame:
9841       case PGNTag:
9842         /* Reached start of next game in file */
9843         if (appData.debugMode)
9844           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9845         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9846           case MT_NONE:
9847           case MT_CHECK:
9848             break;
9849           case MT_CHECKMATE:
9850           case MT_STAINMATE:
9851             if (WhiteOnMove(currentMove)) {
9852                 GameEnds(BlackWins, "Black mates", GE_FILE);
9853             } else {
9854                 GameEnds(WhiteWins, "White mates", GE_FILE);
9855             }
9856             break;
9857           case MT_STALEMATE:
9858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9859             break;
9860         }
9861         done = TRUE;
9862         break;
9863
9864       case PositionDiagram:     /* should not happen; ignore */
9865       case ElapsedTime:         /* ignore */
9866       case NAG:                 /* ignore */
9867         if (appData.debugMode)
9868           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9869                   yy_text, (int) moveType);
9870         return LoadGameOneMove(EndOfFile); /* tail recursion */
9871
9872       case IllegalMove:
9873         if (appData.testLegality) {
9874             if (appData.debugMode)
9875               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9876             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9877                     (forwardMostMove / 2) + 1,
9878                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9879             DisplayError(move, 0);
9880             done = TRUE;
9881         } else {
9882             if (appData.debugMode)
9883               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9884                       yy_text, currentMoveString);
9885             fromX = currentMoveString[0] - AAA;
9886             fromY = currentMoveString[1] - ONE;
9887             toX = currentMoveString[2] - AAA;
9888             toY = currentMoveString[3] - ONE;
9889             promoChar = currentMoveString[4];
9890         }
9891         break;
9892
9893       case AmbiguousMove:
9894         if (appData.debugMode)
9895           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9896         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9897                 (forwardMostMove / 2) + 1,
9898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9899         DisplayError(move, 0);
9900         done = TRUE;
9901         break;
9902
9903       default:
9904       case ImpossibleMove:
9905         if (appData.debugMode)
9906           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9907         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9908                 (forwardMostMove / 2) + 1,
9909                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9910         DisplayError(move, 0);
9911         done = TRUE;
9912         break;
9913     }
9914
9915     if (done) {
9916         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9917             DrawPosition(FALSE, boards[currentMove]);
9918             DisplayBothClocks();
9919             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9920               DisplayComment(currentMove - 1, commentList[currentMove]);
9921         }
9922         (void) StopLoadGameTimer();
9923         gameFileFP = NULL;
9924         cmailOldMove = forwardMostMove;
9925         return FALSE;
9926     } else {
9927         /* currentMoveString is set as a side-effect of yylex */
9928
9929         thinkOutput[0] = NULLCHAR;
9930         MakeMove(fromX, fromY, toX, toY, promoChar);
9931         currentMove = forwardMostMove;
9932         return TRUE;
9933     }
9934 }
9935
9936 /* Load the nth game from the given file */
9937 int
9938 LoadGameFromFile(filename, n, title, useList)
9939      char *filename;
9940      int n;
9941      char *title;
9942      /*Boolean*/ int useList;
9943 {
9944     FILE *f;
9945     char buf[MSG_SIZ];
9946
9947     if (strcmp(filename, "-") == 0) {
9948         f = stdin;
9949         title = "stdin";
9950     } else {
9951         f = fopen(filename, "rb");
9952         if (f == NULL) {
9953           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9954             DisplayError(buf, errno);
9955             return FALSE;
9956         }
9957     }
9958     if (fseek(f, 0, 0) == -1) {
9959         /* f is not seekable; probably a pipe */
9960         useList = FALSE;
9961     }
9962     if (useList && n == 0) {
9963         int error = GameListBuild(f);
9964         if (error) {
9965             DisplayError(_("Cannot build game list"), error);
9966         } else if (!ListEmpty(&gameList) &&
9967                    ((ListGame *) gameList.tailPred)->number > 1) {
9968             GameListPopUp(f, title);
9969             return TRUE;
9970         }
9971         GameListDestroy();
9972         n = 1;
9973     }
9974     if (n == 0) n = 1;
9975     return LoadGame(f, n, title, FALSE);
9976 }
9977
9978
9979 void
9980 MakeRegisteredMove()
9981 {
9982     int fromX, fromY, toX, toY;
9983     char promoChar;
9984     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9985         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9986           case CMAIL_MOVE:
9987           case CMAIL_DRAW:
9988             if (appData.debugMode)
9989               fprintf(debugFP, "Restoring %s for game %d\n",
9990                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9991
9992             thinkOutput[0] = NULLCHAR;
9993             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9994             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9995             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9996             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9997             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9998             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9999             MakeMove(fromX, fromY, toX, toY, promoChar);
10000             ShowMove(fromX, fromY, toX, toY);
10001
10002             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10003               case MT_NONE:
10004               case MT_CHECK:
10005                 break;
10006
10007               case MT_CHECKMATE:
10008               case MT_STAINMATE:
10009                 if (WhiteOnMove(currentMove)) {
10010                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10011                 } else {
10012                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10013                 }
10014                 break;
10015
10016               case MT_STALEMATE:
10017                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10018                 break;
10019             }
10020
10021             break;
10022
10023           case CMAIL_RESIGN:
10024             if (WhiteOnMove(currentMove)) {
10025                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10026             } else {
10027                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10028             }
10029             break;
10030
10031           case CMAIL_ACCEPT:
10032             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10033             break;
10034
10035           default:
10036             break;
10037         }
10038     }
10039
10040     return;
10041 }
10042
10043 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10044 int
10045 CmailLoadGame(f, gameNumber, title, useList)
10046      FILE *f;
10047      int gameNumber;
10048      char *title;
10049      int useList;
10050 {
10051     int retVal;
10052
10053     if (gameNumber > nCmailGames) {
10054         DisplayError(_("No more games in this message"), 0);
10055         return FALSE;
10056     }
10057     if (f == lastLoadGameFP) {
10058         int offset = gameNumber - lastLoadGameNumber;
10059         if (offset == 0) {
10060             cmailMsg[0] = NULLCHAR;
10061             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10062                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10063                 nCmailMovesRegistered--;
10064             }
10065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10066             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10067                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10068             }
10069         } else {
10070             if (! RegisterMove()) return FALSE;
10071         }
10072     }
10073
10074     retVal = LoadGame(f, gameNumber, title, useList);
10075
10076     /* Make move registered during previous look at this game, if any */
10077     MakeRegisteredMove();
10078
10079     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10080         commentList[currentMove]
10081           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10082         DisplayComment(currentMove - 1, commentList[currentMove]);
10083     }
10084
10085     return retVal;
10086 }
10087
10088 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10089 int
10090 ReloadGame(offset)
10091      int offset;
10092 {
10093     int gameNumber = lastLoadGameNumber + offset;
10094     if (lastLoadGameFP == NULL) {
10095         DisplayError(_("No game has been loaded yet"), 0);
10096         return FALSE;
10097     }
10098     if (gameNumber <= 0) {
10099         DisplayError(_("Can't back up any further"), 0);
10100         return FALSE;
10101     }
10102     if (cmailMsgLoaded) {
10103         return CmailLoadGame(lastLoadGameFP, gameNumber,
10104                              lastLoadGameTitle, lastLoadGameUseList);
10105     } else {
10106         return LoadGame(lastLoadGameFP, gameNumber,
10107                         lastLoadGameTitle, lastLoadGameUseList);
10108     }
10109 }
10110
10111
10112
10113 /* Load the nth game from open file f */
10114 int
10115 LoadGame(f, gameNumber, title, useList)
10116      FILE *f;
10117      int gameNumber;
10118      char *title;
10119      int useList;
10120 {
10121     ChessMove cm;
10122     char buf[MSG_SIZ];
10123     int gn = gameNumber;
10124     ListGame *lg = NULL;
10125     int numPGNTags = 0;
10126     int err;
10127     GameMode oldGameMode;
10128     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10129
10130     if (appData.debugMode)
10131         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10132
10133     if (gameMode == Training )
10134         SetTrainingModeOff();
10135
10136     oldGameMode = gameMode;
10137     if (gameMode != BeginningOfGame) {
10138       Reset(FALSE, TRUE);
10139     }
10140
10141     gameFileFP = f;
10142     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10143         fclose(lastLoadGameFP);
10144     }
10145
10146     if (useList) {
10147         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10148
10149         if (lg) {
10150             fseek(f, lg->offset, 0);
10151             GameListHighlight(gameNumber);
10152             gn = 1;
10153         }
10154         else {
10155             DisplayError(_("Game number out of range"), 0);
10156             return FALSE;
10157         }
10158     } else {
10159         GameListDestroy();
10160         if (fseek(f, 0, 0) == -1) {
10161             if (f == lastLoadGameFP ?
10162                 gameNumber == lastLoadGameNumber + 1 :
10163                 gameNumber == 1) {
10164                 gn = 1;
10165             } else {
10166                 DisplayError(_("Can't seek on game file"), 0);
10167                 return FALSE;
10168             }
10169         }
10170     }
10171     lastLoadGameFP = f;
10172     lastLoadGameNumber = gameNumber;
10173     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10174     lastLoadGameUseList = useList;
10175
10176     yynewfile(f);
10177
10178     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10179       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10180                 lg->gameInfo.black);
10181             DisplayTitle(buf);
10182     } else if (*title != NULLCHAR) {
10183         if (gameNumber > 1) {
10184           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10185             DisplayTitle(buf);
10186         } else {
10187             DisplayTitle(title);
10188         }
10189     }
10190
10191     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10192         gameMode = PlayFromGameFile;
10193         ModeHighlight();
10194     }
10195
10196     currentMove = forwardMostMove = backwardMostMove = 0;
10197     CopyBoard(boards[0], initialPosition);
10198     StopClocks();
10199
10200     /*
10201      * Skip the first gn-1 games in the file.
10202      * Also skip over anything that precedes an identifiable
10203      * start of game marker, to avoid being confused by
10204      * garbage at the start of the file.  Currently
10205      * recognized start of game markers are the move number "1",
10206      * the pattern "gnuchess .* game", the pattern
10207      * "^[#;%] [^ ]* game file", and a PGN tag block.
10208      * A game that starts with one of the latter two patterns
10209      * will also have a move number 1, possibly
10210      * following a position diagram.
10211      * 5-4-02: Let's try being more lenient and allowing a game to
10212      * start with an unnumbered move.  Does that break anything?
10213      */
10214     cm = lastLoadGameStart = EndOfFile;
10215     while (gn > 0) {
10216         yyboardindex = forwardMostMove;
10217         cm = (ChessMove) Myylex();
10218         switch (cm) {
10219           case EndOfFile:
10220             if (cmailMsgLoaded) {
10221                 nCmailGames = CMAIL_MAX_GAMES - gn;
10222             } else {
10223                 Reset(TRUE, TRUE);
10224                 DisplayError(_("Game not found in file"), 0);
10225             }
10226             return FALSE;
10227
10228           case GNUChessGame:
10229           case XBoardGame:
10230             gn--;
10231             lastLoadGameStart = cm;
10232             break;
10233
10234           case MoveNumberOne:
10235             switch (lastLoadGameStart) {
10236               case GNUChessGame:
10237               case XBoardGame:
10238               case PGNTag:
10239                 break;
10240               case MoveNumberOne:
10241               case EndOfFile:
10242                 gn--;           /* count this game */
10243                 lastLoadGameStart = cm;
10244                 break;
10245               default:
10246                 /* impossible */
10247                 break;
10248             }
10249             break;
10250
10251           case PGNTag:
10252             switch (lastLoadGameStart) {
10253               case GNUChessGame:
10254               case PGNTag:
10255               case MoveNumberOne:
10256               case EndOfFile:
10257                 gn--;           /* count this game */
10258                 lastLoadGameStart = cm;
10259                 break;
10260               case XBoardGame:
10261                 lastLoadGameStart = cm; /* game counted already */
10262                 break;
10263               default:
10264                 /* impossible */
10265                 break;
10266             }
10267             if (gn > 0) {
10268                 do {
10269                     yyboardindex = forwardMostMove;
10270                     cm = (ChessMove) Myylex();
10271                 } while (cm == PGNTag || cm == Comment);
10272             }
10273             break;
10274
10275           case WhiteWins:
10276           case BlackWins:
10277           case GameIsDrawn:
10278             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10279                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10280                     != CMAIL_OLD_RESULT) {
10281                     nCmailResults ++ ;
10282                     cmailResult[  CMAIL_MAX_GAMES
10283                                 - gn - 1] = CMAIL_OLD_RESULT;
10284                 }
10285             }
10286             break;
10287
10288           case NormalMove:
10289             /* Only a NormalMove can be at the start of a game
10290              * without a position diagram. */
10291             if (lastLoadGameStart == EndOfFile ) {
10292               gn--;
10293               lastLoadGameStart = MoveNumberOne;
10294             }
10295             break;
10296
10297           default:
10298             break;
10299         }
10300     }
10301
10302     if (appData.debugMode)
10303       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10304
10305     if (cm == XBoardGame) {
10306         /* Skip any header junk before position diagram and/or move 1 */
10307         for (;;) {
10308             yyboardindex = forwardMostMove;
10309             cm = (ChessMove) Myylex();
10310
10311             if (cm == EndOfFile ||
10312                 cm == GNUChessGame || cm == XBoardGame) {
10313                 /* Empty game; pretend end-of-file and handle later */
10314                 cm = EndOfFile;
10315                 break;
10316             }
10317
10318             if (cm == MoveNumberOne || cm == PositionDiagram ||
10319                 cm == PGNTag || cm == Comment)
10320               break;
10321         }
10322     } else if (cm == GNUChessGame) {
10323         if (gameInfo.event != NULL) {
10324             free(gameInfo.event);
10325         }
10326         gameInfo.event = StrSave(yy_text);
10327     }
10328
10329     startedFromSetupPosition = FALSE;
10330     while (cm == PGNTag) {
10331         if (appData.debugMode)
10332           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10333         err = ParsePGNTag(yy_text, &gameInfo);
10334         if (!err) numPGNTags++;
10335
10336         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10337         if(gameInfo.variant != oldVariant) {
10338             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10339             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10340             InitPosition(TRUE);
10341             oldVariant = gameInfo.variant;
10342             if (appData.debugMode)
10343               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10344         }
10345
10346
10347         if (gameInfo.fen != NULL) {
10348           Board initial_position;
10349           startedFromSetupPosition = TRUE;
10350           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10351             Reset(TRUE, TRUE);
10352             DisplayError(_("Bad FEN position in file"), 0);
10353             return FALSE;
10354           }
10355           CopyBoard(boards[0], initial_position);
10356           if (blackPlaysFirst) {
10357             currentMove = forwardMostMove = backwardMostMove = 1;
10358             CopyBoard(boards[1], initial_position);
10359             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10360             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10361             timeRemaining[0][1] = whiteTimeRemaining;
10362             timeRemaining[1][1] = blackTimeRemaining;
10363             if (commentList[0] != NULL) {
10364               commentList[1] = commentList[0];
10365               commentList[0] = NULL;
10366             }
10367           } else {
10368             currentMove = forwardMostMove = backwardMostMove = 0;
10369           }
10370           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10371           {   int i;
10372               initialRulePlies = FENrulePlies;
10373               for( i=0; i< nrCastlingRights; i++ )
10374                   initialRights[i] = initial_position[CASTLING][i];
10375           }
10376           yyboardindex = forwardMostMove;
10377           free(gameInfo.fen);
10378           gameInfo.fen = NULL;
10379         }
10380
10381         yyboardindex = forwardMostMove;
10382         cm = (ChessMove) Myylex();
10383
10384         /* Handle comments interspersed among the tags */
10385         while (cm == Comment) {
10386             char *p;
10387             if (appData.debugMode)
10388               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10389             p = yy_text;
10390             AppendComment(currentMove, p, FALSE);
10391             yyboardindex = forwardMostMove;
10392             cm = (ChessMove) Myylex();
10393         }
10394     }
10395
10396     /* don't rely on existence of Event tag since if game was
10397      * pasted from clipboard the Event tag may not exist
10398      */
10399     if (numPGNTags > 0){
10400         char *tags;
10401         if (gameInfo.variant == VariantNormal) {
10402           VariantClass v = StringToVariant(gameInfo.event);
10403           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10404           if(v < VariantShogi) gameInfo.variant = v;
10405         }
10406         if (!matchMode) {
10407           if( appData.autoDisplayTags ) {
10408             tags = PGNTags(&gameInfo);
10409             TagsPopUp(tags, CmailMsg());
10410             free(tags);
10411           }
10412         }
10413     } else {
10414         /* Make something up, but don't display it now */
10415         SetGameInfo();
10416         TagsPopDown();
10417     }
10418
10419     if (cm == PositionDiagram) {
10420         int i, j;
10421         char *p;
10422         Board initial_position;
10423
10424         if (appData.debugMode)
10425           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10426
10427         if (!startedFromSetupPosition) {
10428             p = yy_text;
10429             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10430               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10431                 switch (*p) {
10432                   case '[':
10433                   case '-':
10434                   case ' ':
10435                   case '\t':
10436                   case '\n':
10437                   case '\r':
10438                     break;
10439                   default:
10440                     initial_position[i][j++] = CharToPiece(*p);
10441                     break;
10442                 }
10443             while (*p == ' ' || *p == '\t' ||
10444                    *p == '\n' || *p == '\r') p++;
10445
10446             if (strncmp(p, "black", strlen("black"))==0)
10447               blackPlaysFirst = TRUE;
10448             else
10449               blackPlaysFirst = FALSE;
10450             startedFromSetupPosition = TRUE;
10451
10452             CopyBoard(boards[0], initial_position);
10453             if (blackPlaysFirst) {
10454                 currentMove = forwardMostMove = backwardMostMove = 1;
10455                 CopyBoard(boards[1], initial_position);
10456                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10457                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10458                 timeRemaining[0][1] = whiteTimeRemaining;
10459                 timeRemaining[1][1] = blackTimeRemaining;
10460                 if (commentList[0] != NULL) {
10461                     commentList[1] = commentList[0];
10462                     commentList[0] = NULL;
10463                 }
10464             } else {
10465                 currentMove = forwardMostMove = backwardMostMove = 0;
10466             }
10467         }
10468         yyboardindex = forwardMostMove;
10469         cm = (ChessMove) Myylex();
10470     }
10471
10472     if (first.pr == NoProc) {
10473         StartChessProgram(&first);
10474     }
10475     InitChessProgram(&first, FALSE);
10476     SendToProgram("force\n", &first);
10477     if (startedFromSetupPosition) {
10478         SendBoard(&first, forwardMostMove);
10479     if (appData.debugMode) {
10480         fprintf(debugFP, "Load Game\n");
10481     }
10482         DisplayBothClocks();
10483     }
10484
10485     /* [HGM] server: flag to write setup moves in broadcast file as one */
10486     loadFlag = appData.suppressLoadMoves;
10487
10488     while (cm == Comment) {
10489         char *p;
10490         if (appData.debugMode)
10491           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10492         p = yy_text;
10493         AppendComment(currentMove, p, FALSE);
10494         yyboardindex = forwardMostMove;
10495         cm = (ChessMove) Myylex();
10496     }
10497
10498     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10499         cm == WhiteWins || cm == BlackWins ||
10500         cm == GameIsDrawn || cm == GameUnfinished) {
10501         DisplayMessage("", _("No moves in game"));
10502         if (cmailMsgLoaded) {
10503             if (appData.debugMode)
10504               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10505             ClearHighlights();
10506             flipView = FALSE;
10507         }
10508         DrawPosition(FALSE, boards[currentMove]);
10509         DisplayBothClocks();
10510         gameMode = EditGame;
10511         ModeHighlight();
10512         gameFileFP = NULL;
10513         cmailOldMove = 0;
10514         return TRUE;
10515     }
10516
10517     // [HGM] PV info: routine tests if comment empty
10518     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10519         DisplayComment(currentMove - 1, commentList[currentMove]);
10520     }
10521     if (!matchMode && appData.timeDelay != 0)
10522       DrawPosition(FALSE, boards[currentMove]);
10523
10524     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10525       programStats.ok_to_send = 1;
10526     }
10527
10528     /* if the first token after the PGN tags is a move
10529      * and not move number 1, retrieve it from the parser
10530      */
10531     if (cm != MoveNumberOne)
10532         LoadGameOneMove(cm);
10533
10534     /* load the remaining moves from the file */
10535     while (LoadGameOneMove(EndOfFile)) {
10536       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10537       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10538     }
10539
10540     /* rewind to the start of the game */
10541     currentMove = backwardMostMove;
10542
10543     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10544
10545     if (oldGameMode == AnalyzeFile ||
10546         oldGameMode == AnalyzeMode) {
10547       AnalyzeFileEvent();
10548     }
10549
10550     if (matchMode || appData.timeDelay == 0) {
10551       ToEndEvent();
10552       gameMode = EditGame;
10553       ModeHighlight();
10554     } else if (appData.timeDelay > 0) {
10555       AutoPlayGameLoop();
10556     }
10557
10558     if (appData.debugMode)
10559         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10560
10561     loadFlag = 0; /* [HGM] true game starts */
10562     return TRUE;
10563 }
10564
10565 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10566 int
10567 ReloadPosition(offset)
10568      int offset;
10569 {
10570     int positionNumber = lastLoadPositionNumber + offset;
10571     if (lastLoadPositionFP == NULL) {
10572         DisplayError(_("No position has been loaded yet"), 0);
10573         return FALSE;
10574     }
10575     if (positionNumber <= 0) {
10576         DisplayError(_("Can't back up any further"), 0);
10577         return FALSE;
10578     }
10579     return LoadPosition(lastLoadPositionFP, positionNumber,
10580                         lastLoadPositionTitle);
10581 }
10582
10583 /* Load the nth position from the given file */
10584 int
10585 LoadPositionFromFile(filename, n, title)
10586      char *filename;
10587      int n;
10588      char *title;
10589 {
10590     FILE *f;
10591     char buf[MSG_SIZ];
10592
10593     if (strcmp(filename, "-") == 0) {
10594         return LoadPosition(stdin, n, "stdin");
10595     } else {
10596         f = fopen(filename, "rb");
10597         if (f == NULL) {
10598             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10599             DisplayError(buf, errno);
10600             return FALSE;
10601         } else {
10602             return LoadPosition(f, n, title);
10603         }
10604     }
10605 }
10606
10607 /* Load the nth position from the given open file, and close it */
10608 int
10609 LoadPosition(f, positionNumber, title)
10610      FILE *f;
10611      int positionNumber;
10612      char *title;
10613 {
10614     char *p, line[MSG_SIZ];
10615     Board initial_position;
10616     int i, j, fenMode, pn;
10617
10618     if (gameMode == Training )
10619         SetTrainingModeOff();
10620
10621     if (gameMode != BeginningOfGame) {
10622         Reset(FALSE, TRUE);
10623     }
10624     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10625         fclose(lastLoadPositionFP);
10626     }
10627     if (positionNumber == 0) positionNumber = 1;
10628     lastLoadPositionFP = f;
10629     lastLoadPositionNumber = positionNumber;
10630     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10631     if (first.pr == NoProc) {
10632       StartChessProgram(&first);
10633       InitChessProgram(&first, FALSE);
10634     }
10635     pn = positionNumber;
10636     if (positionNumber < 0) {
10637         /* Negative position number means to seek to that byte offset */
10638         if (fseek(f, -positionNumber, 0) == -1) {
10639             DisplayError(_("Can't seek on position file"), 0);
10640             return FALSE;
10641         };
10642         pn = 1;
10643     } else {
10644         if (fseek(f, 0, 0) == -1) {
10645             if (f == lastLoadPositionFP ?
10646                 positionNumber == lastLoadPositionNumber + 1 :
10647                 positionNumber == 1) {
10648                 pn = 1;
10649             } else {
10650                 DisplayError(_("Can't seek on position file"), 0);
10651                 return FALSE;
10652             }
10653         }
10654     }
10655     /* See if this file is FEN or old-style xboard */
10656     if (fgets(line, MSG_SIZ, f) == NULL) {
10657         DisplayError(_("Position not found in file"), 0);
10658         return FALSE;
10659     }
10660     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10661     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10662
10663     if (pn >= 2) {
10664         if (fenMode || line[0] == '#') pn--;
10665         while (pn > 0) {
10666             /* skip positions before number pn */
10667             if (fgets(line, MSG_SIZ, f) == NULL) {
10668                 Reset(TRUE, TRUE);
10669                 DisplayError(_("Position not found in file"), 0);
10670                 return FALSE;
10671             }
10672             if (fenMode || line[0] == '#') pn--;
10673         }
10674     }
10675
10676     if (fenMode) {
10677         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10678             DisplayError(_("Bad FEN position in file"), 0);
10679             return FALSE;
10680         }
10681     } else {
10682         (void) fgets(line, MSG_SIZ, f);
10683         (void) fgets(line, MSG_SIZ, f);
10684
10685         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10686             (void) fgets(line, MSG_SIZ, f);
10687             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10688                 if (*p == ' ')
10689                   continue;
10690                 initial_position[i][j++] = CharToPiece(*p);
10691             }
10692         }
10693
10694         blackPlaysFirst = FALSE;
10695         if (!feof(f)) {
10696             (void) fgets(line, MSG_SIZ, f);
10697             if (strncmp(line, "black", strlen("black"))==0)
10698               blackPlaysFirst = TRUE;
10699         }
10700     }
10701     startedFromSetupPosition = TRUE;
10702
10703     SendToProgram("force\n", &first);
10704     CopyBoard(boards[0], initial_position);
10705     if (blackPlaysFirst) {
10706         currentMove = forwardMostMove = backwardMostMove = 1;
10707         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10708         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10709         CopyBoard(boards[1], initial_position);
10710         DisplayMessage("", _("Black to play"));
10711     } else {
10712         currentMove = forwardMostMove = backwardMostMove = 0;
10713         DisplayMessage("", _("White to play"));
10714     }
10715     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10716     SendBoard(&first, forwardMostMove);
10717     if (appData.debugMode) {
10718 int i, j;
10719   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10720   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10721         fprintf(debugFP, "Load Position\n");
10722     }
10723
10724     if (positionNumber > 1) {
10725       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10726         DisplayTitle(line);
10727     } else {
10728         DisplayTitle(title);
10729     }
10730     gameMode = EditGame;
10731     ModeHighlight();
10732     ResetClocks();
10733     timeRemaining[0][1] = whiteTimeRemaining;
10734     timeRemaining[1][1] = blackTimeRemaining;
10735     DrawPosition(FALSE, boards[currentMove]);
10736
10737     return TRUE;
10738 }
10739
10740
10741 void
10742 CopyPlayerNameIntoFileName(dest, src)
10743      char **dest, *src;
10744 {
10745     while (*src != NULLCHAR && *src != ',') {
10746         if (*src == ' ') {
10747             *(*dest)++ = '_';
10748             src++;
10749         } else {
10750             *(*dest)++ = *src++;
10751         }
10752     }
10753 }
10754
10755 char *DefaultFileName(ext)
10756      char *ext;
10757 {
10758     static char def[MSG_SIZ];
10759     char *p;
10760
10761     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10762         p = def;
10763         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10764         *p++ = '-';
10765         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10766         *p++ = '.';
10767         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10768     } else {
10769         def[0] = NULLCHAR;
10770     }
10771     return def;
10772 }
10773
10774 /* Save the current game to the given file */
10775 int
10776 SaveGameToFile(filename, append)
10777      char *filename;
10778      int append;
10779 {
10780     FILE *f;
10781     char buf[MSG_SIZ];
10782
10783     if (strcmp(filename, "-") == 0) {
10784         return SaveGame(stdout, 0, NULL);
10785     } else {
10786         f = fopen(filename, append ? "a" : "w");
10787         if (f == NULL) {
10788             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10789             DisplayError(buf, errno);
10790             return FALSE;
10791         } else {
10792             return SaveGame(f, 0, NULL);
10793         }
10794     }
10795 }
10796
10797 char *
10798 SavePart(str)
10799      char *str;
10800 {
10801     static char buf[MSG_SIZ];
10802     char *p;
10803
10804     p = strchr(str, ' ');
10805     if (p == NULL) return str;
10806     strncpy(buf, str, p - str);
10807     buf[p - str] = NULLCHAR;
10808     return buf;
10809 }
10810
10811 #define PGN_MAX_LINE 75
10812
10813 #define PGN_SIDE_WHITE  0
10814 #define PGN_SIDE_BLACK  1
10815
10816 /* [AS] */
10817 static int FindFirstMoveOutOfBook( int side )
10818 {
10819     int result = -1;
10820
10821     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10822         int index = backwardMostMove;
10823         int has_book_hit = 0;
10824
10825         if( (index % 2) != side ) {
10826             index++;
10827         }
10828
10829         while( index < forwardMostMove ) {
10830             /* Check to see if engine is in book */
10831             int depth = pvInfoList[index].depth;
10832             int score = pvInfoList[index].score;
10833             int in_book = 0;
10834
10835             if( depth <= 2 ) {
10836                 in_book = 1;
10837             }
10838             else if( score == 0 && depth == 63 ) {
10839                 in_book = 1; /* Zappa */
10840             }
10841             else if( score == 2 && depth == 99 ) {
10842                 in_book = 1; /* Abrok */
10843             }
10844
10845             has_book_hit += in_book;
10846
10847             if( ! in_book ) {
10848                 result = index;
10849
10850                 break;
10851             }
10852
10853             index += 2;
10854         }
10855     }
10856
10857     return result;
10858 }
10859
10860 /* [AS] */
10861 void GetOutOfBookInfo( char * buf )
10862 {
10863     int oob[2];
10864     int i;
10865     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10866
10867     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10868     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10869
10870     *buf = '\0';
10871
10872     if( oob[0] >= 0 || oob[1] >= 0 ) {
10873         for( i=0; i<2; i++ ) {
10874             int idx = oob[i];
10875
10876             if( idx >= 0 ) {
10877                 if( i > 0 && oob[0] >= 0 ) {
10878                     strcat( buf, "   " );
10879                 }
10880
10881                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10882                 sprintf( buf+strlen(buf), "%s%.2f",
10883                     pvInfoList[idx].score >= 0 ? "+" : "",
10884                     pvInfoList[idx].score / 100.0 );
10885             }
10886         }
10887     }
10888 }
10889
10890 /* Save game in PGN style and close the file */
10891 int
10892 SaveGamePGN(f)
10893      FILE *f;
10894 {
10895     int i, offset, linelen, newblock;
10896     time_t tm;
10897 //    char *movetext;
10898     char numtext[32];
10899     int movelen, numlen, blank;
10900     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10901
10902     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10903
10904     tm = time((time_t *) NULL);
10905
10906     PrintPGNTags(f, &gameInfo);
10907
10908     if (backwardMostMove > 0 || startedFromSetupPosition) {
10909         char *fen = PositionToFEN(backwardMostMove, NULL);
10910         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10911         fprintf(f, "\n{--------------\n");
10912         PrintPosition(f, backwardMostMove);
10913         fprintf(f, "--------------}\n");
10914         free(fen);
10915     }
10916     else {
10917         /* [AS] Out of book annotation */
10918         if( appData.saveOutOfBookInfo ) {
10919             char buf[64];
10920
10921             GetOutOfBookInfo( buf );
10922
10923             if( buf[0] != '\0' ) {
10924                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10925             }
10926         }
10927
10928         fprintf(f, "\n");
10929     }
10930
10931     i = backwardMostMove;
10932     linelen = 0;
10933     newblock = TRUE;
10934
10935     while (i < forwardMostMove) {
10936         /* Print comments preceding this move */
10937         if (commentList[i] != NULL) {
10938             if (linelen > 0) fprintf(f, "\n");
10939             fprintf(f, "%s", commentList[i]);
10940             linelen = 0;
10941             newblock = TRUE;
10942         }
10943
10944         /* Format move number */
10945         if ((i % 2) == 0)
10946           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10947         else
10948           if (newblock)
10949             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10950           else
10951             numtext[0] = NULLCHAR;
10952
10953         numlen = strlen(numtext);
10954         newblock = FALSE;
10955
10956         /* Print move number */
10957         blank = linelen > 0 && numlen > 0;
10958         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10959             fprintf(f, "\n");
10960             linelen = 0;
10961             blank = 0;
10962         }
10963         if (blank) {
10964             fprintf(f, " ");
10965             linelen++;
10966         }
10967         fprintf(f, "%s", numtext);
10968         linelen += numlen;
10969
10970         /* Get move */
10971         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10972         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10973
10974         /* Print move */
10975         blank = linelen > 0 && movelen > 0;
10976         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10977             fprintf(f, "\n");
10978             linelen = 0;
10979             blank = 0;
10980         }
10981         if (blank) {
10982             fprintf(f, " ");
10983             linelen++;
10984         }
10985         fprintf(f, "%s", move_buffer);
10986         linelen += movelen;
10987
10988         /* [AS] Add PV info if present */
10989         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10990             /* [HGM] add time */
10991             char buf[MSG_SIZ]; int seconds;
10992
10993             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10994
10995             if( seconds <= 0)
10996               buf[0] = 0;
10997             else
10998               if( seconds < 30 )
10999                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11000               else
11001                 {
11002                   seconds = (seconds + 4)/10; // round to full seconds
11003                   if( seconds < 60 )
11004                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11005                   else
11006                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11007                 }
11008
11009             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11010                       pvInfoList[i].score >= 0 ? "+" : "",
11011                       pvInfoList[i].score / 100.0,
11012                       pvInfoList[i].depth,
11013                       buf );
11014
11015             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11016
11017             /* Print score/depth */
11018             blank = linelen > 0 && movelen > 0;
11019             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11020                 fprintf(f, "\n");
11021                 linelen = 0;
11022                 blank = 0;
11023             }
11024             if (blank) {
11025                 fprintf(f, " ");
11026                 linelen++;
11027             }
11028             fprintf(f, "%s", move_buffer);
11029             linelen += movelen;
11030         }
11031
11032         i++;
11033     }
11034
11035     /* Start a new line */
11036     if (linelen > 0) fprintf(f, "\n");
11037
11038     /* Print comments after last move */
11039     if (commentList[i] != NULL) {
11040         fprintf(f, "%s\n", commentList[i]);
11041     }
11042
11043     /* Print result */
11044     if (gameInfo.resultDetails != NULL &&
11045         gameInfo.resultDetails[0] != NULLCHAR) {
11046         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11047                 PGNResult(gameInfo.result));
11048     } else {
11049         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11050     }
11051
11052     fclose(f);
11053     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11054     return TRUE;
11055 }
11056
11057 /* Save game in old style and close the file */
11058 int
11059 SaveGameOldStyle(f)
11060      FILE *f;
11061 {
11062     int i, offset;
11063     time_t tm;
11064
11065     tm = time((time_t *) NULL);
11066
11067     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11068     PrintOpponents(f);
11069
11070     if (backwardMostMove > 0 || startedFromSetupPosition) {
11071         fprintf(f, "\n[--------------\n");
11072         PrintPosition(f, backwardMostMove);
11073         fprintf(f, "--------------]\n");
11074     } else {
11075         fprintf(f, "\n");
11076     }
11077
11078     i = backwardMostMove;
11079     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11080
11081     while (i < forwardMostMove) {
11082         if (commentList[i] != NULL) {
11083             fprintf(f, "[%s]\n", commentList[i]);
11084         }
11085
11086         if ((i % 2) == 1) {
11087             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11088             i++;
11089         } else {
11090             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11091             i++;
11092             if (commentList[i] != NULL) {
11093                 fprintf(f, "\n");
11094                 continue;
11095             }
11096             if (i >= forwardMostMove) {
11097                 fprintf(f, "\n");
11098                 break;
11099             }
11100             fprintf(f, "%s\n", parseList[i]);
11101             i++;
11102         }
11103     }
11104
11105     if (commentList[i] != NULL) {
11106         fprintf(f, "[%s]\n", commentList[i]);
11107     }
11108
11109     /* This isn't really the old style, but it's close enough */
11110     if (gameInfo.resultDetails != NULL &&
11111         gameInfo.resultDetails[0] != NULLCHAR) {
11112         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11113                 gameInfo.resultDetails);
11114     } else {
11115         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11116     }
11117
11118     fclose(f);
11119     return TRUE;
11120 }
11121
11122 /* Save the current game to open file f and close the file */
11123 int
11124 SaveGame(f, dummy, dummy2)
11125      FILE *f;
11126      int dummy;
11127      char *dummy2;
11128 {
11129     if (gameMode == EditPosition) EditPositionDone(TRUE);
11130     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11131     if (appData.oldSaveStyle)
11132       return SaveGameOldStyle(f);
11133     else
11134       return SaveGamePGN(f);
11135 }
11136
11137 /* Save the current position to the given file */
11138 int
11139 SavePositionToFile(filename)
11140      char *filename;
11141 {
11142     FILE *f;
11143     char buf[MSG_SIZ];
11144
11145     if (strcmp(filename, "-") == 0) {
11146         return SavePosition(stdout, 0, NULL);
11147     } else {
11148         f = fopen(filename, "a");
11149         if (f == NULL) {
11150             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11151             DisplayError(buf, errno);
11152             return FALSE;
11153         } else {
11154             SavePosition(f, 0, NULL);
11155             return TRUE;
11156         }
11157     }
11158 }
11159
11160 /* Save the current position to the given open file and close the file */
11161 int
11162 SavePosition(f, dummy, dummy2)
11163      FILE *f;
11164      int dummy;
11165      char *dummy2;
11166 {
11167     time_t tm;
11168     char *fen;
11169
11170     if (gameMode == EditPosition) EditPositionDone(TRUE);
11171     if (appData.oldSaveStyle) {
11172         tm = time((time_t *) NULL);
11173
11174         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11175         PrintOpponents(f);
11176         fprintf(f, "[--------------\n");
11177         PrintPosition(f, currentMove);
11178         fprintf(f, "--------------]\n");
11179     } else {
11180         fen = PositionToFEN(currentMove, NULL);
11181         fprintf(f, "%s\n", fen);
11182         free(fen);
11183     }
11184     fclose(f);
11185     return TRUE;
11186 }
11187
11188 void
11189 ReloadCmailMsgEvent(unregister)
11190      int unregister;
11191 {
11192 #if !WIN32
11193     static char *inFilename = NULL;
11194     static char *outFilename;
11195     int i;
11196     struct stat inbuf, outbuf;
11197     int status;
11198
11199     /* Any registered moves are unregistered if unregister is set, */
11200     /* i.e. invoked by the signal handler */
11201     if (unregister) {
11202         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11203             cmailMoveRegistered[i] = FALSE;
11204             if (cmailCommentList[i] != NULL) {
11205                 free(cmailCommentList[i]);
11206                 cmailCommentList[i] = NULL;
11207             }
11208         }
11209         nCmailMovesRegistered = 0;
11210     }
11211
11212     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11213         cmailResult[i] = CMAIL_NOT_RESULT;
11214     }
11215     nCmailResults = 0;
11216
11217     if (inFilename == NULL) {
11218         /* Because the filenames are static they only get malloced once  */
11219         /* and they never get freed                                      */
11220         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11221         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11222
11223         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11224         sprintf(outFilename, "%s.out", appData.cmailGameName);
11225     }
11226
11227     status = stat(outFilename, &outbuf);
11228     if (status < 0) {
11229         cmailMailedMove = FALSE;
11230     } else {
11231         status = stat(inFilename, &inbuf);
11232         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11233     }
11234
11235     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11236        counts the games, notes how each one terminated, etc.
11237
11238        It would be nice to remove this kludge and instead gather all
11239        the information while building the game list.  (And to keep it
11240        in the game list nodes instead of having a bunch of fixed-size
11241        parallel arrays.)  Note this will require getting each game's
11242        termination from the PGN tags, as the game list builder does
11243        not process the game moves.  --mann
11244        */
11245     cmailMsgLoaded = TRUE;
11246     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11247
11248     /* Load first game in the file or popup game menu */
11249     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11250
11251 #endif /* !WIN32 */
11252     return;
11253 }
11254
11255 int
11256 RegisterMove()
11257 {
11258     FILE *f;
11259     char string[MSG_SIZ];
11260
11261     if (   cmailMailedMove
11262         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11263         return TRUE;            /* Allow free viewing  */
11264     }
11265
11266     /* Unregister move to ensure that we don't leave RegisterMove        */
11267     /* with the move registered when the conditions for registering no   */
11268     /* longer hold                                                       */
11269     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11270         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11271         nCmailMovesRegistered --;
11272
11273         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11274           {
11275               free(cmailCommentList[lastLoadGameNumber - 1]);
11276               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11277           }
11278     }
11279
11280     if (cmailOldMove == -1) {
11281         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11282         return FALSE;
11283     }
11284
11285     if (currentMove > cmailOldMove + 1) {
11286         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11287         return FALSE;
11288     }
11289
11290     if (currentMove < cmailOldMove) {
11291         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11292         return FALSE;
11293     }
11294
11295     if (forwardMostMove > currentMove) {
11296         /* Silently truncate extra moves */
11297         TruncateGame();
11298     }
11299
11300     if (   (currentMove == cmailOldMove + 1)
11301         || (   (currentMove == cmailOldMove)
11302             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11303                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11304         if (gameInfo.result != GameUnfinished) {
11305             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11306         }
11307
11308         if (commentList[currentMove] != NULL) {
11309             cmailCommentList[lastLoadGameNumber - 1]
11310               = StrSave(commentList[currentMove]);
11311         }
11312         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11313
11314         if (appData.debugMode)
11315           fprintf(debugFP, "Saving %s for game %d\n",
11316                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11317
11318         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11319
11320         f = fopen(string, "w");
11321         if (appData.oldSaveStyle) {
11322             SaveGameOldStyle(f); /* also closes the file */
11323
11324             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11325             f = fopen(string, "w");
11326             SavePosition(f, 0, NULL); /* also closes the file */
11327         } else {
11328             fprintf(f, "{--------------\n");
11329             PrintPosition(f, currentMove);
11330             fprintf(f, "--------------}\n\n");
11331
11332             SaveGame(f, 0, NULL); /* also closes the file*/
11333         }
11334
11335         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11336         nCmailMovesRegistered ++;
11337     } else if (nCmailGames == 1) {
11338         DisplayError(_("You have not made a move yet"), 0);
11339         return FALSE;
11340     }
11341
11342     return TRUE;
11343 }
11344
11345 void
11346 MailMoveEvent()
11347 {
11348 #if !WIN32
11349     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11350     FILE *commandOutput;
11351     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11352     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11353     int nBuffers;
11354     int i;
11355     int archived;
11356     char *arcDir;
11357
11358     if (! cmailMsgLoaded) {
11359         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11360         return;
11361     }
11362
11363     if (nCmailGames == nCmailResults) {
11364         DisplayError(_("No unfinished games"), 0);
11365         return;
11366     }
11367
11368 #if CMAIL_PROHIBIT_REMAIL
11369     if (cmailMailedMove) {
11370       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);
11371         DisplayError(msg, 0);
11372         return;
11373     }
11374 #endif
11375
11376     if (! (cmailMailedMove || RegisterMove())) return;
11377
11378     if (   cmailMailedMove
11379         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11380       snprintf(string, MSG_SIZ, partCommandString,
11381                appData.debugMode ? " -v" : "", appData.cmailGameName);
11382         commandOutput = popen(string, "r");
11383
11384         if (commandOutput == NULL) {
11385             DisplayError(_("Failed to invoke cmail"), 0);
11386         } else {
11387             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11388                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11389             }
11390             if (nBuffers > 1) {
11391                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11392                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11393                 nBytes = MSG_SIZ - 1;
11394             } else {
11395                 (void) memcpy(msg, buffer, nBytes);
11396             }
11397             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11398
11399             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11400                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11401
11402                 archived = TRUE;
11403                 for (i = 0; i < nCmailGames; i ++) {
11404                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11405                         archived = FALSE;
11406                     }
11407                 }
11408                 if (   archived
11409                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11410                         != NULL)) {
11411                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11412                            arcDir,
11413                            appData.cmailGameName,
11414                            gameInfo.date);
11415                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11416                     cmailMsgLoaded = FALSE;
11417                 }
11418             }
11419
11420             DisplayInformation(msg);
11421             pclose(commandOutput);
11422         }
11423     } else {
11424         if ((*cmailMsg) != '\0') {
11425             DisplayInformation(cmailMsg);
11426         }
11427     }
11428
11429     return;
11430 #endif /* !WIN32 */
11431 }
11432
11433 char *
11434 CmailMsg()
11435 {
11436 #if WIN32
11437     return NULL;
11438 #else
11439     int  prependComma = 0;
11440     char number[5];
11441     char string[MSG_SIZ];       /* Space for game-list */
11442     int  i;
11443
11444     if (!cmailMsgLoaded) return "";
11445
11446     if (cmailMailedMove) {
11447       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11448     } else {
11449         /* Create a list of games left */
11450       snprintf(string, MSG_SIZ, "[");
11451         for (i = 0; i < nCmailGames; i ++) {
11452             if (! (   cmailMoveRegistered[i]
11453                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11454                 if (prependComma) {
11455                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11456                 } else {
11457                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11458                     prependComma = 1;
11459                 }
11460
11461                 strcat(string, number);
11462             }
11463         }
11464         strcat(string, "]");
11465
11466         if (nCmailMovesRegistered + nCmailResults == 0) {
11467             switch (nCmailGames) {
11468               case 1:
11469                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11470                 break;
11471
11472               case 2:
11473                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11474                 break;
11475
11476               default:
11477                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11478                          nCmailGames);
11479                 break;
11480             }
11481         } else {
11482             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11483               case 1:
11484                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11485                          string);
11486                 break;
11487
11488               case 0:
11489                 if (nCmailResults == nCmailGames) {
11490                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11491                 } else {
11492                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11493                 }
11494                 break;
11495
11496               default:
11497                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11498                          string);
11499             }
11500         }
11501     }
11502     return cmailMsg;
11503 #endif /* WIN32 */
11504 }
11505
11506 void
11507 ResetGameEvent()
11508 {
11509     if (gameMode == Training)
11510       SetTrainingModeOff();
11511
11512     Reset(TRUE, TRUE);
11513     cmailMsgLoaded = FALSE;
11514     if (appData.icsActive) {
11515       SendToICS(ics_prefix);
11516       SendToICS("refresh\n");
11517     }
11518 }
11519
11520 void
11521 ExitEvent(status)
11522      int status;
11523 {
11524     exiting++;
11525     if (exiting > 2) {
11526       /* Give up on clean exit */
11527       exit(status);
11528     }
11529     if (exiting > 1) {
11530       /* Keep trying for clean exit */
11531       return;
11532     }
11533
11534     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11535
11536     if (telnetISR != NULL) {
11537       RemoveInputSource(telnetISR);
11538     }
11539     if (icsPR != NoProc) {
11540       DestroyChildProcess(icsPR, TRUE);
11541     }
11542
11543     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11544     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11545
11546     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11547     /* make sure this other one finishes before killing it!                  */
11548     if(endingGame) { int count = 0;
11549         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11550         while(endingGame && count++ < 10) DoSleep(1);
11551         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11552     }
11553
11554     /* Kill off chess programs */
11555     if (first.pr != NoProc) {
11556         ExitAnalyzeMode();
11557
11558         DoSleep( appData.delayBeforeQuit );
11559         SendToProgram("quit\n", &first);
11560         DoSleep( appData.delayAfterQuit );
11561         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11562     }
11563     if (second.pr != NoProc) {
11564         DoSleep( appData.delayBeforeQuit );
11565         SendToProgram("quit\n", &second);
11566         DoSleep( appData.delayAfterQuit );
11567         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11568     }
11569     if (first.isr != NULL) {
11570         RemoveInputSource(first.isr);
11571     }
11572     if (second.isr != NULL) {
11573         RemoveInputSource(second.isr);
11574     }
11575
11576     ShutDownFrontEnd();
11577     exit(status);
11578 }
11579
11580 void
11581 PauseEvent()
11582 {
11583     if (appData.debugMode)
11584         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11585     if (pausing) {
11586         pausing = FALSE;
11587         ModeHighlight();
11588         if (gameMode == MachinePlaysWhite ||
11589             gameMode == MachinePlaysBlack) {
11590             StartClocks();
11591         } else {
11592             DisplayBothClocks();
11593         }
11594         if (gameMode == PlayFromGameFile) {
11595             if (appData.timeDelay >= 0)
11596                 AutoPlayGameLoop();
11597         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11598             Reset(FALSE, TRUE);
11599             SendToICS(ics_prefix);
11600             SendToICS("refresh\n");
11601         } else if (currentMove < forwardMostMove) {
11602             ForwardInner(forwardMostMove);
11603         }
11604         pauseExamInvalid = FALSE;
11605     } else {
11606         switch (gameMode) {
11607           default:
11608             return;
11609           case IcsExamining:
11610             pauseExamForwardMostMove = forwardMostMove;
11611             pauseExamInvalid = FALSE;
11612             /* fall through */
11613           case IcsObserving:
11614           case IcsPlayingWhite:
11615           case IcsPlayingBlack:
11616             pausing = TRUE;
11617             ModeHighlight();
11618             return;
11619           case PlayFromGameFile:
11620             (void) StopLoadGameTimer();
11621             pausing = TRUE;
11622             ModeHighlight();
11623             break;
11624           case BeginningOfGame:
11625             if (appData.icsActive) return;
11626             /* else fall through */
11627           case MachinePlaysWhite:
11628           case MachinePlaysBlack:
11629           case TwoMachinesPlay:
11630             if (forwardMostMove == 0)
11631               return;           /* don't pause if no one has moved */
11632             if ((gameMode == MachinePlaysWhite &&
11633                  !WhiteOnMove(forwardMostMove)) ||
11634                 (gameMode == MachinePlaysBlack &&
11635                  WhiteOnMove(forwardMostMove))) {
11636                 StopClocks();
11637             }
11638             pausing = TRUE;
11639             ModeHighlight();
11640             break;
11641         }
11642     }
11643 }
11644
11645 void
11646 EditCommentEvent()
11647 {
11648     char title[MSG_SIZ];
11649
11650     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11651       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11652     } else {
11653       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11654                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11655                parseList[currentMove - 1]);
11656     }
11657
11658     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11659 }
11660
11661
11662 void
11663 EditTagsEvent()
11664 {
11665     char *tags = PGNTags(&gameInfo);
11666     EditTagsPopUp(tags, NULL);
11667     free(tags);
11668 }
11669
11670 void
11671 AnalyzeModeEvent()
11672 {
11673     if (appData.noChessProgram || gameMode == AnalyzeMode)
11674       return;
11675
11676     if (gameMode != AnalyzeFile) {
11677         if (!appData.icsEngineAnalyze) {
11678                EditGameEvent();
11679                if (gameMode != EditGame) return;
11680         }
11681         ResurrectChessProgram();
11682         SendToProgram("analyze\n", &first);
11683         first.analyzing = TRUE;
11684         /*first.maybeThinking = TRUE;*/
11685         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11686         EngineOutputPopUp();
11687     }
11688     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11689     pausing = FALSE;
11690     ModeHighlight();
11691     SetGameInfo();
11692
11693     StartAnalysisClock();
11694     GetTimeMark(&lastNodeCountTime);
11695     lastNodeCount = 0;
11696 }
11697
11698 void
11699 AnalyzeFileEvent()
11700 {
11701     if (appData.noChessProgram || gameMode == AnalyzeFile)
11702       return;
11703
11704     if (gameMode != AnalyzeMode) {
11705         EditGameEvent();
11706         if (gameMode != EditGame) return;
11707         ResurrectChessProgram();
11708         SendToProgram("analyze\n", &first);
11709         first.analyzing = TRUE;
11710         /*first.maybeThinking = TRUE;*/
11711         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11712         EngineOutputPopUp();
11713     }
11714     gameMode = AnalyzeFile;
11715     pausing = FALSE;
11716     ModeHighlight();
11717     SetGameInfo();
11718
11719     StartAnalysisClock();
11720     GetTimeMark(&lastNodeCountTime);
11721     lastNodeCount = 0;
11722 }
11723
11724 void
11725 MachineWhiteEvent()
11726 {
11727     char buf[MSG_SIZ];
11728     char *bookHit = NULL;
11729
11730     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11731       return;
11732
11733
11734     if (gameMode == PlayFromGameFile ||
11735         gameMode == TwoMachinesPlay  ||
11736         gameMode == Training         ||
11737         gameMode == AnalyzeMode      ||
11738         gameMode == EndOfGame)
11739         EditGameEvent();
11740
11741     if (gameMode == EditPosition)
11742         EditPositionDone(TRUE);
11743
11744     if (!WhiteOnMove(currentMove)) {
11745         DisplayError(_("It is not White's turn"), 0);
11746         return;
11747     }
11748
11749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11750       ExitAnalyzeMode();
11751
11752     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11753         gameMode == AnalyzeFile)
11754         TruncateGame();
11755
11756     ResurrectChessProgram();    /* in case it isn't running */
11757     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11758         gameMode = MachinePlaysWhite;
11759         ResetClocks();
11760     } else
11761     gameMode = MachinePlaysWhite;
11762     pausing = FALSE;
11763     ModeHighlight();
11764     SetGameInfo();
11765     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11766     DisplayTitle(buf);
11767     if (first.sendName) {
11768       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11769       SendToProgram(buf, &first);
11770     }
11771     if (first.sendTime) {
11772       if (first.useColors) {
11773         SendToProgram("black\n", &first); /*gnu kludge*/
11774       }
11775       SendTimeRemaining(&first, TRUE);
11776     }
11777     if (first.useColors) {
11778       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11779     }
11780     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11781     SetMachineThinkingEnables();
11782     first.maybeThinking = TRUE;
11783     StartClocks();
11784     firstMove = FALSE;
11785
11786     if (appData.autoFlipView && !flipView) {
11787       flipView = !flipView;
11788       DrawPosition(FALSE, NULL);
11789       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11790     }
11791
11792     if(bookHit) { // [HGM] book: simulate book reply
11793         static char bookMove[MSG_SIZ]; // a bit generous?
11794
11795         programStats.nodes = programStats.depth = programStats.time =
11796         programStats.score = programStats.got_only_move = 0;
11797         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11798
11799         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11800         strcat(bookMove, bookHit);
11801         HandleMachineMove(bookMove, &first);
11802     }
11803 }
11804
11805 void
11806 MachineBlackEvent()
11807 {
11808   char buf[MSG_SIZ];
11809   char *bookHit = NULL;
11810
11811     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11812         return;
11813
11814
11815     if (gameMode == PlayFromGameFile ||
11816         gameMode == TwoMachinesPlay  ||
11817         gameMode == Training         ||
11818         gameMode == AnalyzeMode      ||
11819         gameMode == EndOfGame)
11820         EditGameEvent();
11821
11822     if (gameMode == EditPosition)
11823         EditPositionDone(TRUE);
11824
11825     if (WhiteOnMove(currentMove)) {
11826         DisplayError(_("It is not Black's turn"), 0);
11827         return;
11828     }
11829
11830     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11831       ExitAnalyzeMode();
11832
11833     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11834         gameMode == AnalyzeFile)
11835         TruncateGame();
11836
11837     ResurrectChessProgram();    /* in case it isn't running */
11838     gameMode = MachinePlaysBlack;
11839     pausing = FALSE;
11840     ModeHighlight();
11841     SetGameInfo();
11842     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11843     DisplayTitle(buf);
11844     if (first.sendName) {
11845       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11846       SendToProgram(buf, &first);
11847     }
11848     if (first.sendTime) {
11849       if (first.useColors) {
11850         SendToProgram("white\n", &first); /*gnu kludge*/
11851       }
11852       SendTimeRemaining(&first, FALSE);
11853     }
11854     if (first.useColors) {
11855       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11856     }
11857     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11858     SetMachineThinkingEnables();
11859     first.maybeThinking = TRUE;
11860     StartClocks();
11861
11862     if (appData.autoFlipView && flipView) {
11863       flipView = !flipView;
11864       DrawPosition(FALSE, NULL);
11865       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11866     }
11867     if(bookHit) { // [HGM] book: simulate book reply
11868         static char bookMove[MSG_SIZ]; // a bit generous?
11869
11870         programStats.nodes = programStats.depth = programStats.time =
11871         programStats.score = programStats.got_only_move = 0;
11872         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11873
11874         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11875         strcat(bookMove, bookHit);
11876         HandleMachineMove(bookMove, &first);
11877     }
11878 }
11879
11880
11881 void
11882 DisplayTwoMachinesTitle()
11883 {
11884     char buf[MSG_SIZ];
11885     if (appData.matchGames > 0) {
11886         if (first.twoMachinesColor[0] == 'w') {
11887           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11888                    gameInfo.white, gameInfo.black,
11889                    first.matchWins, second.matchWins,
11890                    matchGame - 1 - (first.matchWins + second.matchWins));
11891         } else {
11892           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11893                    gameInfo.white, gameInfo.black,
11894                    second.matchWins, first.matchWins,
11895                    matchGame - 1 - (first.matchWins + second.matchWins));
11896         }
11897     } else {
11898       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11899     }
11900     DisplayTitle(buf);
11901 }
11902
11903 void
11904 SettingsMenuIfReady()
11905 {
11906   if (second.lastPing != second.lastPong) {
11907     DisplayMessage("", _("Waiting for second chess program"));
11908     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11909     return;
11910   }
11911   ThawUI();
11912   DisplayMessage("", "");
11913   SettingsPopUp(&second);
11914 }
11915
11916 int
11917 WaitForSecond(DelayedEventCallback retry)
11918 {
11919     if (second.pr == NULL) {
11920         StartChessProgram(&second);
11921         if (second.protocolVersion == 1) {
11922           retry();
11923         } else {
11924           /* kludge: allow timeout for initial "feature" command */
11925           FreezeUI();
11926           DisplayMessage("", _("Starting second chess program"));
11927           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11928         }
11929         return 1;
11930     }
11931     return 0;
11932 }
11933
11934 void
11935 TwoMachinesEvent P((void))
11936 {
11937     int i;
11938     char buf[MSG_SIZ];
11939     ChessProgramState *onmove;
11940     char *bookHit = NULL;
11941
11942     if (appData.noChessProgram) return;
11943
11944     switch (gameMode) {
11945       case TwoMachinesPlay:
11946         return;
11947       case MachinePlaysWhite:
11948       case MachinePlaysBlack:
11949         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11950             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11951             return;
11952         }
11953         /* fall through */
11954       case BeginningOfGame:
11955       case PlayFromGameFile:
11956       case EndOfGame:
11957         EditGameEvent();
11958         if (gameMode != EditGame) return;
11959         break;
11960       case EditPosition:
11961         EditPositionDone(TRUE);
11962         break;
11963       case AnalyzeMode:
11964       case AnalyzeFile:
11965         ExitAnalyzeMode();
11966         break;
11967       case EditGame:
11968       default:
11969         break;
11970     }
11971
11972 //    forwardMostMove = currentMove;
11973     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11974     ResurrectChessProgram();    /* in case first program isn't running */
11975
11976     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11977     DisplayMessage("", "");
11978     InitChessProgram(&second, FALSE);
11979     SendToProgram("force\n", &second);
11980     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11981       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11982       return;
11983     }
11984     if (startedFromSetupPosition) {
11985         SendBoard(&second, backwardMostMove);
11986     if (appData.debugMode) {
11987         fprintf(debugFP, "Two Machines\n");
11988     }
11989     }
11990     for (i = backwardMostMove; i < forwardMostMove; i++) {
11991         SendMoveToProgram(i, &second);
11992     }
11993
11994     gameMode = TwoMachinesPlay;
11995     pausing = FALSE;
11996     ModeHighlight();
11997     SetGameInfo();
11998     DisplayTwoMachinesTitle();
11999     firstMove = TRUE;
12000     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12001         onmove = &first;
12002     } else {
12003         onmove = &second;
12004     }
12005
12006     SendToProgram(first.computerString, &first);
12007     if (first.sendName) {
12008       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12009       SendToProgram(buf, &first);
12010     }
12011     SendToProgram(second.computerString, &second);
12012     if (second.sendName) {
12013       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12014       SendToProgram(buf, &second);
12015     }
12016
12017     ResetClocks();
12018     if (!first.sendTime || !second.sendTime) {
12019         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12020         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12021     }
12022     if (onmove->sendTime) {
12023       if (onmove->useColors) {
12024         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12025       }
12026       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12027     }
12028     if (onmove->useColors) {
12029       SendToProgram(onmove->twoMachinesColor, onmove);
12030     }
12031     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12032 //    SendToProgram("go\n", onmove);
12033     onmove->maybeThinking = TRUE;
12034     SetMachineThinkingEnables();
12035
12036     StartClocks();
12037
12038     if(bookHit) { // [HGM] book: simulate book reply
12039         static char bookMove[MSG_SIZ]; // a bit generous?
12040
12041         programStats.nodes = programStats.depth = programStats.time =
12042         programStats.score = programStats.got_only_move = 0;
12043         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12044
12045         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12046         strcat(bookMove, bookHit);
12047         savedMessage = bookMove; // args for deferred call
12048         savedState = onmove;
12049         ScheduleDelayedEvent(DeferredBookMove, 1);
12050     }
12051 }
12052
12053 void
12054 TrainingEvent()
12055 {
12056     if (gameMode == Training) {
12057       SetTrainingModeOff();
12058       gameMode = PlayFromGameFile;
12059       DisplayMessage("", _("Training mode off"));
12060     } else {
12061       gameMode = Training;
12062       animateTraining = appData.animate;
12063
12064       /* make sure we are not already at the end of the game */
12065       if (currentMove < forwardMostMove) {
12066         SetTrainingModeOn();
12067         DisplayMessage("", _("Training mode on"));
12068       } else {
12069         gameMode = PlayFromGameFile;
12070         DisplayError(_("Already at end of game"), 0);
12071       }
12072     }
12073     ModeHighlight();
12074 }
12075
12076 void
12077 IcsClientEvent()
12078 {
12079     if (!appData.icsActive) return;
12080     switch (gameMode) {
12081       case IcsPlayingWhite:
12082       case IcsPlayingBlack:
12083       case IcsObserving:
12084       case IcsIdle:
12085       case BeginningOfGame:
12086       case IcsExamining:
12087         return;
12088
12089       case EditGame:
12090         break;
12091
12092       case EditPosition:
12093         EditPositionDone(TRUE);
12094         break;
12095
12096       case AnalyzeMode:
12097       case AnalyzeFile:
12098         ExitAnalyzeMode();
12099         break;
12100
12101       default:
12102         EditGameEvent();
12103         break;
12104     }
12105
12106     gameMode = IcsIdle;
12107     ModeHighlight();
12108     return;
12109 }
12110
12111
12112 void
12113 EditGameEvent()
12114 {
12115     int i;
12116
12117     switch (gameMode) {
12118       case Training:
12119         SetTrainingModeOff();
12120         break;
12121       case MachinePlaysWhite:
12122       case MachinePlaysBlack:
12123       case BeginningOfGame:
12124         SendToProgram("force\n", &first);
12125         SetUserThinkingEnables();
12126         break;
12127       case PlayFromGameFile:
12128         (void) StopLoadGameTimer();
12129         if (gameFileFP != NULL) {
12130             gameFileFP = NULL;
12131         }
12132         break;
12133       case EditPosition:
12134         EditPositionDone(TRUE);
12135         break;
12136       case AnalyzeMode:
12137       case AnalyzeFile:
12138         ExitAnalyzeMode();
12139         SendToProgram("force\n", &first);
12140         break;
12141       case TwoMachinesPlay:
12142         GameEnds(EndOfFile, NULL, GE_PLAYER);
12143         ResurrectChessProgram();
12144         SetUserThinkingEnables();
12145         break;
12146       case EndOfGame:
12147         ResurrectChessProgram();
12148         break;
12149       case IcsPlayingBlack:
12150       case IcsPlayingWhite:
12151         DisplayError(_("Warning: You are still playing a game"), 0);
12152         break;
12153       case IcsObserving:
12154         DisplayError(_("Warning: You are still observing a game"), 0);
12155         break;
12156       case IcsExamining:
12157         DisplayError(_("Warning: You are still examining a game"), 0);
12158         break;
12159       case IcsIdle:
12160         break;
12161       case EditGame:
12162       default:
12163         return;
12164     }
12165
12166     pausing = FALSE;
12167     StopClocks();
12168     first.offeredDraw = second.offeredDraw = 0;
12169
12170     if (gameMode == PlayFromGameFile) {
12171         whiteTimeRemaining = timeRemaining[0][currentMove];
12172         blackTimeRemaining = timeRemaining[1][currentMove];
12173         DisplayTitle("");
12174     }
12175
12176     if (gameMode == MachinePlaysWhite ||
12177         gameMode == MachinePlaysBlack ||
12178         gameMode == TwoMachinesPlay ||
12179         gameMode == EndOfGame) {
12180         i = forwardMostMove;
12181         while (i > currentMove) {
12182             SendToProgram("undo\n", &first);
12183             i--;
12184         }
12185         whiteTimeRemaining = timeRemaining[0][currentMove];
12186         blackTimeRemaining = timeRemaining[1][currentMove];
12187         DisplayBothClocks();
12188         if (whiteFlag || blackFlag) {
12189             whiteFlag = blackFlag = 0;
12190         }
12191         DisplayTitle("");
12192     }
12193
12194     gameMode = EditGame;
12195     ModeHighlight();
12196     SetGameInfo();
12197 }
12198
12199
12200 void
12201 EditPositionEvent()
12202 {
12203     if (gameMode == EditPosition) {
12204         EditGameEvent();
12205         return;
12206     }
12207
12208     EditGameEvent();
12209     if (gameMode != EditGame) return;
12210
12211     gameMode = EditPosition;
12212     ModeHighlight();
12213     SetGameInfo();
12214     if (currentMove > 0)
12215       CopyBoard(boards[0], boards[currentMove]);
12216
12217     blackPlaysFirst = !WhiteOnMove(currentMove);
12218     ResetClocks();
12219     currentMove = forwardMostMove = backwardMostMove = 0;
12220     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12221     DisplayMove(-1);
12222 }
12223
12224 void
12225 ExitAnalyzeMode()
12226 {
12227     /* [DM] icsEngineAnalyze - possible call from other functions */
12228     if (appData.icsEngineAnalyze) {
12229         appData.icsEngineAnalyze = FALSE;
12230
12231         DisplayMessage("",_("Close ICS engine analyze..."));
12232     }
12233     if (first.analysisSupport && first.analyzing) {
12234       SendToProgram("exit\n", &first);
12235       first.analyzing = FALSE;
12236     }
12237     thinkOutput[0] = NULLCHAR;
12238 }
12239
12240 void
12241 EditPositionDone(Boolean fakeRights)
12242 {
12243     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12244
12245     startedFromSetupPosition = TRUE;
12246     InitChessProgram(&first, FALSE);
12247     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12248       boards[0][EP_STATUS] = EP_NONE;
12249       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12250     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12251         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12252         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12253       } else boards[0][CASTLING][2] = NoRights;
12254     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12255         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12256         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12257       } else boards[0][CASTLING][5] = NoRights;
12258     }
12259     SendToProgram("force\n", &first);
12260     if (blackPlaysFirst) {
12261         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12262         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12263         currentMove = forwardMostMove = backwardMostMove = 1;
12264         CopyBoard(boards[1], boards[0]);
12265     } else {
12266         currentMove = forwardMostMove = backwardMostMove = 0;
12267     }
12268     SendBoard(&first, forwardMostMove);
12269     if (appData.debugMode) {
12270         fprintf(debugFP, "EditPosDone\n");
12271     }
12272     DisplayTitle("");
12273     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12274     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12275     gameMode = EditGame;
12276     ModeHighlight();
12277     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12278     ClearHighlights(); /* [AS] */
12279 }
12280
12281 /* Pause for `ms' milliseconds */
12282 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12283 void
12284 TimeDelay(ms)
12285      long ms;
12286 {
12287     TimeMark m1, m2;
12288
12289     GetTimeMark(&m1);
12290     do {
12291         GetTimeMark(&m2);
12292     } while (SubtractTimeMarks(&m2, &m1) < ms);
12293 }
12294
12295 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12296 void
12297 SendMultiLineToICS(buf)
12298      char *buf;
12299 {
12300     char temp[MSG_SIZ+1], *p;
12301     int len;
12302
12303     len = strlen(buf);
12304     if (len > MSG_SIZ)
12305       len = MSG_SIZ;
12306
12307     strncpy(temp, buf, len);
12308     temp[len] = 0;
12309
12310     p = temp;
12311     while (*p) {
12312         if (*p == '\n' || *p == '\r')
12313           *p = ' ';
12314         ++p;
12315     }
12316
12317     strcat(temp, "\n");
12318     SendToICS(temp);
12319     SendToPlayer(temp, strlen(temp));
12320 }
12321
12322 void
12323 SetWhiteToPlayEvent()
12324 {
12325     if (gameMode == EditPosition) {
12326         blackPlaysFirst = FALSE;
12327         DisplayBothClocks();    /* works because currentMove is 0 */
12328     } else if (gameMode == IcsExamining) {
12329         SendToICS(ics_prefix);
12330         SendToICS("tomove white\n");
12331     }
12332 }
12333
12334 void
12335 SetBlackToPlayEvent()
12336 {
12337     if (gameMode == EditPosition) {
12338         blackPlaysFirst = TRUE;
12339         currentMove = 1;        /* kludge */
12340         DisplayBothClocks();
12341         currentMove = 0;
12342     } else if (gameMode == IcsExamining) {
12343         SendToICS(ics_prefix);
12344         SendToICS("tomove black\n");
12345     }
12346 }
12347
12348 void
12349 EditPositionMenuEvent(selection, x, y)
12350      ChessSquare selection;
12351      int x, y;
12352 {
12353     char buf[MSG_SIZ];
12354     ChessSquare piece = boards[0][y][x];
12355
12356     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12357
12358     switch (selection) {
12359       case ClearBoard:
12360         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12361             SendToICS(ics_prefix);
12362             SendToICS("bsetup clear\n");
12363         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12364             SendToICS(ics_prefix);
12365             SendToICS("clearboard\n");
12366         } else {
12367             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12368                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12369                 for (y = 0; y < BOARD_HEIGHT; y++) {
12370                     if (gameMode == IcsExamining) {
12371                         if (boards[currentMove][y][x] != EmptySquare) {
12372                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12373                                     AAA + x, ONE + y);
12374                             SendToICS(buf);
12375                         }
12376                     } else {
12377                         boards[0][y][x] = p;
12378                     }
12379                 }
12380             }
12381         }
12382         if (gameMode == EditPosition) {
12383             DrawPosition(FALSE, boards[0]);
12384         }
12385         break;
12386
12387       case WhitePlay:
12388         SetWhiteToPlayEvent();
12389         break;
12390
12391       case BlackPlay:
12392         SetBlackToPlayEvent();
12393         break;
12394
12395       case EmptySquare:
12396         if (gameMode == IcsExamining) {
12397             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12398             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12399             SendToICS(buf);
12400         } else {
12401             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12402                 if(x == BOARD_LEFT-2) {
12403                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12404                     boards[0][y][1] = 0;
12405                 } else
12406                 if(x == BOARD_RGHT+1) {
12407                     if(y >= gameInfo.holdingsSize) break;
12408                     boards[0][y][BOARD_WIDTH-2] = 0;
12409                 } else break;
12410             }
12411             boards[0][y][x] = EmptySquare;
12412             DrawPosition(FALSE, boards[0]);
12413         }
12414         break;
12415
12416       case PromotePiece:
12417         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12418            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12419             selection = (ChessSquare) (PROMOTED piece);
12420         } else if(piece == EmptySquare) selection = WhiteSilver;
12421         else selection = (ChessSquare)((int)piece - 1);
12422         goto defaultlabel;
12423
12424       case DemotePiece:
12425         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12426            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12427             selection = (ChessSquare) (DEMOTED piece);
12428         } else if(piece == EmptySquare) selection = BlackSilver;
12429         else selection = (ChessSquare)((int)piece + 1);
12430         goto defaultlabel;
12431
12432       case WhiteQueen:
12433       case BlackQueen:
12434         if(gameInfo.variant == VariantShatranj ||
12435            gameInfo.variant == VariantXiangqi  ||
12436            gameInfo.variant == VariantCourier  ||
12437            gameInfo.variant == VariantMakruk     )
12438             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12439         goto defaultlabel;
12440
12441       case WhiteKing:
12442       case BlackKing:
12443         if(gameInfo.variant == VariantXiangqi)
12444             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12445         if(gameInfo.variant == VariantKnightmate)
12446             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12447       default:
12448         defaultlabel:
12449         if (gameMode == IcsExamining) {
12450             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12451             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12452                      PieceToChar(selection), AAA + x, ONE + y);
12453             SendToICS(buf);
12454         } else {
12455             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12456                 int n;
12457                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12458                     n = PieceToNumber(selection - BlackPawn);
12459                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12460                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12461                     boards[0][BOARD_HEIGHT-1-n][1]++;
12462                 } else
12463                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12464                     n = PieceToNumber(selection);
12465                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12466                     boards[0][n][BOARD_WIDTH-1] = selection;
12467                     boards[0][n][BOARD_WIDTH-2]++;
12468                 }
12469             } else
12470             boards[0][y][x] = selection;
12471             DrawPosition(TRUE, boards[0]);
12472         }
12473         break;
12474     }
12475 }
12476
12477
12478 void
12479 DropMenuEvent(selection, x, y)
12480      ChessSquare selection;
12481      int x, y;
12482 {
12483     ChessMove moveType;
12484
12485     switch (gameMode) {
12486       case IcsPlayingWhite:
12487       case MachinePlaysBlack:
12488         if (!WhiteOnMove(currentMove)) {
12489             DisplayMoveError(_("It is Black's turn"));
12490             return;
12491         }
12492         moveType = WhiteDrop;
12493         break;
12494       case IcsPlayingBlack:
12495       case MachinePlaysWhite:
12496         if (WhiteOnMove(currentMove)) {
12497             DisplayMoveError(_("It is White's turn"));
12498             return;
12499         }
12500         moveType = BlackDrop;
12501         break;
12502       case EditGame:
12503         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12504         break;
12505       default:
12506         return;
12507     }
12508
12509     if (moveType == BlackDrop && selection < BlackPawn) {
12510       selection = (ChessSquare) ((int) selection
12511                                  + (int) BlackPawn - (int) WhitePawn);
12512     }
12513     if (boards[currentMove][y][x] != EmptySquare) {
12514         DisplayMoveError(_("That square is occupied"));
12515         return;
12516     }
12517
12518     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12519 }
12520
12521 void
12522 AcceptEvent()
12523 {
12524     /* Accept a pending offer of any kind from opponent */
12525
12526     if (appData.icsActive) {
12527         SendToICS(ics_prefix);
12528         SendToICS("accept\n");
12529     } else if (cmailMsgLoaded) {
12530         if (currentMove == cmailOldMove &&
12531             commentList[cmailOldMove] != NULL &&
12532             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12533                    "Black offers a draw" : "White offers a draw")) {
12534             TruncateGame();
12535             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12536             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12537         } else {
12538             DisplayError(_("There is no pending offer on this move"), 0);
12539             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12540         }
12541     } else {
12542         /* Not used for offers from chess program */
12543     }
12544 }
12545
12546 void
12547 DeclineEvent()
12548 {
12549     /* Decline a pending offer of any kind from opponent */
12550
12551     if (appData.icsActive) {
12552         SendToICS(ics_prefix);
12553         SendToICS("decline\n");
12554     } else if (cmailMsgLoaded) {
12555         if (currentMove == cmailOldMove &&
12556             commentList[cmailOldMove] != NULL &&
12557             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12558                    "Black offers a draw" : "White offers a draw")) {
12559 #ifdef NOTDEF
12560             AppendComment(cmailOldMove, "Draw declined", TRUE);
12561             DisplayComment(cmailOldMove - 1, "Draw declined");
12562 #endif /*NOTDEF*/
12563         } else {
12564             DisplayError(_("There is no pending offer on this move"), 0);
12565         }
12566     } else {
12567         /* Not used for offers from chess program */
12568     }
12569 }
12570
12571 void
12572 RematchEvent()
12573 {
12574     /* Issue ICS rematch command */
12575     if (appData.icsActive) {
12576         SendToICS(ics_prefix);
12577         SendToICS("rematch\n");
12578     }
12579 }
12580
12581 void
12582 CallFlagEvent()
12583 {
12584     /* Call your opponent's flag (claim a win on time) */
12585     if (appData.icsActive) {
12586         SendToICS(ics_prefix);
12587         SendToICS("flag\n");
12588     } else {
12589         switch (gameMode) {
12590           default:
12591             return;
12592           case MachinePlaysWhite:
12593             if (whiteFlag) {
12594                 if (blackFlag)
12595                   GameEnds(GameIsDrawn, "Both players ran out of time",
12596                            GE_PLAYER);
12597                 else
12598                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12599             } else {
12600                 DisplayError(_("Your opponent is not out of time"), 0);
12601             }
12602             break;
12603           case MachinePlaysBlack:
12604             if (blackFlag) {
12605                 if (whiteFlag)
12606                   GameEnds(GameIsDrawn, "Both players ran out of time",
12607                            GE_PLAYER);
12608                 else
12609                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12610             } else {
12611                 DisplayError(_("Your opponent is not out of time"), 0);
12612             }
12613             break;
12614         }
12615     }
12616 }
12617
12618 void
12619 ClockClick(int which)
12620 {       // [HGM] code moved to back-end from winboard.c
12621         if(which) { // black clock
12622           if (gameMode == EditPosition || gameMode == IcsExamining) {
12623             SetBlackToPlayEvent();
12624           } else if (gameMode == EditGame || shiftKey) {
12625             AdjustClock(which, -1);
12626           } else if (gameMode == IcsPlayingWhite ||
12627                      gameMode == MachinePlaysBlack) {
12628             CallFlagEvent();
12629           }
12630         } else { // white clock
12631           if (gameMode == EditPosition || gameMode == IcsExamining) {
12632             SetWhiteToPlayEvent();
12633           } else if (gameMode == EditGame || shiftKey) {
12634             AdjustClock(which, -1);
12635           } else if (gameMode == IcsPlayingBlack ||
12636                    gameMode == MachinePlaysWhite) {
12637             CallFlagEvent();
12638           }
12639         }
12640 }
12641
12642 void
12643 DrawEvent()
12644 {
12645     /* Offer draw or accept pending draw offer from opponent */
12646
12647     if (appData.icsActive) {
12648         /* Note: tournament rules require draw offers to be
12649            made after you make your move but before you punch
12650            your clock.  Currently ICS doesn't let you do that;
12651            instead, you immediately punch your clock after making
12652            a move, but you can offer a draw at any time. */
12653
12654         SendToICS(ics_prefix);
12655         SendToICS("draw\n");
12656         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12657     } else if (cmailMsgLoaded) {
12658         if (currentMove == cmailOldMove &&
12659             commentList[cmailOldMove] != NULL &&
12660             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12661                    "Black offers a draw" : "White offers a draw")) {
12662             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12663             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12664         } else if (currentMove == cmailOldMove + 1) {
12665             char *offer = WhiteOnMove(cmailOldMove) ?
12666               "White offers a draw" : "Black offers a draw";
12667             AppendComment(currentMove, offer, TRUE);
12668             DisplayComment(currentMove - 1, offer);
12669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12670         } else {
12671             DisplayError(_("You must make your move before offering a draw"), 0);
12672             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12673         }
12674     } else if (first.offeredDraw) {
12675         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12676     } else {
12677         if (first.sendDrawOffers) {
12678             SendToProgram("draw\n", &first);
12679             userOfferedDraw = TRUE;
12680         }
12681     }
12682 }
12683
12684 void
12685 AdjournEvent()
12686 {
12687     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12688
12689     if (appData.icsActive) {
12690         SendToICS(ics_prefix);
12691         SendToICS("adjourn\n");
12692     } else {
12693         /* Currently GNU Chess doesn't offer or accept Adjourns */
12694     }
12695 }
12696
12697
12698 void
12699 AbortEvent()
12700 {
12701     /* Offer Abort or accept pending Abort offer from opponent */
12702
12703     if (appData.icsActive) {
12704         SendToICS(ics_prefix);
12705         SendToICS("abort\n");
12706     } else {
12707         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12708     }
12709 }
12710
12711 void
12712 ResignEvent()
12713 {
12714     /* Resign.  You can do this even if it's not your turn. */
12715
12716     if (appData.icsActive) {
12717         SendToICS(ics_prefix);
12718         SendToICS("resign\n");
12719     } else {
12720         switch (gameMode) {
12721           case MachinePlaysWhite:
12722             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12723             break;
12724           case MachinePlaysBlack:
12725             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12726             break;
12727           case EditGame:
12728             if (cmailMsgLoaded) {
12729                 TruncateGame();
12730                 if (WhiteOnMove(cmailOldMove)) {
12731                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12732                 } else {
12733                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12734                 }
12735                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12736             }
12737             break;
12738           default:
12739             break;
12740         }
12741     }
12742 }
12743
12744
12745 void
12746 StopObservingEvent()
12747 {
12748     /* Stop observing current games */
12749     SendToICS(ics_prefix);
12750     SendToICS("unobserve\n");
12751 }
12752
12753 void
12754 StopExaminingEvent()
12755 {
12756     /* Stop observing current game */
12757     SendToICS(ics_prefix);
12758     SendToICS("unexamine\n");
12759 }
12760
12761 void
12762 ForwardInner(target)
12763      int target;
12764 {
12765     int limit;
12766
12767     if (appData.debugMode)
12768         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12769                 target, currentMove, forwardMostMove);
12770
12771     if (gameMode == EditPosition)
12772       return;
12773
12774     if (gameMode == PlayFromGameFile && !pausing)
12775       PauseEvent();
12776
12777     if (gameMode == IcsExamining && pausing)
12778       limit = pauseExamForwardMostMove;
12779     else
12780       limit = forwardMostMove;
12781
12782     if (target > limit) target = limit;
12783
12784     if (target > 0 && moveList[target - 1][0]) {
12785         int fromX, fromY, toX, toY;
12786         toX = moveList[target - 1][2] - AAA;
12787         toY = moveList[target - 1][3] - ONE;
12788         if (moveList[target - 1][1] == '@') {
12789             if (appData.highlightLastMove) {
12790                 SetHighlights(-1, -1, toX, toY);
12791             }
12792         } else {
12793             fromX = moveList[target - 1][0] - AAA;
12794             fromY = moveList[target - 1][1] - ONE;
12795             if (target == currentMove + 1) {
12796                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12797             }
12798             if (appData.highlightLastMove) {
12799                 SetHighlights(fromX, fromY, toX, toY);
12800             }
12801         }
12802     }
12803     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12804         gameMode == Training || gameMode == PlayFromGameFile ||
12805         gameMode == AnalyzeFile) {
12806         while (currentMove < target) {
12807             SendMoveToProgram(currentMove++, &first);
12808         }
12809     } else {
12810         currentMove = target;
12811     }
12812
12813     if (gameMode == EditGame || gameMode == EndOfGame) {
12814         whiteTimeRemaining = timeRemaining[0][currentMove];
12815         blackTimeRemaining = timeRemaining[1][currentMove];
12816     }
12817     DisplayBothClocks();
12818     DisplayMove(currentMove - 1);
12819     DrawPosition(FALSE, boards[currentMove]);
12820     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12821     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12822         DisplayComment(currentMove - 1, commentList[currentMove]);
12823     }
12824 }
12825
12826
12827 void
12828 ForwardEvent()
12829 {
12830     if (gameMode == IcsExamining && !pausing) {
12831         SendToICS(ics_prefix);
12832         SendToICS("forward\n");
12833     } else {
12834         ForwardInner(currentMove + 1);
12835     }
12836 }
12837
12838 void
12839 ToEndEvent()
12840 {
12841     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12842         /* to optimze, we temporarily turn off analysis mode while we feed
12843          * the remaining moves to the engine. Otherwise we get analysis output
12844          * after each move.
12845          */
12846         if (first.analysisSupport) {
12847           SendToProgram("exit\nforce\n", &first);
12848           first.analyzing = FALSE;
12849         }
12850     }
12851
12852     if (gameMode == IcsExamining && !pausing) {
12853         SendToICS(ics_prefix);
12854         SendToICS("forward 999999\n");
12855     } else {
12856         ForwardInner(forwardMostMove);
12857     }
12858
12859     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12860         /* we have fed all the moves, so reactivate analysis mode */
12861         SendToProgram("analyze\n", &first);
12862         first.analyzing = TRUE;
12863         /*first.maybeThinking = TRUE;*/
12864         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12865     }
12866 }
12867
12868 void
12869 BackwardInner(target)
12870      int target;
12871 {
12872     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12873
12874     if (appData.debugMode)
12875         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12876                 target, currentMove, forwardMostMove);
12877
12878     if (gameMode == EditPosition) return;
12879     if (currentMove <= backwardMostMove) {
12880         ClearHighlights();
12881         DrawPosition(full_redraw, boards[currentMove]);
12882         return;
12883     }
12884     if (gameMode == PlayFromGameFile && !pausing)
12885       PauseEvent();
12886
12887     if (moveList[target][0]) {
12888         int fromX, fromY, toX, toY;
12889         toX = moveList[target][2] - AAA;
12890         toY = moveList[target][3] - ONE;
12891         if (moveList[target][1] == '@') {
12892             if (appData.highlightLastMove) {
12893                 SetHighlights(-1, -1, toX, toY);
12894             }
12895         } else {
12896             fromX = moveList[target][0] - AAA;
12897             fromY = moveList[target][1] - ONE;
12898             if (target == currentMove - 1) {
12899                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12900             }
12901             if (appData.highlightLastMove) {
12902                 SetHighlights(fromX, fromY, toX, toY);
12903             }
12904         }
12905     }
12906     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12907         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12908         while (currentMove > target) {
12909             SendToProgram("undo\n", &first);
12910             currentMove--;
12911         }
12912     } else {
12913         currentMove = target;
12914     }
12915
12916     if (gameMode == EditGame || gameMode == EndOfGame) {
12917         whiteTimeRemaining = timeRemaining[0][currentMove];
12918         blackTimeRemaining = timeRemaining[1][currentMove];
12919     }
12920     DisplayBothClocks();
12921     DisplayMove(currentMove - 1);
12922     DrawPosition(full_redraw, boards[currentMove]);
12923     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12924     // [HGM] PV info: routine tests if comment empty
12925     DisplayComment(currentMove - 1, commentList[currentMove]);
12926 }
12927
12928 void
12929 BackwardEvent()
12930 {
12931     if (gameMode == IcsExamining && !pausing) {
12932         SendToICS(ics_prefix);
12933         SendToICS("backward\n");
12934     } else {
12935         BackwardInner(currentMove - 1);
12936     }
12937 }
12938
12939 void
12940 ToStartEvent()
12941 {
12942     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12943         /* to optimize, we temporarily turn off analysis mode while we undo
12944          * all the moves. Otherwise we get analysis output after each undo.
12945          */
12946         if (first.analysisSupport) {
12947           SendToProgram("exit\nforce\n", &first);
12948           first.analyzing = FALSE;
12949         }
12950     }
12951
12952     if (gameMode == IcsExamining && !pausing) {
12953         SendToICS(ics_prefix);
12954         SendToICS("backward 999999\n");
12955     } else {
12956         BackwardInner(backwardMostMove);
12957     }
12958
12959     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12960         /* we have fed all the moves, so reactivate analysis mode */
12961         SendToProgram("analyze\n", &first);
12962         first.analyzing = TRUE;
12963         /*first.maybeThinking = TRUE;*/
12964         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12965     }
12966 }
12967
12968 void
12969 ToNrEvent(int to)
12970 {
12971   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12972   if (to >= forwardMostMove) to = forwardMostMove;
12973   if (to <= backwardMostMove) to = backwardMostMove;
12974   if (to < currentMove) {
12975     BackwardInner(to);
12976   } else {
12977     ForwardInner(to);
12978   }
12979 }
12980
12981 void
12982 RevertEvent(Boolean annotate)
12983 {
12984     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12985         return;
12986     }
12987     if (gameMode != IcsExamining) {
12988         DisplayError(_("You are not examining a game"), 0);
12989         return;
12990     }
12991     if (pausing) {
12992         DisplayError(_("You can't revert while pausing"), 0);
12993         return;
12994     }
12995     SendToICS(ics_prefix);
12996     SendToICS("revert\n");
12997 }
12998
12999 void
13000 RetractMoveEvent()
13001 {
13002     switch (gameMode) {
13003       case MachinePlaysWhite:
13004       case MachinePlaysBlack:
13005         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13006             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13007             return;
13008         }
13009         if (forwardMostMove < 2) return;
13010         currentMove = forwardMostMove = forwardMostMove - 2;
13011         whiteTimeRemaining = timeRemaining[0][currentMove];
13012         blackTimeRemaining = timeRemaining[1][currentMove];
13013         DisplayBothClocks();
13014         DisplayMove(currentMove - 1);
13015         ClearHighlights();/*!! could figure this out*/
13016         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13017         SendToProgram("remove\n", &first);
13018         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13019         break;
13020
13021       case BeginningOfGame:
13022       default:
13023         break;
13024
13025       case IcsPlayingWhite:
13026       case IcsPlayingBlack:
13027         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13028             SendToICS(ics_prefix);
13029             SendToICS("takeback 2\n");
13030         } else {
13031             SendToICS(ics_prefix);
13032             SendToICS("takeback 1\n");
13033         }
13034         break;
13035     }
13036 }
13037
13038 void
13039 MoveNowEvent()
13040 {
13041     ChessProgramState *cps;
13042
13043     switch (gameMode) {
13044       case MachinePlaysWhite:
13045         if (!WhiteOnMove(forwardMostMove)) {
13046             DisplayError(_("It is your turn"), 0);
13047             return;
13048         }
13049         cps = &first;
13050         break;
13051       case MachinePlaysBlack:
13052         if (WhiteOnMove(forwardMostMove)) {
13053             DisplayError(_("It is your turn"), 0);
13054             return;
13055         }
13056         cps = &first;
13057         break;
13058       case TwoMachinesPlay:
13059         if (WhiteOnMove(forwardMostMove) ==
13060             (first.twoMachinesColor[0] == 'w')) {
13061             cps = &first;
13062         } else {
13063             cps = &second;
13064         }
13065         break;
13066       case BeginningOfGame:
13067       default:
13068         return;
13069     }
13070     SendToProgram("?\n", cps);
13071 }
13072
13073 void
13074 TruncateGameEvent()
13075 {
13076     EditGameEvent();
13077     if (gameMode != EditGame) return;
13078     TruncateGame();
13079 }
13080
13081 void
13082 TruncateGame()
13083 {
13084     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13085     if (forwardMostMove > currentMove) {
13086         if (gameInfo.resultDetails != NULL) {
13087             free(gameInfo.resultDetails);
13088             gameInfo.resultDetails = NULL;
13089             gameInfo.result = GameUnfinished;
13090         }
13091         forwardMostMove = currentMove;
13092         HistorySet(parseList, backwardMostMove, forwardMostMove,
13093                    currentMove-1);
13094     }
13095 }
13096
13097 void
13098 HintEvent()
13099 {
13100     if (appData.noChessProgram) return;
13101     switch (gameMode) {
13102       case MachinePlaysWhite:
13103         if (WhiteOnMove(forwardMostMove)) {
13104             DisplayError(_("Wait until your turn"), 0);
13105             return;
13106         }
13107         break;
13108       case BeginningOfGame:
13109       case MachinePlaysBlack:
13110         if (!WhiteOnMove(forwardMostMove)) {
13111             DisplayError(_("Wait until your turn"), 0);
13112             return;
13113         }
13114         break;
13115       default:
13116         DisplayError(_("No hint available"), 0);
13117         return;
13118     }
13119     SendToProgram("hint\n", &first);
13120     hintRequested = TRUE;
13121 }
13122
13123 void
13124 BookEvent()
13125 {
13126     if (appData.noChessProgram) return;
13127     switch (gameMode) {
13128       case MachinePlaysWhite:
13129         if (WhiteOnMove(forwardMostMove)) {
13130             DisplayError(_("Wait until your turn"), 0);
13131             return;
13132         }
13133         break;
13134       case BeginningOfGame:
13135       case MachinePlaysBlack:
13136         if (!WhiteOnMove(forwardMostMove)) {
13137             DisplayError(_("Wait until your turn"), 0);
13138             return;
13139         }
13140         break;
13141       case EditPosition:
13142         EditPositionDone(TRUE);
13143         break;
13144       case TwoMachinesPlay:
13145         return;
13146       default:
13147         break;
13148     }
13149     SendToProgram("bk\n", &first);
13150     bookOutput[0] = NULLCHAR;
13151     bookRequested = TRUE;
13152 }
13153
13154 void
13155 AboutGameEvent()
13156 {
13157     char *tags = PGNTags(&gameInfo);
13158     TagsPopUp(tags, CmailMsg());
13159     free(tags);
13160 }
13161
13162 /* end button procedures */
13163
13164 void
13165 PrintPosition(fp, move)
13166      FILE *fp;
13167      int move;
13168 {
13169     int i, j;
13170
13171     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13172         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13173             char c = PieceToChar(boards[move][i][j]);
13174             fputc(c == 'x' ? '.' : c, fp);
13175             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13176         }
13177     }
13178     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13179       fprintf(fp, "white to play\n");
13180     else
13181       fprintf(fp, "black to play\n");
13182 }
13183
13184 void
13185 PrintOpponents(fp)
13186      FILE *fp;
13187 {
13188     if (gameInfo.white != NULL) {
13189         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13190     } else {
13191         fprintf(fp, "\n");
13192     }
13193 }
13194
13195 /* Find last component of program's own name, using some heuristics */
13196 void
13197 TidyProgramName(prog, host, buf)
13198      char *prog, *host, buf[MSG_SIZ];
13199 {
13200     char *p, *q;
13201     int local = (strcmp(host, "localhost") == 0);
13202     while (!local && (p = strchr(prog, ';')) != NULL) {
13203         p++;
13204         while (*p == ' ') p++;
13205         prog = p;
13206     }
13207     if (*prog == '"' || *prog == '\'') {
13208         q = strchr(prog + 1, *prog);
13209     } else {
13210         q = strchr(prog, ' ');
13211     }
13212     if (q == NULL) q = prog + strlen(prog);
13213     p = q;
13214     while (p >= prog && *p != '/' && *p != '\\') p--;
13215     p++;
13216     if(p == prog && *p == '"') p++;
13217     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13218     memcpy(buf, p, q - p);
13219     buf[q - p] = NULLCHAR;
13220     if (!local) {
13221         strcat(buf, "@");
13222         strcat(buf, host);
13223     }
13224 }
13225
13226 char *
13227 TimeControlTagValue()
13228 {
13229     char buf[MSG_SIZ];
13230     if (!appData.clockMode) {
13231       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13232     } else if (movesPerSession > 0) {
13233       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13234     } else if (timeIncrement == 0) {
13235       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13236     } else {
13237       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13238     }
13239     return StrSave(buf);
13240 }
13241
13242 void
13243 SetGameInfo()
13244 {
13245     /* This routine is used only for certain modes */
13246     VariantClass v = gameInfo.variant;
13247     ChessMove r = GameUnfinished;
13248     char *p = NULL;
13249
13250     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13251         r = gameInfo.result;
13252         p = gameInfo.resultDetails;
13253         gameInfo.resultDetails = NULL;
13254     }
13255     ClearGameInfo(&gameInfo);
13256     gameInfo.variant = v;
13257
13258     switch (gameMode) {
13259       case MachinePlaysWhite:
13260         gameInfo.event = StrSave( appData.pgnEventHeader );
13261         gameInfo.site = StrSave(HostName());
13262         gameInfo.date = PGNDate();
13263         gameInfo.round = StrSave("-");
13264         gameInfo.white = StrSave(first.tidy);
13265         gameInfo.black = StrSave(UserName());
13266         gameInfo.timeControl = TimeControlTagValue();
13267         break;
13268
13269       case MachinePlaysBlack:
13270         gameInfo.event = StrSave( appData.pgnEventHeader );
13271         gameInfo.site = StrSave(HostName());
13272         gameInfo.date = PGNDate();
13273         gameInfo.round = StrSave("-");
13274         gameInfo.white = StrSave(UserName());
13275         gameInfo.black = StrSave(first.tidy);
13276         gameInfo.timeControl = TimeControlTagValue();
13277         break;
13278
13279       case TwoMachinesPlay:
13280         gameInfo.event = StrSave( appData.pgnEventHeader );
13281         gameInfo.site = StrSave(HostName());
13282         gameInfo.date = PGNDate();
13283         if (matchGame > 0) {
13284             char buf[MSG_SIZ];
13285             snprintf(buf, MSG_SIZ, "%d", matchGame);
13286             gameInfo.round = StrSave(buf);
13287         } else {
13288             gameInfo.round = StrSave("-");
13289         }
13290         if (first.twoMachinesColor[0] == 'w') {
13291             gameInfo.white = StrSave(first.tidy);
13292             gameInfo.black = StrSave(second.tidy);
13293         } else {
13294             gameInfo.white = StrSave(second.tidy);
13295             gameInfo.black = StrSave(first.tidy);
13296         }
13297         gameInfo.timeControl = TimeControlTagValue();
13298         break;
13299
13300       case EditGame:
13301         gameInfo.event = StrSave("Edited game");
13302         gameInfo.site = StrSave(HostName());
13303         gameInfo.date = PGNDate();
13304         gameInfo.round = StrSave("-");
13305         gameInfo.white = StrSave("-");
13306         gameInfo.black = StrSave("-");
13307         gameInfo.result = r;
13308         gameInfo.resultDetails = p;
13309         break;
13310
13311       case EditPosition:
13312         gameInfo.event = StrSave("Edited position");
13313         gameInfo.site = StrSave(HostName());
13314         gameInfo.date = PGNDate();
13315         gameInfo.round = StrSave("-");
13316         gameInfo.white = StrSave("-");
13317         gameInfo.black = StrSave("-");
13318         break;
13319
13320       case IcsPlayingWhite:
13321       case IcsPlayingBlack:
13322       case IcsObserving:
13323       case IcsExamining:
13324         break;
13325
13326       case PlayFromGameFile:
13327         gameInfo.event = StrSave("Game from non-PGN file");
13328         gameInfo.site = StrSave(HostName());
13329         gameInfo.date = PGNDate();
13330         gameInfo.round = StrSave("-");
13331         gameInfo.white = StrSave("?");
13332         gameInfo.black = StrSave("?");
13333         break;
13334
13335       default:
13336         break;
13337     }
13338 }
13339
13340 void
13341 ReplaceComment(index, text)
13342      int index;
13343      char *text;
13344 {
13345     int len;
13346     char *p;
13347     float score;
13348
13349     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13350        pvInfoList[index-1].depth == len &&
13351        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13352        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13353     while (*text == '\n') text++;
13354     len = strlen(text);
13355     while (len > 0 && text[len - 1] == '\n') len--;
13356
13357     if (commentList[index] != NULL)
13358       free(commentList[index]);
13359
13360     if (len == 0) {
13361         commentList[index] = NULL;
13362         return;
13363     }
13364   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13365       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13366       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13367     commentList[index] = (char *) malloc(len + 2);
13368     strncpy(commentList[index], text, len);
13369     commentList[index][len] = '\n';
13370     commentList[index][len + 1] = NULLCHAR;
13371   } else {
13372     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13373     char *p;
13374     commentList[index] = (char *) malloc(len + 7);
13375     safeStrCpy(commentList[index], "{\n", 3);
13376     safeStrCpy(commentList[index]+2, text, len+1);
13377     commentList[index][len+2] = NULLCHAR;
13378     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13379     strcat(commentList[index], "\n}\n");
13380   }
13381 }
13382
13383 void
13384 CrushCRs(text)
13385      char *text;
13386 {
13387   char *p = text;
13388   char *q = text;
13389   char ch;
13390
13391   do {
13392     ch = *p++;
13393     if (ch == '\r') continue;
13394     *q++ = ch;
13395   } while (ch != '\0');
13396 }
13397
13398 void
13399 AppendComment(index, text, addBraces)
13400      int index;
13401      char *text;
13402      Boolean addBraces; // [HGM] braces: tells if we should add {}
13403 {
13404     int oldlen, len;
13405     char *old;
13406
13407 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13408     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13409
13410     CrushCRs(text);
13411     while (*text == '\n') text++;
13412     len = strlen(text);
13413     while (len > 0 && text[len - 1] == '\n') len--;
13414
13415     if (len == 0) return;
13416
13417     if (commentList[index] != NULL) {
13418         old = commentList[index];
13419         oldlen = strlen(old);
13420         while(commentList[index][oldlen-1] ==  '\n')
13421           commentList[index][--oldlen] = NULLCHAR;
13422         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13423         safeStrCpy(commentList[index], old, oldlen + len + 6);
13424         free(old);
13425         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13426         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13427           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13428           while (*text == '\n') { text++; len--; }
13429           commentList[index][--oldlen] = NULLCHAR;
13430       }
13431         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13432         else          strcat(commentList[index], "\n");
13433         strcat(commentList[index], text);
13434         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13435         else          strcat(commentList[index], "\n");
13436     } else {
13437         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13438         if(addBraces)
13439           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13440         else commentList[index][0] = NULLCHAR;
13441         strcat(commentList[index], text);
13442         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13443         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13444     }
13445 }
13446
13447 static char * FindStr( char * text, char * sub_text )
13448 {
13449     char * result = strstr( text, sub_text );
13450
13451     if( result != NULL ) {
13452         result += strlen( sub_text );
13453     }
13454
13455     return result;
13456 }
13457
13458 /* [AS] Try to extract PV info from PGN comment */
13459 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13460 char *GetInfoFromComment( int index, char * text )
13461 {
13462     char * sep = text, *p;
13463
13464     if( text != NULL && index > 0 ) {
13465         int score = 0;
13466         int depth = 0;
13467         int time = -1, sec = 0, deci;
13468         char * s_eval = FindStr( text, "[%eval " );
13469         char * s_emt = FindStr( text, "[%emt " );
13470
13471         if( s_eval != NULL || s_emt != NULL ) {
13472             /* New style */
13473             char delim;
13474
13475             if( s_eval != NULL ) {
13476                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13477                     return text;
13478                 }
13479
13480                 if( delim != ']' ) {
13481                     return text;
13482                 }
13483             }
13484
13485             if( s_emt != NULL ) {
13486             }
13487                 return text;
13488         }
13489         else {
13490             /* We expect something like: [+|-]nnn.nn/dd */
13491             int score_lo = 0;
13492
13493             if(*text != '{') return text; // [HGM] braces: must be normal comment
13494
13495             sep = strchr( text, '/' );
13496             if( sep == NULL || sep < (text+4) ) {
13497                 return text;
13498             }
13499
13500             p = text;
13501             if(p[1] == '(') { // comment starts with PV
13502                p = strchr(p, ')'); // locate end of PV
13503                if(p == NULL || sep < p+5) return text;
13504                // at this point we have something like "{(.*) +0.23/6 ..."
13505                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13506                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13507                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13508             }
13509             time = -1; sec = -1; deci = -1;
13510             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13511                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13512                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13513                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13514                 return text;
13515             }
13516
13517             if( score_lo < 0 || score_lo >= 100 ) {
13518                 return text;
13519             }
13520
13521             if(sec >= 0) time = 600*time + 10*sec; else
13522             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13523
13524             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13525
13526             /* [HGM] PV time: now locate end of PV info */
13527             while( *++sep >= '0' && *sep <= '9'); // strip depth
13528             if(time >= 0)
13529             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13530             if(sec >= 0)
13531             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13532             if(deci >= 0)
13533             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13534             while(*sep == ' ') sep++;
13535         }
13536
13537         if( depth <= 0 ) {
13538             return text;
13539         }
13540
13541         if( time < 0 ) {
13542             time = -1;
13543         }
13544
13545         pvInfoList[index-1].depth = depth;
13546         pvInfoList[index-1].score = score;
13547         pvInfoList[index-1].time  = 10*time; // centi-sec
13548         if(*sep == '}') *sep = 0; else *--sep = '{';
13549         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13550     }
13551     return sep;
13552 }
13553
13554 void
13555 SendToProgram(message, cps)
13556      char *message;
13557      ChessProgramState *cps;
13558 {
13559     int count, outCount, error;
13560     char buf[MSG_SIZ];
13561
13562     if (cps->pr == NULL) return;
13563     Attention(cps);
13564
13565     if (appData.debugMode) {
13566         TimeMark now;
13567         GetTimeMark(&now);
13568         fprintf(debugFP, "%ld >%-6s: %s",
13569                 SubtractTimeMarks(&now, &programStartTime),
13570                 cps->which, message);
13571     }
13572
13573     count = strlen(message);
13574     outCount = OutputToProcess(cps->pr, message, count, &error);
13575     if (outCount < count && !exiting
13576                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13577       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13578         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13579             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13580                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13581                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13582             } else {
13583                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13584             }
13585             gameInfo.resultDetails = StrSave(buf);
13586         }
13587         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13588     }
13589 }
13590
13591 void
13592 ReceiveFromProgram(isr, closure, message, count, error)
13593      InputSourceRef isr;
13594      VOIDSTAR closure;
13595      char *message;
13596      int count;
13597      int error;
13598 {
13599     char *end_str;
13600     char buf[MSG_SIZ];
13601     ChessProgramState *cps = (ChessProgramState *)closure;
13602
13603     if (isr != cps->isr) return; /* Killed intentionally */
13604     if (count <= 0) {
13605         if (count == 0) {
13606             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13607                     cps->which, cps->program);
13608         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13609                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13610                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13611                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13612                 } else {
13613                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13614                 }
13615                 gameInfo.resultDetails = StrSave(buf);
13616             }
13617             RemoveInputSource(cps->isr);
13618             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13619         } else {
13620             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13621                     cps->which, cps->program);
13622             RemoveInputSource(cps->isr);
13623
13624             /* [AS] Program is misbehaving badly... kill it */
13625             if( count == -2 ) {
13626                 DestroyChildProcess( cps->pr, 9 );
13627                 cps->pr = NoProc;
13628             }
13629
13630             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13631         }
13632         return;
13633     }
13634
13635     if ((end_str = strchr(message, '\r')) != NULL)
13636       *end_str = NULLCHAR;
13637     if ((end_str = strchr(message, '\n')) != NULL)
13638       *end_str = NULLCHAR;
13639
13640     if (appData.debugMode) {
13641         TimeMark now; int print = 1;
13642         char *quote = ""; char c; int i;
13643
13644         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13645                 char start = message[0];
13646                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13647                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13648                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13649                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13650                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13651                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13652                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13653                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13654                    sscanf(message, "hint: %c", &c)!=1 && 
13655                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13656                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13657                     print = (appData.engineComments >= 2);
13658                 }
13659                 message[0] = start; // restore original message
13660         }
13661         if(print) {
13662                 GetTimeMark(&now);
13663                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13664                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13665                         quote,
13666                         message);
13667         }
13668     }
13669
13670     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13671     if (appData.icsEngineAnalyze) {
13672         if (strstr(message, "whisper") != NULL ||
13673              strstr(message, "kibitz") != NULL ||
13674             strstr(message, "tellics") != NULL) return;
13675     }
13676
13677     HandleMachineMove(message, cps);
13678 }
13679
13680
13681 void
13682 SendTimeControl(cps, mps, tc, inc, sd, st)
13683      ChessProgramState *cps;
13684      int mps, inc, sd, st;
13685      long tc;
13686 {
13687     char buf[MSG_SIZ];
13688     int seconds;
13689
13690     if( timeControl_2 > 0 ) {
13691         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13692             tc = timeControl_2;
13693         }
13694     }
13695     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13696     inc /= cps->timeOdds;
13697     st  /= cps->timeOdds;
13698
13699     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13700
13701     if (st > 0) {
13702       /* Set exact time per move, normally using st command */
13703       if (cps->stKludge) {
13704         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13705         seconds = st % 60;
13706         if (seconds == 0) {
13707           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13708         } else {
13709           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13710         }
13711       } else {
13712         snprintf(buf, MSG_SIZ, "st %d\n", st);
13713       }
13714     } else {
13715       /* Set conventional or incremental time control, using level command */
13716       if (seconds == 0) {
13717         /* Note old gnuchess bug -- minutes:seconds used to not work.
13718            Fixed in later versions, but still avoid :seconds
13719            when seconds is 0. */
13720         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13721       } else {
13722         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13723                  seconds, inc/1000.);
13724       }
13725     }
13726     SendToProgram(buf, cps);
13727
13728     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13729     /* Orthogonally, limit search to given depth */
13730     if (sd > 0) {
13731       if (cps->sdKludge) {
13732         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13733       } else {
13734         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13735       }
13736       SendToProgram(buf, cps);
13737     }
13738
13739     if(cps->nps > 0) { /* [HGM] nps */
13740         if(cps->supportsNPS == FALSE)
13741           cps->nps = -1; // don't use if engine explicitly says not supported!
13742         else {
13743           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13744           SendToProgram(buf, cps);
13745         }
13746     }
13747 }
13748
13749 ChessProgramState *WhitePlayer()
13750 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13751 {
13752     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13753        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13754         return &second;
13755     return &first;
13756 }
13757
13758 void
13759 SendTimeRemaining(cps, machineWhite)
13760      ChessProgramState *cps;
13761      int /*boolean*/ machineWhite;
13762 {
13763     char message[MSG_SIZ];
13764     long time, otime;
13765
13766     /* Note: this routine must be called when the clocks are stopped
13767        or when they have *just* been set or switched; otherwise
13768        it will be off by the time since the current tick started.
13769     */
13770     if (machineWhite) {
13771         time = whiteTimeRemaining / 10;
13772         otime = blackTimeRemaining / 10;
13773     } else {
13774         time = blackTimeRemaining / 10;
13775         otime = whiteTimeRemaining / 10;
13776     }
13777     /* [HGM] translate opponent's time by time-odds factor */
13778     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13779     if (appData.debugMode) {
13780         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13781     }
13782
13783     if (time <= 0) time = 1;
13784     if (otime <= 0) otime = 1;
13785
13786     snprintf(message, MSG_SIZ, "time %ld\n", time);
13787     SendToProgram(message, cps);
13788
13789     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13790     SendToProgram(message, cps);
13791 }
13792
13793 int
13794 BoolFeature(p, name, loc, cps)
13795      char **p;
13796      char *name;
13797      int *loc;
13798      ChessProgramState *cps;
13799 {
13800   char buf[MSG_SIZ];
13801   int len = strlen(name);
13802   int val;
13803
13804   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13805     (*p) += len + 1;
13806     sscanf(*p, "%d", &val);
13807     *loc = (val != 0);
13808     while (**p && **p != ' ')
13809       (*p)++;
13810     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13811     SendToProgram(buf, cps);
13812     return TRUE;
13813   }
13814   return FALSE;
13815 }
13816
13817 int
13818 IntFeature(p, name, loc, cps)
13819      char **p;
13820      char *name;
13821      int *loc;
13822      ChessProgramState *cps;
13823 {
13824   char buf[MSG_SIZ];
13825   int len = strlen(name);
13826   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13827     (*p) += len + 1;
13828     sscanf(*p, "%d", loc);
13829     while (**p && **p != ' ') (*p)++;
13830     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13831     SendToProgram(buf, cps);
13832     return TRUE;
13833   }
13834   return FALSE;
13835 }
13836
13837 int
13838 StringFeature(p, name, loc, cps)
13839      char **p;
13840      char *name;
13841      char loc[];
13842      ChessProgramState *cps;
13843 {
13844   char buf[MSG_SIZ];
13845   int len = strlen(name);
13846   if (strncmp((*p), name, len) == 0
13847       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13848     (*p) += len + 2;
13849     sscanf(*p, "%[^\"]", loc);
13850     while (**p && **p != '\"') (*p)++;
13851     if (**p == '\"') (*p)++;
13852     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13853     SendToProgram(buf, cps);
13854     return TRUE;
13855   }
13856   return FALSE;
13857 }
13858
13859 int
13860 ParseOption(Option *opt, ChessProgramState *cps)
13861 // [HGM] options: process the string that defines an engine option, and determine
13862 // name, type, default value, and allowed value range
13863 {
13864         char *p, *q, buf[MSG_SIZ];
13865         int n, min = (-1)<<31, max = 1<<31, def;
13866
13867         if(p = strstr(opt->name, " -spin ")) {
13868             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13869             if(max < min) max = min; // enforce consistency
13870             if(def < min) def = min;
13871             if(def > max) def = max;
13872             opt->value = def;
13873             opt->min = min;
13874             opt->max = max;
13875             opt->type = Spin;
13876         } else if((p = strstr(opt->name, " -slider "))) {
13877             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13878             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13879             if(max < min) max = min; // enforce consistency
13880             if(def < min) def = min;
13881             if(def > max) def = max;
13882             opt->value = def;
13883             opt->min = min;
13884             opt->max = max;
13885             opt->type = Spin; // Slider;
13886         } else if((p = strstr(opt->name, " -string "))) {
13887             opt->textValue = p+9;
13888             opt->type = TextBox;
13889         } else if((p = strstr(opt->name, " -file "))) {
13890             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13891             opt->textValue = p+7;
13892             opt->type = TextBox; // FileName;
13893         } else if((p = strstr(opt->name, " -path "))) {
13894             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13895             opt->textValue = p+7;
13896             opt->type = TextBox; // PathName;
13897         } else if(p = strstr(opt->name, " -check ")) {
13898             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13899             opt->value = (def != 0);
13900             opt->type = CheckBox;
13901         } else if(p = strstr(opt->name, " -combo ")) {
13902             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13903             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13904             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13905             opt->value = n = 0;
13906             while(q = StrStr(q, " /// ")) {
13907                 n++; *q = 0;    // count choices, and null-terminate each of them
13908                 q += 5;
13909                 if(*q == '*') { // remember default, which is marked with * prefix
13910                     q++;
13911                     opt->value = n;
13912                 }
13913                 cps->comboList[cps->comboCnt++] = q;
13914             }
13915             cps->comboList[cps->comboCnt++] = NULL;
13916             opt->max = n + 1;
13917             opt->type = ComboBox;
13918         } else if(p = strstr(opt->name, " -button")) {
13919             opt->type = Button;
13920         } else if(p = strstr(opt->name, " -save")) {
13921             opt->type = SaveButton;
13922         } else return FALSE;
13923         *p = 0; // terminate option name
13924         // now look if the command-line options define a setting for this engine option.
13925         if(cps->optionSettings && cps->optionSettings[0])
13926             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13927         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13928           snprintf(buf, MSG_SIZ, "option %s", p);
13929                 if(p = strstr(buf, ",")) *p = 0;
13930                 if(q = strchr(buf, '=')) switch(opt->type) {
13931                     case ComboBox:
13932                         for(n=0; n<opt->max; n++)
13933                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13934                         break;
13935                     case TextBox:
13936                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13937                         break;
13938                     case Spin:
13939                     case CheckBox:
13940                         opt->value = atoi(q+1);
13941                     default:
13942                         break;
13943                 }
13944                 strcat(buf, "\n");
13945                 SendToProgram(buf, cps);
13946         }
13947         return TRUE;
13948 }
13949
13950 void
13951 FeatureDone(cps, val)
13952      ChessProgramState* cps;
13953      int val;
13954 {
13955   DelayedEventCallback cb = GetDelayedEvent();
13956   if ((cb == InitBackEnd3 && cps == &first) ||
13957       (cb == SettingsMenuIfReady && cps == &second) ||
13958       (cb == TwoMachinesEventIfReady && cps == &second)) {
13959     CancelDelayedEvent();
13960     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13961   }
13962   cps->initDone = val;
13963 }
13964
13965 /* Parse feature command from engine */
13966 void
13967 ParseFeatures(args, cps)
13968      char* args;
13969      ChessProgramState *cps;
13970 {
13971   char *p = args;
13972   char *q;
13973   int val;
13974   char buf[MSG_SIZ];
13975
13976   for (;;) {
13977     while (*p == ' ') p++;
13978     if (*p == NULLCHAR) return;
13979
13980     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13981     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13982     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13983     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13984     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13985     if (BoolFeature(&p, "reuse", &val, cps)) {
13986       /* Engine can disable reuse, but can't enable it if user said no */
13987       if (!val) cps->reuse = FALSE;
13988       continue;
13989     }
13990     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13991     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13992       if (gameMode == TwoMachinesPlay) {
13993         DisplayTwoMachinesTitle();
13994       } else {
13995         DisplayTitle("");
13996       }
13997       continue;
13998     }
13999     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14000     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14001     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14002     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14003     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14004     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14005     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14006     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14007     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14008     if (IntFeature(&p, "done", &val, cps)) {
14009       FeatureDone(cps, val);
14010       continue;
14011     }
14012     /* Added by Tord: */
14013     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14014     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14015     /* End of additions by Tord */
14016
14017     /* [HGM] added features: */
14018     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14019     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14020     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14021     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14022     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14023     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14024     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14025         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14026           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14027             SendToProgram(buf, cps);
14028             continue;
14029         }
14030         if(cps->nrOptions >= MAX_OPTIONS) {
14031             cps->nrOptions--;
14032             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14033             DisplayError(buf, 0);
14034         }
14035         continue;
14036     }
14037     /* End of additions by HGM */
14038
14039     /* unknown feature: complain and skip */
14040     q = p;
14041     while (*q && *q != '=') q++;
14042     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14043     SendToProgram(buf, cps);
14044     p = q;
14045     if (*p == '=') {
14046       p++;
14047       if (*p == '\"') {
14048         p++;
14049         while (*p && *p != '\"') p++;
14050         if (*p == '\"') p++;
14051       } else {
14052         while (*p && *p != ' ') p++;
14053       }
14054     }
14055   }
14056
14057 }
14058
14059 void
14060 PeriodicUpdatesEvent(newState)
14061      int newState;
14062 {
14063     if (newState == appData.periodicUpdates)
14064       return;
14065
14066     appData.periodicUpdates=newState;
14067
14068     /* Display type changes, so update it now */
14069 //    DisplayAnalysis();
14070
14071     /* Get the ball rolling again... */
14072     if (newState) {
14073         AnalysisPeriodicEvent(1);
14074         StartAnalysisClock();
14075     }
14076 }
14077
14078 void
14079 PonderNextMoveEvent(newState)
14080      int newState;
14081 {
14082     if (newState == appData.ponderNextMove) return;
14083     if (gameMode == EditPosition) EditPositionDone(TRUE);
14084     if (newState) {
14085         SendToProgram("hard\n", &first);
14086         if (gameMode == TwoMachinesPlay) {
14087             SendToProgram("hard\n", &second);
14088         }
14089     } else {
14090         SendToProgram("easy\n", &first);
14091         thinkOutput[0] = NULLCHAR;
14092         if (gameMode == TwoMachinesPlay) {
14093             SendToProgram("easy\n", &second);
14094         }
14095     }
14096     appData.ponderNextMove = newState;
14097 }
14098
14099 void
14100 NewSettingEvent(option, feature, command, value)
14101      char *command;
14102      int option, value, *feature;
14103 {
14104     char buf[MSG_SIZ];
14105
14106     if (gameMode == EditPosition) EditPositionDone(TRUE);
14107     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14108     if(feature == NULL || *feature) SendToProgram(buf, &first);
14109     if (gameMode == TwoMachinesPlay) {
14110         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14111     }
14112 }
14113
14114 void
14115 ShowThinkingEvent()
14116 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14117 {
14118     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14119     int newState = appData.showThinking
14120         // [HGM] thinking: other features now need thinking output as well
14121         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14122
14123     if (oldState == newState) return;
14124     oldState = newState;
14125     if (gameMode == EditPosition) EditPositionDone(TRUE);
14126     if (oldState) {
14127         SendToProgram("post\n", &first);
14128         if (gameMode == TwoMachinesPlay) {
14129             SendToProgram("post\n", &second);
14130         }
14131     } else {
14132         SendToProgram("nopost\n", &first);
14133         thinkOutput[0] = NULLCHAR;
14134         if (gameMode == TwoMachinesPlay) {
14135             SendToProgram("nopost\n", &second);
14136         }
14137     }
14138 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14139 }
14140
14141 void
14142 AskQuestionEvent(title, question, replyPrefix, which)
14143      char *title; char *question; char *replyPrefix; char *which;
14144 {
14145   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14146   if (pr == NoProc) return;
14147   AskQuestion(title, question, replyPrefix, pr);
14148 }
14149
14150 void
14151 DisplayMove(moveNumber)
14152      int moveNumber;
14153 {
14154     char message[MSG_SIZ];
14155     char res[MSG_SIZ];
14156     char cpThinkOutput[MSG_SIZ];
14157
14158     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14159
14160     if (moveNumber == forwardMostMove - 1 ||
14161         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14162
14163         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14164
14165         if (strchr(cpThinkOutput, '\n')) {
14166             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14167         }
14168     } else {
14169         *cpThinkOutput = NULLCHAR;
14170     }
14171
14172     /* [AS] Hide thinking from human user */
14173     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14174         *cpThinkOutput = NULLCHAR;
14175         if( thinkOutput[0] != NULLCHAR ) {
14176             int i;
14177
14178             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14179                 cpThinkOutput[i] = '.';
14180             }
14181             cpThinkOutput[i] = NULLCHAR;
14182             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14183         }
14184     }
14185
14186     if (moveNumber == forwardMostMove - 1 &&
14187         gameInfo.resultDetails != NULL) {
14188         if (gameInfo.resultDetails[0] == NULLCHAR) {
14189           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14190         } else {
14191           snprintf(res, MSG_SIZ, " {%s} %s",
14192                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14193         }
14194     } else {
14195         res[0] = NULLCHAR;
14196     }
14197
14198     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14199         DisplayMessage(res, cpThinkOutput);
14200     } else {
14201       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14202                 WhiteOnMove(moveNumber) ? " " : ".. ",
14203                 parseList[moveNumber], res);
14204         DisplayMessage(message, cpThinkOutput);
14205     }
14206 }
14207
14208 void
14209 DisplayComment(moveNumber, text)
14210      int moveNumber;
14211      char *text;
14212 {
14213     char title[MSG_SIZ];
14214     char buf[8000]; // comment can be long!
14215     int score, depth;
14216
14217     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14218       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14219     } else {
14220       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14221               WhiteOnMove(moveNumber) ? " " : ".. ",
14222               parseList[moveNumber]);
14223     }
14224     // [HGM] PV info: display PV info together with (or as) comment
14225     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14226       if(text == NULL) text = "";
14227       score = pvInfoList[moveNumber].score;
14228       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14229               depth, (pvInfoList[moveNumber].time+50)/100, text);
14230       text = buf;
14231     }
14232     if (text != NULL && (appData.autoDisplayComment || commentUp))
14233         CommentPopUp(title, text);
14234 }
14235
14236 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14237  * might be busy thinking or pondering.  It can be omitted if your
14238  * gnuchess is configured to stop thinking immediately on any user
14239  * input.  However, that gnuchess feature depends on the FIONREAD
14240  * ioctl, which does not work properly on some flavors of Unix.
14241  */
14242 void
14243 Attention(cps)
14244      ChessProgramState *cps;
14245 {
14246 #if ATTENTION
14247     if (!cps->useSigint) return;
14248     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14249     switch (gameMode) {
14250       case MachinePlaysWhite:
14251       case MachinePlaysBlack:
14252       case TwoMachinesPlay:
14253       case IcsPlayingWhite:
14254       case IcsPlayingBlack:
14255       case AnalyzeMode:
14256       case AnalyzeFile:
14257         /* Skip if we know it isn't thinking */
14258         if (!cps->maybeThinking) return;
14259         if (appData.debugMode)
14260           fprintf(debugFP, "Interrupting %s\n", cps->which);
14261         InterruptChildProcess(cps->pr);
14262         cps->maybeThinking = FALSE;
14263         break;
14264       default:
14265         break;
14266     }
14267 #endif /*ATTENTION*/
14268 }
14269
14270 int
14271 CheckFlags()
14272 {
14273     if (whiteTimeRemaining <= 0) {
14274         if (!whiteFlag) {
14275             whiteFlag = TRUE;
14276             if (appData.icsActive) {
14277                 if (appData.autoCallFlag &&
14278                     gameMode == IcsPlayingBlack && !blackFlag) {
14279                   SendToICS(ics_prefix);
14280                   SendToICS("flag\n");
14281                 }
14282             } else {
14283                 if (blackFlag) {
14284                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14285                 } else {
14286                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14287                     if (appData.autoCallFlag) {
14288                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14289                         return TRUE;
14290                     }
14291                 }
14292             }
14293         }
14294     }
14295     if (blackTimeRemaining <= 0) {
14296         if (!blackFlag) {
14297             blackFlag = TRUE;
14298             if (appData.icsActive) {
14299                 if (appData.autoCallFlag &&
14300                     gameMode == IcsPlayingWhite && !whiteFlag) {
14301                   SendToICS(ics_prefix);
14302                   SendToICS("flag\n");
14303                 }
14304             } else {
14305                 if (whiteFlag) {
14306                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14307                 } else {
14308                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14309                     if (appData.autoCallFlag) {
14310                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14311                         return TRUE;
14312                     }
14313                 }
14314             }
14315         }
14316     }
14317     return FALSE;
14318 }
14319
14320 void
14321 CheckTimeControl()
14322 {
14323     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14324         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14325
14326     /*
14327      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14328      */
14329     if ( !WhiteOnMove(forwardMostMove) ) {
14330         /* White made time control */
14331         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14332         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14333         /* [HGM] time odds: correct new time quota for time odds! */
14334                                             / WhitePlayer()->timeOdds;
14335         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14336     } else {
14337         lastBlack -= blackTimeRemaining;
14338         /* Black made time control */
14339         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14340                                             / WhitePlayer()->other->timeOdds;
14341         lastWhite = whiteTimeRemaining;
14342     }
14343 }
14344
14345 void
14346 DisplayBothClocks()
14347 {
14348     int wom = gameMode == EditPosition ?
14349       !blackPlaysFirst : WhiteOnMove(currentMove);
14350     DisplayWhiteClock(whiteTimeRemaining, wom);
14351     DisplayBlackClock(blackTimeRemaining, !wom);
14352 }
14353
14354
14355 /* Timekeeping seems to be a portability nightmare.  I think everyone
14356    has ftime(), but I'm really not sure, so I'm including some ifdefs
14357    to use other calls if you don't.  Clocks will be less accurate if
14358    you have neither ftime nor gettimeofday.
14359 */
14360
14361 /* VS 2008 requires the #include outside of the function */
14362 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14363 #include <sys/timeb.h>
14364 #endif
14365
14366 /* Get the current time as a TimeMark */
14367 void
14368 GetTimeMark(tm)
14369      TimeMark *tm;
14370 {
14371 #if HAVE_GETTIMEOFDAY
14372
14373     struct timeval timeVal;
14374     struct timezone timeZone;
14375
14376     gettimeofday(&timeVal, &timeZone);
14377     tm->sec = (long) timeVal.tv_sec;
14378     tm->ms = (int) (timeVal.tv_usec / 1000L);
14379
14380 #else /*!HAVE_GETTIMEOFDAY*/
14381 #if HAVE_FTIME
14382
14383 // include <sys/timeb.h> / moved to just above start of function
14384     struct timeb timeB;
14385
14386     ftime(&timeB);
14387     tm->sec = (long) timeB.time;
14388     tm->ms = (int) timeB.millitm;
14389
14390 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14391     tm->sec = (long) time(NULL);
14392     tm->ms = 0;
14393 #endif
14394 #endif
14395 }
14396
14397 /* Return the difference in milliseconds between two
14398    time marks.  We assume the difference will fit in a long!
14399 */
14400 long
14401 SubtractTimeMarks(tm2, tm1)
14402      TimeMark *tm2, *tm1;
14403 {
14404     return 1000L*(tm2->sec - tm1->sec) +
14405            (long) (tm2->ms - tm1->ms);
14406 }
14407
14408
14409 /*
14410  * Code to manage the game clocks.
14411  *
14412  * In tournament play, black starts the clock and then white makes a move.
14413  * We give the human user a slight advantage if he is playing white---the
14414  * clocks don't run until he makes his first move, so it takes zero time.
14415  * Also, we don't account for network lag, so we could get out of sync
14416  * with GNU Chess's clock -- but then, referees are always right.
14417  */
14418
14419 static TimeMark tickStartTM;
14420 static long intendedTickLength;
14421
14422 long
14423 NextTickLength(timeRemaining)
14424      long timeRemaining;
14425 {
14426     long nominalTickLength, nextTickLength;
14427
14428     if (timeRemaining > 0L && timeRemaining <= 10000L)
14429       nominalTickLength = 100L;
14430     else
14431       nominalTickLength = 1000L;
14432     nextTickLength = timeRemaining % nominalTickLength;
14433     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14434
14435     return nextTickLength;
14436 }
14437
14438 /* Adjust clock one minute up or down */
14439 void
14440 AdjustClock(Boolean which, int dir)
14441 {
14442     if(which) blackTimeRemaining += 60000*dir;
14443     else      whiteTimeRemaining += 60000*dir;
14444     DisplayBothClocks();
14445 }
14446
14447 /* Stop clocks and reset to a fresh time control */
14448 void
14449 ResetClocks()
14450 {
14451     (void) StopClockTimer();
14452     if (appData.icsActive) {
14453         whiteTimeRemaining = blackTimeRemaining = 0;
14454     } else if (searchTime) {
14455         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14456         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14457     } else { /* [HGM] correct new time quote for time odds */
14458         whiteTC = blackTC = fullTimeControlString;
14459         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14460         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14461     }
14462     if (whiteFlag || blackFlag) {
14463         DisplayTitle("");
14464         whiteFlag = blackFlag = FALSE;
14465     }
14466     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14467     DisplayBothClocks();
14468 }
14469
14470 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14471
14472 /* Decrement running clock by amount of time that has passed */
14473 void
14474 DecrementClocks()
14475 {
14476     long timeRemaining;
14477     long lastTickLength, fudge;
14478     TimeMark now;
14479
14480     if (!appData.clockMode) return;
14481     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14482
14483     GetTimeMark(&now);
14484
14485     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14486
14487     /* Fudge if we woke up a little too soon */
14488     fudge = intendedTickLength - lastTickLength;
14489     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14490
14491     if (WhiteOnMove(forwardMostMove)) {
14492         if(whiteNPS >= 0) lastTickLength = 0;
14493         timeRemaining = whiteTimeRemaining -= lastTickLength;
14494         if(timeRemaining < 0 && !appData.icsActive) {
14495             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14496             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14497                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14498                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14499             }
14500         }
14501         DisplayWhiteClock(whiteTimeRemaining - fudge,
14502                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14503     } else {
14504         if(blackNPS >= 0) lastTickLength = 0;
14505         timeRemaining = blackTimeRemaining -= lastTickLength;
14506         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14507             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14508             if(suddenDeath) {
14509                 blackStartMove = forwardMostMove;
14510                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14511             }
14512         }
14513         DisplayBlackClock(blackTimeRemaining - fudge,
14514                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14515     }
14516     if (CheckFlags()) return;
14517
14518     tickStartTM = now;
14519     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14520     StartClockTimer(intendedTickLength);
14521
14522     /* if the time remaining has fallen below the alarm threshold, sound the
14523      * alarm. if the alarm has sounded and (due to a takeback or time control
14524      * with increment) the time remaining has increased to a level above the
14525      * threshold, reset the alarm so it can sound again.
14526      */
14527
14528     if (appData.icsActive && appData.icsAlarm) {
14529
14530         /* make sure we are dealing with the user's clock */
14531         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14532                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14533            )) return;
14534
14535         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14536             alarmSounded = FALSE;
14537         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14538             PlayAlarmSound();
14539             alarmSounded = TRUE;
14540         }
14541     }
14542 }
14543
14544
14545 /* A player has just moved, so stop the previously running
14546    clock and (if in clock mode) start the other one.
14547    We redisplay both clocks in case we're in ICS mode, because
14548    ICS gives us an update to both clocks after every move.
14549    Note that this routine is called *after* forwardMostMove
14550    is updated, so the last fractional tick must be subtracted
14551    from the color that is *not* on move now.
14552 */
14553 void
14554 SwitchClocks(int newMoveNr)
14555 {
14556     long lastTickLength;
14557     TimeMark now;
14558     int flagged = FALSE;
14559
14560     GetTimeMark(&now);
14561
14562     if (StopClockTimer() && appData.clockMode) {
14563         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14564         if (!WhiteOnMove(forwardMostMove)) {
14565             if(blackNPS >= 0) lastTickLength = 0;
14566             blackTimeRemaining -= lastTickLength;
14567            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14568 //         if(pvInfoList[forwardMostMove].time == -1)
14569                  pvInfoList[forwardMostMove].time =               // use GUI time
14570                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14571         } else {
14572            if(whiteNPS >= 0) lastTickLength = 0;
14573            whiteTimeRemaining -= lastTickLength;
14574            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14575 //         if(pvInfoList[forwardMostMove].time == -1)
14576                  pvInfoList[forwardMostMove].time =
14577                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14578         }
14579         flagged = CheckFlags();
14580     }
14581     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14582     CheckTimeControl();
14583
14584     if (flagged || !appData.clockMode) return;
14585
14586     switch (gameMode) {
14587       case MachinePlaysBlack:
14588       case MachinePlaysWhite:
14589       case BeginningOfGame:
14590         if (pausing) return;
14591         break;
14592
14593       case EditGame:
14594       case PlayFromGameFile:
14595       case IcsExamining:
14596         return;
14597
14598       default:
14599         break;
14600     }
14601
14602     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14603         if(WhiteOnMove(forwardMostMove))
14604              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14605         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14606     }
14607
14608     tickStartTM = now;
14609     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14610       whiteTimeRemaining : blackTimeRemaining);
14611     StartClockTimer(intendedTickLength);
14612 }
14613
14614
14615 /* Stop both clocks */
14616 void
14617 StopClocks()
14618 {
14619     long lastTickLength;
14620     TimeMark now;
14621
14622     if (!StopClockTimer()) return;
14623     if (!appData.clockMode) return;
14624
14625     GetTimeMark(&now);
14626
14627     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14628     if (WhiteOnMove(forwardMostMove)) {
14629         if(whiteNPS >= 0) lastTickLength = 0;
14630         whiteTimeRemaining -= lastTickLength;
14631         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14632     } else {
14633         if(blackNPS >= 0) lastTickLength = 0;
14634         blackTimeRemaining -= lastTickLength;
14635         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14636     }
14637     CheckFlags();
14638 }
14639
14640 /* Start clock of player on move.  Time may have been reset, so
14641    if clock is already running, stop and restart it. */
14642 void
14643 StartClocks()
14644 {
14645     (void) StopClockTimer(); /* in case it was running already */
14646     DisplayBothClocks();
14647     if (CheckFlags()) return;
14648
14649     if (!appData.clockMode) return;
14650     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14651
14652     GetTimeMark(&tickStartTM);
14653     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14654       whiteTimeRemaining : blackTimeRemaining);
14655
14656    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14657     whiteNPS = blackNPS = -1;
14658     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14659        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14660         whiteNPS = first.nps;
14661     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14662        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14663         blackNPS = first.nps;
14664     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14665         whiteNPS = second.nps;
14666     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14667         blackNPS = second.nps;
14668     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14669
14670     StartClockTimer(intendedTickLength);
14671 }
14672
14673 char *
14674 TimeString(ms)
14675      long ms;
14676 {
14677     long second, minute, hour, day;
14678     char *sign = "";
14679     static char buf[32];
14680
14681     if (ms > 0 && ms <= 9900) {
14682       /* convert milliseconds to tenths, rounding up */
14683       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14684
14685       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14686       return buf;
14687     }
14688
14689     /* convert milliseconds to seconds, rounding up */
14690     /* use floating point to avoid strangeness of integer division
14691        with negative dividends on many machines */
14692     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14693
14694     if (second < 0) {
14695         sign = "-";
14696         second = -second;
14697     }
14698
14699     day = second / (60 * 60 * 24);
14700     second = second % (60 * 60 * 24);
14701     hour = second / (60 * 60);
14702     second = second % (60 * 60);
14703     minute = second / 60;
14704     second = second % 60;
14705
14706     if (day > 0)
14707       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14708               sign, day, hour, minute, second);
14709     else if (hour > 0)
14710       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14711     else
14712       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14713
14714     return buf;
14715 }
14716
14717
14718 /*
14719  * This is necessary because some C libraries aren't ANSI C compliant yet.
14720  */
14721 char *
14722 StrStr(string, match)
14723      char *string, *match;
14724 {
14725     int i, length;
14726
14727     length = strlen(match);
14728
14729     for (i = strlen(string) - length; i >= 0; i--, string++)
14730       if (!strncmp(match, string, length))
14731         return string;
14732
14733     return NULL;
14734 }
14735
14736 char *
14737 StrCaseStr(string, match)
14738      char *string, *match;
14739 {
14740     int i, j, length;
14741
14742     length = strlen(match);
14743
14744     for (i = strlen(string) - length; i >= 0; i--, string++) {
14745         for (j = 0; j < length; j++) {
14746             if (ToLower(match[j]) != ToLower(string[j]))
14747               break;
14748         }
14749         if (j == length) return string;
14750     }
14751
14752     return NULL;
14753 }
14754
14755 #ifndef _amigados
14756 int
14757 StrCaseCmp(s1, s2)
14758      char *s1, *s2;
14759 {
14760     char c1, c2;
14761
14762     for (;;) {
14763         c1 = ToLower(*s1++);
14764         c2 = ToLower(*s2++);
14765         if (c1 > c2) return 1;
14766         if (c1 < c2) return -1;
14767         if (c1 == NULLCHAR) return 0;
14768     }
14769 }
14770
14771
14772 int
14773 ToLower(c)
14774      int c;
14775 {
14776     return isupper(c) ? tolower(c) : c;
14777 }
14778
14779
14780 int
14781 ToUpper(c)
14782      int c;
14783 {
14784     return islower(c) ? toupper(c) : c;
14785 }
14786 #endif /* !_amigados    */
14787
14788 char *
14789 StrSave(s)
14790      char *s;
14791 {
14792   char *ret;
14793
14794   if ((ret = (char *) malloc(strlen(s) + 1)))
14795     {
14796       safeStrCpy(ret, s, strlen(s)+1);
14797     }
14798   return ret;
14799 }
14800
14801 char *
14802 StrSavePtr(s, savePtr)
14803      char *s, **savePtr;
14804 {
14805     if (*savePtr) {
14806         free(*savePtr);
14807     }
14808     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14809       safeStrCpy(*savePtr, s, strlen(s)+1);
14810     }
14811     return(*savePtr);
14812 }
14813
14814 char *
14815 PGNDate()
14816 {
14817     time_t clock;
14818     struct tm *tm;
14819     char buf[MSG_SIZ];
14820
14821     clock = time((time_t *)NULL);
14822     tm = localtime(&clock);
14823     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14824             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14825     return StrSave(buf);
14826 }
14827
14828
14829 char *
14830 PositionToFEN(move, overrideCastling)
14831      int move;
14832      char *overrideCastling;
14833 {
14834     int i, j, fromX, fromY, toX, toY;
14835     int whiteToPlay;
14836     char buf[128];
14837     char *p, *q;
14838     int emptycount;
14839     ChessSquare piece;
14840
14841     whiteToPlay = (gameMode == EditPosition) ?
14842       !blackPlaysFirst : (move % 2 == 0);
14843     p = buf;
14844
14845     /* Piece placement data */
14846     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14847         emptycount = 0;
14848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14849             if (boards[move][i][j] == EmptySquare) {
14850                 emptycount++;
14851             } else { ChessSquare piece = boards[move][i][j];
14852                 if (emptycount > 0) {
14853                     if(emptycount<10) /* [HGM] can be >= 10 */
14854                         *p++ = '0' + emptycount;
14855                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14856                     emptycount = 0;
14857                 }
14858                 if(PieceToChar(piece) == '+') {
14859                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14860                     *p++ = '+';
14861                     piece = (ChessSquare)(DEMOTED piece);
14862                 }
14863                 *p++ = PieceToChar(piece);
14864                 if(p[-1] == '~') {
14865                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14866                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14867                     *p++ = '~';
14868                 }
14869             }
14870         }
14871         if (emptycount > 0) {
14872             if(emptycount<10) /* [HGM] can be >= 10 */
14873                 *p++ = '0' + emptycount;
14874             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14875             emptycount = 0;
14876         }
14877         *p++ = '/';
14878     }
14879     *(p - 1) = ' ';
14880
14881     /* [HGM] print Crazyhouse or Shogi holdings */
14882     if( gameInfo.holdingsWidth ) {
14883         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14884         q = p;
14885         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14886             piece = boards[move][i][BOARD_WIDTH-1];
14887             if( piece != EmptySquare )
14888               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14889                   *p++ = PieceToChar(piece);
14890         }
14891         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14892             piece = boards[move][BOARD_HEIGHT-i-1][0];
14893             if( piece != EmptySquare )
14894               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14895                   *p++ = PieceToChar(piece);
14896         }
14897
14898         if( q == p ) *p++ = '-';
14899         *p++ = ']';
14900         *p++ = ' ';
14901     }
14902
14903     /* Active color */
14904     *p++ = whiteToPlay ? 'w' : 'b';
14905     *p++ = ' ';
14906
14907   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14908     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14909   } else {
14910   if(nrCastlingRights) {
14911      q = p;
14912      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14913        /* [HGM] write directly from rights */
14914            if(boards[move][CASTLING][2] != NoRights &&
14915               boards[move][CASTLING][0] != NoRights   )
14916                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14917            if(boards[move][CASTLING][2] != NoRights &&
14918               boards[move][CASTLING][1] != NoRights   )
14919                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14920            if(boards[move][CASTLING][5] != NoRights &&
14921               boards[move][CASTLING][3] != NoRights   )
14922                 *p++ = boards[move][CASTLING][3] + AAA;
14923            if(boards[move][CASTLING][5] != NoRights &&
14924               boards[move][CASTLING][4] != NoRights   )
14925                 *p++ = boards[move][CASTLING][4] + AAA;
14926      } else {
14927
14928         /* [HGM] write true castling rights */
14929         if( nrCastlingRights == 6 ) {
14930             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14931                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14932             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14933                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14934             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14935                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14936             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14937                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14938         }
14939      }
14940      if (q == p) *p++ = '-'; /* No castling rights */
14941      *p++ = ' ';
14942   }
14943
14944   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14945      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14946     /* En passant target square */
14947     if (move > backwardMostMove) {
14948         fromX = moveList[move - 1][0] - AAA;
14949         fromY = moveList[move - 1][1] - ONE;
14950         toX = moveList[move - 1][2] - AAA;
14951         toY = moveList[move - 1][3] - ONE;
14952         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14953             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14954             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14955             fromX == toX) {
14956             /* 2-square pawn move just happened */
14957             *p++ = toX + AAA;
14958             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14959         } else {
14960             *p++ = '-';
14961         }
14962     } else if(move == backwardMostMove) {
14963         // [HGM] perhaps we should always do it like this, and forget the above?
14964         if((signed char)boards[move][EP_STATUS] >= 0) {
14965             *p++ = boards[move][EP_STATUS] + AAA;
14966             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14967         } else {
14968             *p++ = '-';
14969         }
14970     } else {
14971         *p++ = '-';
14972     }
14973     *p++ = ' ';
14974   }
14975   }
14976
14977     /* [HGM] find reversible plies */
14978     {   int i = 0, j=move;
14979
14980         if (appData.debugMode) { int k;
14981             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14982             for(k=backwardMostMove; k<=forwardMostMove; k++)
14983                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14984
14985         }
14986
14987         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14988         if( j == backwardMostMove ) i += initialRulePlies;
14989         sprintf(p, "%d ", i);
14990         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14991     }
14992     /* Fullmove number */
14993     sprintf(p, "%d", (move / 2) + 1);
14994
14995     return StrSave(buf);
14996 }
14997
14998 Boolean
14999 ParseFEN(board, blackPlaysFirst, fen)
15000     Board board;
15001      int *blackPlaysFirst;
15002      char *fen;
15003 {
15004     int i, j;
15005     char *p, c;
15006     int emptycount;
15007     ChessSquare piece;
15008
15009     p = fen;
15010
15011     /* [HGM] by default clear Crazyhouse holdings, if present */
15012     if(gameInfo.holdingsWidth) {
15013        for(i=0; i<BOARD_HEIGHT; i++) {
15014            board[i][0]             = EmptySquare; /* black holdings */
15015            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15016            board[i][1]             = (ChessSquare) 0; /* black counts */
15017            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15018        }
15019     }
15020
15021     /* Piece placement data */
15022     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15023         j = 0;
15024         for (;;) {
15025             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15026                 if (*p == '/') p++;
15027                 emptycount = gameInfo.boardWidth - j;
15028                 while (emptycount--)
15029                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15030                 break;
15031 #if(BOARD_FILES >= 10)
15032             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15033                 p++; emptycount=10;
15034                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15035                 while (emptycount--)
15036                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15037 #endif
15038             } else if (isdigit(*p)) {
15039                 emptycount = *p++ - '0';
15040                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15041                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15042                 while (emptycount--)
15043                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15044             } else if (*p == '+' || isalpha(*p)) {
15045                 if (j >= gameInfo.boardWidth) return FALSE;
15046                 if(*p=='+') {
15047                     piece = CharToPiece(*++p);
15048                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15049                     piece = (ChessSquare) (PROMOTED piece ); p++;
15050                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15051                 } else piece = CharToPiece(*p++);
15052
15053                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15054                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15055                     piece = (ChessSquare) (PROMOTED piece);
15056                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15057                     p++;
15058                 }
15059                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15060             } else {
15061                 return FALSE;
15062             }
15063         }
15064     }
15065     while (*p == '/' || *p == ' ') p++;
15066
15067     /* [HGM] look for Crazyhouse holdings here */
15068     while(*p==' ') p++;
15069     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15070         if(*p == '[') p++;
15071         if(*p == '-' ) p++; /* empty holdings */ else {
15072             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15073             /* if we would allow FEN reading to set board size, we would   */
15074             /* have to add holdings and shift the board read so far here   */
15075             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15076                 p++;
15077                 if((int) piece >= (int) BlackPawn ) {
15078                     i = (int)piece - (int)BlackPawn;
15079                     i = PieceToNumber((ChessSquare)i);
15080                     if( i >= gameInfo.holdingsSize ) return FALSE;
15081                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15082                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15083                 } else {
15084                     i = (int)piece - (int)WhitePawn;
15085                     i = PieceToNumber((ChessSquare)i);
15086                     if( i >= gameInfo.holdingsSize ) return FALSE;
15087                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15088                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15089                 }
15090             }
15091         }
15092         if(*p == ']') p++;
15093     }
15094
15095     while(*p == ' ') p++;
15096
15097     /* Active color */
15098     c = *p++;
15099     if(appData.colorNickNames) {
15100       if( c == appData.colorNickNames[0] ) c = 'w'; else
15101       if( c == appData.colorNickNames[1] ) c = 'b';
15102     }
15103     switch (c) {
15104       case 'w':
15105         *blackPlaysFirst = FALSE;
15106         break;
15107       case 'b':
15108         *blackPlaysFirst = TRUE;
15109         break;
15110       default:
15111         return FALSE;
15112     }
15113
15114     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15115     /* return the extra info in global variiables             */
15116
15117     /* set defaults in case FEN is incomplete */
15118     board[EP_STATUS] = EP_UNKNOWN;
15119     for(i=0; i<nrCastlingRights; i++ ) {
15120         board[CASTLING][i] =
15121             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15122     }   /* assume possible unless obviously impossible */
15123     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15124     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15125     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15126                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15127     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15128     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15129     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15130                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15131     FENrulePlies = 0;
15132
15133     while(*p==' ') p++;
15134     if(nrCastlingRights) {
15135       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15136           /* castling indicator present, so default becomes no castlings */
15137           for(i=0; i<nrCastlingRights; i++ ) {
15138                  board[CASTLING][i] = NoRights;
15139           }
15140       }
15141       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15142              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15143              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15144              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15145         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15146
15147         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15148             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15149             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15150         }
15151         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15152             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15153         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15154                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15155         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15156                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15157         switch(c) {
15158           case'K':
15159               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15160               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15161               board[CASTLING][2] = whiteKingFile;
15162               break;
15163           case'Q':
15164               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15165               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15166               board[CASTLING][2] = whiteKingFile;
15167               break;
15168           case'k':
15169               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15170               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15171               board[CASTLING][5] = blackKingFile;
15172               break;
15173           case'q':
15174               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15175               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15176               board[CASTLING][5] = blackKingFile;
15177           case '-':
15178               break;
15179           default: /* FRC castlings */
15180               if(c >= 'a') { /* black rights */
15181                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15182                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15183                   if(i == BOARD_RGHT) break;
15184                   board[CASTLING][5] = i;
15185                   c -= AAA;
15186                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15187                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15188                   if(c > i)
15189                       board[CASTLING][3] = c;
15190                   else
15191                       board[CASTLING][4] = c;
15192               } else { /* white rights */
15193                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15194                     if(board[0][i] == WhiteKing) break;
15195                   if(i == BOARD_RGHT) break;
15196                   board[CASTLING][2] = i;
15197                   c -= AAA - 'a' + 'A';
15198                   if(board[0][c] >= WhiteKing) break;
15199                   if(c > i)
15200                       board[CASTLING][0] = c;
15201                   else
15202                       board[CASTLING][1] = c;
15203               }
15204         }
15205       }
15206       for(i=0; i<nrCastlingRights; i++)
15207         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15208     if (appData.debugMode) {
15209         fprintf(debugFP, "FEN castling rights:");
15210         for(i=0; i<nrCastlingRights; i++)
15211         fprintf(debugFP, " %d", board[CASTLING][i]);
15212         fprintf(debugFP, "\n");
15213     }
15214
15215       while(*p==' ') p++;
15216     }
15217
15218     /* read e.p. field in games that know e.p. capture */
15219     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15220        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15221       if(*p=='-') {
15222         p++; board[EP_STATUS] = EP_NONE;
15223       } else {
15224          char c = *p++ - AAA;
15225
15226          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15227          if(*p >= '0' && *p <='9') p++;
15228          board[EP_STATUS] = c;
15229       }
15230     }
15231
15232
15233     if(sscanf(p, "%d", &i) == 1) {
15234         FENrulePlies = i; /* 50-move ply counter */
15235         /* (The move number is still ignored)    */
15236     }
15237
15238     return TRUE;
15239 }
15240
15241 void
15242 EditPositionPasteFEN(char *fen)
15243 {
15244   if (fen != NULL) {
15245     Board initial_position;
15246
15247     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15248       DisplayError(_("Bad FEN position in clipboard"), 0);
15249       return ;
15250     } else {
15251       int savedBlackPlaysFirst = blackPlaysFirst;
15252       EditPositionEvent();
15253       blackPlaysFirst = savedBlackPlaysFirst;
15254       CopyBoard(boards[0], initial_position);
15255       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15256       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15257       DisplayBothClocks();
15258       DrawPosition(FALSE, boards[currentMove]);
15259     }
15260   }
15261 }
15262
15263 static char cseq[12] = "\\   ";
15264
15265 Boolean set_cont_sequence(char *new_seq)
15266 {
15267     int len;
15268     Boolean ret;
15269
15270     // handle bad attempts to set the sequence
15271         if (!new_seq)
15272                 return 0; // acceptable error - no debug
15273
15274     len = strlen(new_seq);
15275     ret = (len > 0) && (len < sizeof(cseq));
15276     if (ret)
15277       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15278     else if (appData.debugMode)
15279       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15280     return ret;
15281 }
15282
15283 /*
15284     reformat a source message so words don't cross the width boundary.  internal
15285     newlines are not removed.  returns the wrapped size (no null character unless
15286     included in source message).  If dest is NULL, only calculate the size required
15287     for the dest buffer.  lp argument indicats line position upon entry, and it's
15288     passed back upon exit.
15289 */
15290 int wrap(char *dest, char *src, int count, int width, int *lp)
15291 {
15292     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15293
15294     cseq_len = strlen(cseq);
15295     old_line = line = *lp;
15296     ansi = len = clen = 0;
15297
15298     for (i=0; i < count; i++)
15299     {
15300         if (src[i] == '\033')
15301             ansi = 1;
15302
15303         // if we hit the width, back up
15304         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15305         {
15306             // store i & len in case the word is too long
15307             old_i = i, old_len = len;
15308
15309             // find the end of the last word
15310             while (i && src[i] != ' ' && src[i] != '\n')
15311             {
15312                 i--;
15313                 len--;
15314             }
15315
15316             // word too long?  restore i & len before splitting it
15317             if ((old_i-i+clen) >= width)
15318             {
15319                 i = old_i;
15320                 len = old_len;
15321             }
15322
15323             // extra space?
15324             if (i && src[i-1] == ' ')
15325                 len--;
15326
15327             if (src[i] != ' ' && src[i] != '\n')
15328             {
15329                 i--;
15330                 if (len)
15331                     len--;
15332             }
15333
15334             // now append the newline and continuation sequence
15335             if (dest)
15336                 dest[len] = '\n';
15337             len++;
15338             if (dest)
15339                 strncpy(dest+len, cseq, cseq_len);
15340             len += cseq_len;
15341             line = cseq_len;
15342             clen = cseq_len;
15343             continue;
15344         }
15345
15346         if (dest)
15347             dest[len] = src[i];
15348         len++;
15349         if (!ansi)
15350             line++;
15351         if (src[i] == '\n')
15352             line = 0;
15353         if (src[i] == 'm')
15354             ansi = 0;
15355     }
15356     if (dest && appData.debugMode)
15357     {
15358         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15359             count, width, line, len, *lp);
15360         show_bytes(debugFP, src, count);
15361         fprintf(debugFP, "\ndest: ");
15362         show_bytes(debugFP, dest, len);
15363         fprintf(debugFP, "\n");
15364     }
15365     *lp = dest ? line : old_line;
15366
15367     return len;
15368 }
15369
15370 // [HGM] vari: routines for shelving variations
15371
15372 void
15373 PushTail(int firstMove, int lastMove)
15374 {
15375         int i, j, nrMoves = lastMove - firstMove;
15376
15377         if(appData.icsActive) { // only in local mode
15378                 forwardMostMove = currentMove; // mimic old ICS behavior
15379                 return;
15380         }
15381         if(storedGames >= MAX_VARIATIONS-1) return;
15382
15383         // push current tail of game on stack
15384         savedResult[storedGames] = gameInfo.result;
15385         savedDetails[storedGames] = gameInfo.resultDetails;
15386         gameInfo.resultDetails = NULL;
15387         savedFirst[storedGames] = firstMove;
15388         savedLast [storedGames] = lastMove;
15389         savedFramePtr[storedGames] = framePtr;
15390         framePtr -= nrMoves; // reserve space for the boards
15391         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15392             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15393             for(j=0; j<MOVE_LEN; j++)
15394                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15395             for(j=0; j<2*MOVE_LEN; j++)
15396                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15397             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15398             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15399             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15400             pvInfoList[firstMove+i-1].depth = 0;
15401             commentList[framePtr+i] = commentList[firstMove+i];
15402             commentList[firstMove+i] = NULL;
15403         }
15404
15405         storedGames++;
15406         forwardMostMove = firstMove; // truncate game so we can start variation
15407         if(storedGames == 1) GreyRevert(FALSE);
15408 }
15409
15410 Boolean
15411 PopTail(Boolean annotate)
15412 {
15413         int i, j, nrMoves;
15414         char buf[8000], moveBuf[20];
15415
15416         if(appData.icsActive) return FALSE; // only in local mode
15417         if(!storedGames) return FALSE; // sanity
15418         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15419
15420         storedGames--;
15421         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15422         nrMoves = savedLast[storedGames] - currentMove;
15423         if(annotate) {
15424                 int cnt = 10;
15425                 if(!WhiteOnMove(currentMove))
15426                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15427                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15428                 for(i=currentMove; i<forwardMostMove; i++) {
15429                         if(WhiteOnMove(i))
15430                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15431                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15432                         strcat(buf, moveBuf);
15433                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15434                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15435                 }
15436                 strcat(buf, ")");
15437         }
15438         for(i=1; i<=nrMoves; i++) { // copy last variation back
15439             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15440             for(j=0; j<MOVE_LEN; j++)
15441                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15442             for(j=0; j<2*MOVE_LEN; j++)
15443                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15444             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15445             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15446             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15447             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15448             commentList[currentMove+i] = commentList[framePtr+i];
15449             commentList[framePtr+i] = NULL;
15450         }
15451         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15452         framePtr = savedFramePtr[storedGames];
15453         gameInfo.result = savedResult[storedGames];
15454         if(gameInfo.resultDetails != NULL) {
15455             free(gameInfo.resultDetails);
15456       }
15457         gameInfo.resultDetails = savedDetails[storedGames];
15458         forwardMostMove = currentMove + nrMoves;
15459         if(storedGames == 0) GreyRevert(TRUE);
15460         return TRUE;
15461 }
15462
15463 void
15464 CleanupTail()
15465 {       // remove all shelved variations
15466         int i;
15467         for(i=0; i<storedGames; i++) {
15468             if(savedDetails[i])
15469                 free(savedDetails[i]);
15470             savedDetails[i] = NULL;
15471         }
15472         for(i=framePtr; i<MAX_MOVES; i++) {
15473                 if(commentList[i]) free(commentList[i]);
15474                 commentList[i] = NULL;
15475         }
15476         framePtr = MAX_MOVES-1;
15477         storedGames = 0;
15478 }
15479
15480 void
15481 LoadVariation(int index, char *text)
15482 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15483         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15484         int level = 0, move;
15485
15486         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15487         // first find outermost bracketing variation
15488         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15489             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15490                 if(*p == '{') wait = '}'; else
15491                 if(*p == '[') wait = ']'; else
15492                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15493                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15494             }
15495             if(*p == wait) wait = NULLCHAR; // closing ]} found
15496             p++;
15497         }
15498         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15499         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15500         end[1] = NULLCHAR; // clip off comment beyond variation
15501         ToNrEvent(currentMove-1);
15502         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15503         // kludge: use ParsePV() to append variation to game
15504         move = currentMove;
15505         ParsePV(start, TRUE);
15506         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15507         ClearPremoveHighlights();
15508         CommentPopDown();
15509         ToNrEvent(currentMove+1);
15510 }
15511