cfc9b6f8e0da42822168717b111abd515b412284
[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 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 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 int rightsBoard[BOARD_RANKS][BOARD_FILES];                  /* [HGM] editrights */
246 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
247 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
248 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
249 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
250 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
251 int opponentKibitzes;
252 int lastSavedGame; /* [HGM] save: ID of game */
253 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
254 extern int chatCount;
255 int chattingPartner;
256 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257
258 /* States for ics_getting_history */
259 #define H_FALSE 0
260 #define H_REQUESTED 1
261 #define H_GOT_REQ_HEADER 2
262 #define H_GOT_UNREQ_HEADER 3
263 #define H_GETTING_MOVES 4
264 #define H_GOT_UNWANTED_HEADER 5
265
266 /* whosays values for GameEnds */
267 #define GE_ICS 0
268 #define GE_ENGINE 1
269 #define GE_PLAYER 2
270 #define GE_FILE 3
271 #define GE_XBOARD 4
272 #define GE_ENGINE1 5
273 #define GE_ENGINE2 6
274
275 /* Maximum number of games in a cmail message */
276 #define CMAIL_MAX_GAMES 20
277
278 /* Different types of move when calling RegisterMove */
279 #define CMAIL_MOVE   0
280 #define CMAIL_RESIGN 1
281 #define CMAIL_DRAW   2
282 #define CMAIL_ACCEPT 3
283
284 /* Different types of result to remember for each game */
285 #define CMAIL_NOT_RESULT 0
286 #define CMAIL_OLD_RESULT 1
287 #define CMAIL_NEW_RESULT 2
288
289 /* Telnet protocol constants */
290 #define TN_WILL 0373
291 #define TN_WONT 0374
292 #define TN_DO   0375
293 #define TN_DONT 0376
294 #define TN_IAC  0377
295 #define TN_ECHO 0001
296 #define TN_SGA  0003
297 #define TN_PORT 23
298
299 /* [AS] */
300 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 {
302     assert( dst != NULL );
303     assert( src != NULL );
304     assert( count > 0 );
305
306     strncpy( dst, src, count );
307     dst[ count-1 ] = '\0';
308     return dst;
309 }
310
311 /* Some compiler can't cast u64 to double
312  * This function do the job for us:
313
314  * We use the highest bit for cast, this only
315  * works if the highest bit is not
316  * in use (This should not happen)
317  *
318  * We used this for all compiler
319  */
320 double
321 u64ToDouble(u64 value)
322 {
323   double r;
324   u64 tmp = value & u64Const(0x7fffffffffffffff);
325   r = (double)(s64)tmp;
326   if (value & u64Const(0x8000000000000000))
327        r +=  9.2233720368547758080e18; /* 2^63 */
328  return r;
329 }
330
331 /* Fake up flags for now, as we aren't keeping track of castling
332    availability yet. [HGM] Change of logic: the flag now only
333    indicates the type of castlings allowed by the rule of the game.
334    The actual rights themselves are maintained in the array
335    castlingRights, as part of the game history, and are not probed
336    by this function.
337  */
338 int
339 PosFlags(index)
340 {
341   int flags = F_ALL_CASTLE_OK;
342   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
343   switch (gameInfo.variant) {
344   case VariantSuicide:
345     flags &= ~F_ALL_CASTLE_OK;
346   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
347     flags |= F_IGNORE_CHECK;
348   case VariantLosers:
349     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
350     break;
351   case VariantAtomic:
352     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353     break;
354   case VariantKriegspiel:
355     flags |= F_KRIEGSPIEL_CAPTURE;
356     break;
357   case VariantCapaRandom:
358   case VariantFischeRandom:
359     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
360   case VariantNoCastle:
361   case VariantShatranj:
362   case VariantCourier:
363   case VariantMakruk:
364     flags &= ~F_ALL_CASTLE_OK;
365     break;
366   default:
367     break;
368   }
369   return flags;
370 }
371
372 FILE *gameFileFP, *debugFP;
373
374 /*
375     [AS] Note: sometimes, the sscanf() function is used to parse the input
376     into a fixed-size buffer. Because of this, we must be prepared to
377     receive strings as long as the size of the input buffer, which is currently
378     set to 4K for Windows and 8K for the rest.
379     So, we must either allocate sufficiently large buffers here, or
380     reduce the size of the input buffer in the input reading part.
381 */
382
383 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
384 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
385 char thinkOutput1[MSG_SIZ*10];
386
387 ChessProgramState first, second;
388
389 /* premove variables */
390 int premoveToX = 0;
391 int premoveToY = 0;
392 int premoveFromX = 0;
393 int premoveFromY = 0;
394 int premovePromoChar = 0;
395 int gotPremove = 0;
396 Boolean alarmSounded;
397 /* end premove variables */
398
399 char *ics_prefix = "$";
400 int ics_type = ICS_GENERIC;
401
402 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
403 int pauseExamForwardMostMove = 0;
404 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
405 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
406 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
407 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
408 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
409 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
410 int whiteFlag = FALSE, blackFlag = FALSE;
411 int userOfferedDraw = FALSE;
412 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
413 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
414 int cmailMoveType[CMAIL_MAX_GAMES];
415 long ics_clock_paused = 0;
416 ProcRef icsPR = NoProc, cmailPR = NoProc;
417 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
418 GameMode gameMode = BeginningOfGame;
419 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
420 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
421 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
422 int hiddenThinkOutputState = 0; /* [AS] */
423 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
424 int adjudicateLossPlies = 6;
425 char white_holding[64], black_holding[64];
426 TimeMark lastNodeCountTime;
427 long lastNodeCount=0;
428 int have_sent_ICS_logon = 0;
429 int movesPerSession;
430 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
431 long timeControl_2; /* [AS] Allow separate time controls */
432 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
433 long timeRemaining[2][MAX_MOVES];
434 int matchGame = 0;
435 TimeMark programStartTime;
436 char ics_handle[MSG_SIZ];
437 int have_set_title = 0;
438
439 /* animateTraining preserves the state of appData.animate
440  * when Training mode is activated. This allows the
441  * response to be animated when appData.animate == TRUE and
442  * appData.animateDragging == TRUE.
443  */
444 Boolean animateTraining;
445
446 GameInfo gameInfo;
447
448 AppData appData;
449
450 Board boards[MAX_MOVES];
451 /* [HGM] Following 7 needed for accurate legality tests: */
452 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
453 signed char  initialRights[BOARD_FILES];
454 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
455 int   initialRulePlies, FENrulePlies;
456 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
457 int loadFlag = 0;
458 int shuffleOpenings;
459 int mute; // mute all sounds
460
461 // [HGM] vari: next 12 to save and restore variations
462 #define MAX_VARIATIONS 10
463 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int storedGames = 0;
465 int savedFirst[MAX_VARIATIONS];
466 int savedLast[MAX_VARIATIONS];
467 int savedFramePtr[MAX_VARIATIONS];
468 char *savedDetails[MAX_VARIATIONS];
469 ChessMove savedResult[MAX_VARIATIONS];
470
471 void PushTail P((int firstMove, int lastMove));
472 Boolean PopTail P((Boolean annotate));
473 void CleanupTail P((void));
474
475 ChessSquare  FIDEArray[2][BOARD_FILES] = {
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
477         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
479         BlackKing, BlackBishop, BlackKnight, BlackRook }
480 };
481
482 ChessSquare twoKingsArray[2][BOARD_FILES] = {
483     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
484         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
486         BlackKing, BlackKing, BlackKnight, BlackRook }
487 };
488
489 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
491         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
492     { BlackRook, BlackMan, BlackBishop, BlackQueen,
493         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 };
495
496 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
497     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
498         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
499     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
500         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
501 };
502
503 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
504     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
505         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
506     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
507         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 };
509
510 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
511     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
512         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackMan, BlackFerz,
514         BlackKing, BlackMan, BlackKnight, BlackRook }
515 };
516
517
518 #if (BOARD_FILES>=10)
519 ChessSquare ShogiArray[2][BOARD_FILES] = {
520     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
521         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
522     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
523         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
524 };
525
526 ChessSquare XiangqiArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
528         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
530         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
531 };
532
533 ChessSquare CapablancaArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
537         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
538 };
539
540 ChessSquare GreatArray[2][BOARD_FILES] = {
541     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
542         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
543     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
544         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
545 };
546
547 ChessSquare JanusArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
549         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
550     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
551         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
552 };
553
554 #ifdef GOTHIC
555 ChessSquare GothicArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
557         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
559         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
560 };
561 #else // !GOTHIC
562 #define GothicArray CapablancaArray
563 #endif // !GOTHIC
564
565 #ifdef FALCON
566 ChessSquare FalconArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
568         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
570         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
571 };
572 #else // !FALCON
573 #define FalconArray CapablancaArray
574 #endif // !FALCON
575
576 #else // !(BOARD_FILES>=10)
577 #define XiangqiPosition FIDEArray
578 #define CapablancaArray FIDEArray
579 #define GothicArray FIDEArray
580 #define GreatArray FIDEArray
581 #endif // !(BOARD_FILES>=10)
582
583 #if (BOARD_FILES>=12)
584 ChessSquare CourierArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
588         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 };
590 #else // !(BOARD_FILES>=12)
591 #define CourierArray CapablancaArray
592 #endif // !(BOARD_FILES>=12)
593
594
595 Board initialPosition;
596
597
598 /* Convert str to a rating. Checks for special cases of "----",
599
600    "++++", etc. Also strips ()'s */
601 int
602 string_to_rating(str)
603   char *str;
604 {
605   while(*str && !isdigit(*str)) ++str;
606   if (!*str)
607     return 0;   /* One of the special "no rating" cases */
608   else
609     return atoi(str);
610 }
611
612 void
613 ClearProgramStats()
614 {
615     /* Init programStats */
616     programStats.movelist[0] = 0;
617     programStats.depth = 0;
618     programStats.nr_moves = 0;
619     programStats.moves_left = 0;
620     programStats.nodes = 0;
621     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
622     programStats.score = 0;
623     programStats.got_only_move = 0;
624     programStats.got_fail = 0;
625     programStats.line_is_book = 0;
626 }
627
628 void
629 InitBackEnd1()
630 {
631     int matched, min, sec;
632
633     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634
635     GetTimeMark(&programStartTime);
636     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
637
638     ClearProgramStats();
639     programStats.ok_to_send = 1;
640     programStats.seen_stat = 0;
641
642     /*
643      * Initialize game list
644      */
645     ListNew(&gameList);
646
647
648     /*
649      * Internet chess server status
650      */
651     if (appData.icsActive) {
652         appData.matchMode = FALSE;
653         appData.matchGames = 0;
654 #if ZIPPY
655         appData.noChessProgram = !appData.zippyPlay;
656 #else
657         appData.zippyPlay = FALSE;
658         appData.zippyTalk = FALSE;
659         appData.noChessProgram = TRUE;
660 #endif
661         if (*appData.icsHelper != NULLCHAR) {
662             appData.useTelnet = TRUE;
663             appData.telnetProgram = appData.icsHelper;
664         }
665     } else {
666         appData.zippyTalk = appData.zippyPlay = FALSE;
667     }
668
669     /* [AS] Initialize pv info list [HGM] and game state */
670     {
671         int i, j;
672
673         for( i=0; i<=framePtr; i++ ) {
674             pvInfoList[i].depth = -1;
675             boards[i][EP_STATUS] = EP_NONE;
676             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
677         }
678     }
679
680     /*
681      * Parse timeControl resource
682      */
683     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
684                           appData.movesPerSession)) {
685         char buf[MSG_SIZ];
686         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
687         DisplayFatalError(buf, 0, 2);
688     }
689
690     /*
691      * Parse searchTime resource
692      */
693     if (*appData.searchTime != NULLCHAR) {
694         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695         if (matched == 1) {
696             searchTime = min * 60;
697         } else if (matched == 2) {
698             searchTime = min * 60 + sec;
699         } else {
700             char buf[MSG_SIZ];
701             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
702             DisplayFatalError(buf, 0, 2);
703         }
704     }
705
706     /* [AS] Adjudication threshold */
707     adjudicateLossThreshold = appData.adjudicateLossThreshold;
708
709     first.which = "first";
710     second.which = "second";
711     first.maybeThinking = second.maybeThinking = FALSE;
712     first.pr = second.pr = NoProc;
713     first.isr = second.isr = NULL;
714     first.sendTime = second.sendTime = 2;
715     first.sendDrawOffers = 1;
716     if (appData.firstPlaysBlack) {
717         first.twoMachinesColor = "black\n";
718         second.twoMachinesColor = "white\n";
719     } else {
720         first.twoMachinesColor = "white\n";
721         second.twoMachinesColor = "black\n";
722     }
723     first.program = appData.firstChessProgram;
724     second.program = appData.secondChessProgram;
725     first.host = appData.firstHost;
726     second.host = appData.secondHost;
727     first.dir = appData.firstDirectory;
728     second.dir = appData.secondDirectory;
729     first.other = &second;
730     second.other = &first;
731     first.initString = appData.initString;
732     second.initString = appData.secondInitString;
733     first.computerString = appData.firstComputerString;
734     second.computerString = appData.secondComputerString;
735     first.useSigint = second.useSigint = TRUE;
736     first.useSigterm = second.useSigterm = TRUE;
737     first.reuse = appData.reuseFirst;
738     second.reuse = appData.reuseSecond;
739     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
740     second.nps = appData.secondNPS;
741     first.useSetboard = second.useSetboard = FALSE;
742     first.useSAN = second.useSAN = FALSE;
743     first.usePing = second.usePing = FALSE;
744     first.lastPing = second.lastPing = 0;
745     first.lastPong = second.lastPong = 0;
746     first.usePlayother = second.usePlayother = FALSE;
747     first.useColors = second.useColors = TRUE;
748     first.useUsermove = second.useUsermove = FALSE;
749     first.sendICS = second.sendICS = FALSE;
750     first.sendName = second.sendName = appData.icsActive;
751     first.sdKludge = second.sdKludge = FALSE;
752     first.stKludge = second.stKludge = FALSE;
753     TidyProgramName(first.program, first.host, first.tidy);
754     TidyProgramName(second.program, second.host, second.tidy);
755     first.matchWins = second.matchWins = 0;
756     strcpy(first.variants, appData.variant);
757     strcpy(second.variants, appData.variant);
758     first.analysisSupport = second.analysisSupport = 2; /* detect */
759     first.analyzing = second.analyzing = FALSE;
760     first.initDone = second.initDone = FALSE;
761
762     /* New features added by Tord: */
763     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
764     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
765     /* End of new features added by Tord. */
766     first.fenOverride  = appData.fenOverride1;
767     second.fenOverride = appData.fenOverride2;
768
769     /* [HGM] time odds: set factor for each machine */
770     first.timeOdds  = appData.firstTimeOdds;
771     second.timeOdds = appData.secondTimeOdds;
772     { float norm = 1;
773         if(appData.timeOddsMode) {
774             norm = first.timeOdds;
775             if(norm > second.timeOdds) norm = second.timeOdds;
776         }
777         first.timeOdds /= norm;
778         second.timeOdds /= norm;
779     }
780
781     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
782     first.accumulateTC = appData.firstAccumulateTC;
783     second.accumulateTC = appData.secondAccumulateTC;
784     first.maxNrOfSessions = second.maxNrOfSessions = 1;
785
786     /* [HGM] debug */
787     first.debug = second.debug = FALSE;
788     first.supportsNPS = second.supportsNPS = UNKNOWN;
789
790     /* [HGM] options */
791     first.optionSettings  = appData.firstOptions;
792     second.optionSettings = appData.secondOptions;
793
794     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
795     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
796     first.isUCI = appData.firstIsUCI; /* [AS] */
797     second.isUCI = appData.secondIsUCI; /* [AS] */
798     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
799     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800
801     if (appData.firstProtocolVersion > PROTOVER ||
802         appData.firstProtocolVersion < 1) {
803       char buf[MSG_SIZ];
804       sprintf(buf, _("protocol version %d not supported"),
805               appData.firstProtocolVersion);
806       DisplayFatalError(buf, 0, 2);
807     } else {
808       first.protocolVersion = appData.firstProtocolVersion;
809     }
810
811     if (appData.secondProtocolVersion > PROTOVER ||
812         appData.secondProtocolVersion < 1) {
813       char buf[MSG_SIZ];
814       sprintf(buf, _("protocol version %d not supported"),
815               appData.secondProtocolVersion);
816       DisplayFatalError(buf, 0, 2);
817     } else {
818       second.protocolVersion = appData.secondProtocolVersion;
819     }
820
821     if (appData.icsActive) {
822         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
823 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
824     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
825         appData.clockMode = FALSE;
826         first.sendTime = second.sendTime = 0;
827     }
828
829 #if ZIPPY
830     /* Override some settings from environment variables, for backward
831        compatibility.  Unfortunately it's not feasible to have the env
832        vars just set defaults, at least in xboard.  Ugh.
833     */
834     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
835       ZippyInit();
836     }
837 #endif
838
839     if (appData.noChessProgram) {
840         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
841         sprintf(programVersion, "%s", PACKAGE_STRING);
842     } else {
843       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846     }
847
848     if (!appData.icsActive) {
849       char buf[MSG_SIZ];
850       /* Check for variants that are supported only in ICS mode,
851          or not at all.  Some that are accepted here nevertheless
852          have bugs; see comments below.
853       */
854       VariantClass variant = StringToVariant(appData.variant);
855       switch (variant) {
856       case VariantBughouse:     /* need four players and two boards */
857       case VariantKriegspiel:   /* need to hide pieces and move details */
858       /* case VariantFischeRandom: (Fabien: moved below) */
859         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantUnknown:
864       case VariantLoadable:
865       case Variant29:
866       case Variant30:
867       case Variant31:
868       case Variant32:
869       case Variant33:
870       case Variant34:
871       case Variant35:
872       case Variant36:
873       default:
874         sprintf(buf, _("Unknown variant name %s"), appData.variant);
875         DisplayFatalError(buf, 0, 2);
876         return;
877
878       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
879       case VariantFairy:      /* [HGM] TestLegality definitely off! */
880       case VariantGothic:     /* [HGM] should work */
881       case VariantCapablanca: /* [HGM] should work */
882       case VariantCourier:    /* [HGM] initial forced moves not implemented */
883       case VariantShogi:      /* [HGM] drops not tested for legality */
884       case VariantKnightmate: /* [HGM] should work */
885       case VariantCylinder:   /* [HGM] untested */
886       case VariantFalcon:     /* [HGM] untested */
887       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
888                                  offboard interposition not understood */
889       case VariantNormal:     /* definitely works! */
890       case VariantWildCastle: /* pieces not automatically shuffled */
891       case VariantNoCastle:   /* pieces not automatically shuffled */
892       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
893       case VariantLosers:     /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantSuicide:    /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantGiveaway:   /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantTwoKings:   /* should work */
900       case VariantAtomic:     /* should work except for win condition */
901       case Variant3Check:     /* should work except for win condition */
902       case VariantShatranj:   /* should work except for all win conditions */
903       case VariantMakruk:     /* should work except for daw countdown */
904       case VariantBerolina:   /* might work if TestLegality is off */
905       case VariantCapaRandom: /* should work */
906       case VariantJanus:      /* should work */
907       case VariantSuper:      /* experimental */
908       case VariantGreat:      /* experimental, requires legality testing to be off */
909         break;
910       }
911     }
912
913     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
914     InitEngineUCI( installDir, &second );
915 }
916
917 int NextIntegerFromString( char ** str, long * value )
918 {
919     int result = -1;
920     char * s = *str;
921
922     while( *s == ' ' || *s == '\t' ) {
923         s++;
924     }
925
926     *value = 0;
927
928     if( *s >= '0' && *s <= '9' ) {
929         while( *s >= '0' && *s <= '9' ) {
930             *value = *value * 10 + (*s - '0');
931             s++;
932         }
933
934         result = 0;
935     }
936
937     *str = s;
938
939     return result;
940 }
941
942 int NextTimeControlFromString( char ** str, long * value )
943 {
944     long temp;
945     int result = NextIntegerFromString( str, &temp );
946
947     if( result == 0 ) {
948         *value = temp * 60; /* Minutes */
949         if( **str == ':' ) {
950             (*str)++;
951             result = NextIntegerFromString( str, &temp );
952             *value += temp; /* Seconds */
953         }
954     }
955
956     return result;
957 }
958
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
961     int result = -1; long temp, temp2;
962
963     if(**str != '+') return -1; // old params remain in force!
964     (*str)++;
965     if( NextTimeControlFromString( str, &temp ) ) return -1;
966
967     if(**str != '/') {
968         /* time only: incremental or sudden-death time control */
969         if(**str == '+') { /* increment follows; read it */
970             (*str)++;
971             if(result = NextIntegerFromString( str, &temp2)) return -1;
972             *inc = temp2 * 1000;
973         } else *inc = 0;
974         *moves = 0; *tc = temp * 1000;
975         return 0;
976     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
977
978     (*str)++; /* classical time control */
979     result = NextTimeControlFromString( str, &temp2);
980     if(result == 0) {
981         *moves = temp/60;
982         *tc    = temp2 * 1000;
983         *inc   = 0;
984     }
985     return result;
986 }
987
988 int GetTimeQuota(int movenr)
989 {   /* [HGM] get time to add from the multi-session time-control string */
990     int moves=1; /* kludge to force reading of first session */
991     long time, increment;
992     char *s = fullTimeControlString;
993
994     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995     do {
996         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998         if(movenr == -1) return time;    /* last move before new session     */
999         if(!moves) return increment;     /* current session is incremental   */
1000         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001     } while(movenr >= -1);               /* try again for next session       */
1002
1003     return 0; // no new time quota on this move
1004 }
1005
1006 int
1007 ParseTimeControl(tc, ti, mps)
1008      char *tc;
1009      int ti;
1010      int mps;
1011 {
1012   long tc1;
1013   long tc2;
1014   char buf[MSG_SIZ];
1015   
1016   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1017   if(ti > 0) {
1018     if(mps)
1019       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1020     else sprintf(buf, "+%s+%d", tc, ti);
1021   } else {
1022     if(mps)
1023              sprintf(buf, "+%d/%s", mps, tc);
1024     else sprintf(buf, "+%s", tc);
1025   }
1026   fullTimeControlString = StrSave(buf);
1027   
1028   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1029     return FALSE;
1030   }
1031   
1032   if( *tc == '/' ) {
1033     /* Parse second time control */
1034     tc++;
1035     
1036     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1037       return FALSE;
1038     }
1039     
1040     if( tc2 == 0 ) {
1041       return FALSE;
1042     }
1043     
1044     timeControl_2 = tc2 * 1000;
1045   }
1046   else {
1047     timeControl_2 = 0;
1048   }
1049   
1050   if( tc1 == 0 ) {
1051     return FALSE;
1052   }
1053   
1054   timeControl = tc1 * 1000;
1055   
1056   if (ti >= 0) {
1057     timeIncrement = ti * 1000;  /* convert to ms */
1058     movesPerSession = 0;
1059   } else {
1060     timeIncrement = 0;
1061     movesPerSession = mps;
1062   }
1063   return TRUE;
1064 }
1065
1066 void
1067 InitBackEnd2()
1068 {
1069   if (appData.debugMode) {
1070     fprintf(debugFP, "%s\n", programVersion);
1071   }
1072
1073   set_cont_sequence(appData.wrapContSeq);
1074   if (appData.matchGames > 0) {
1075     appData.matchMode = TRUE;
1076   } else if (appData.matchMode) {
1077     appData.matchGames = 1;
1078   }
1079   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1080     appData.matchGames = appData.sameColorGames;
1081   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1082     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1083     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1084   }
1085   Reset(TRUE, FALSE);
1086   if (appData.noChessProgram || first.protocolVersion == 1) {
1087     InitBackEnd3();
1088     } else {
1089     /* kludge: allow timeout for initial "feature" commands */
1090     FreezeUI();
1091     DisplayMessage("", _("Starting chess program"));
1092     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1093   }
1094 }
1095
1096 void
1097 InitBackEnd3 P((void))
1098 {
1099   GameMode initialMode;
1100   char buf[MSG_SIZ];
1101   int err;
1102   
1103   InitChessProgram(&first, startedFromSetupPosition);
1104   
1105   
1106   if (appData.icsActive) 
1107     {
1108       err = establish();
1109       if (err != 0) 
1110         {
1111           if (*appData.icsCommPort != NULLCHAR) 
1112             {
1113               sprintf(buf, _("Could not open comm port %s"),
1114                       appData.icsCommPort);
1115             }
1116           else 
1117             {
1118               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1119                        appData.icsHost, appData.icsPort);
1120             }
1121           DisplayFatalError(buf, err, 1);
1122           return;
1123         }
1124
1125         SetICSMode();
1126         telnetISR =
1127           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1128         fromUserISR =
1129           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1130         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1131             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1132     }
1133   else if (appData.noChessProgram) 
1134     {
1135       SetNCPMode();
1136     } 
1137   else 
1138     {
1139       SetGNUMode();
1140     }
1141   
1142   if (*appData.cmailGameName != NULLCHAR) 
1143     {
1144       SetCmailMode();
1145       OpenLoopback(&cmailPR);
1146       cmailISR =
1147         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149   
1150   ThawUI();
1151   DisplayMessage("", "");
1152   if (StrCaseCmp(appData.initialMode, "") == 0) 
1153     {
1154       initialMode = BeginningOfGame;
1155     } 
1156   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1157     {
1158       initialMode = TwoMachinesPlay;
1159     } 
1160   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1161     {
1162       initialMode = AnalyzeFile;
1163     } 
1164   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1165     {
1166       initialMode = AnalyzeMode;
1167     } 
1168   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1169     {
1170       initialMode = MachinePlaysWhite;
1171     } 
1172   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1173     {
1174       initialMode = MachinePlaysBlack;
1175     } 
1176   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1177     {
1178       initialMode = EditGame;
1179     } 
1180   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1181     {
1182       initialMode = EditPosition;
1183     } 
1184   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1185     {
1186       initialMode = Training;
1187     } 
1188   else 
1189     {
1190       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1191       DisplayFatalError(buf, 0, 2);
1192       return;
1193     }
1194   
1195   if (appData.matchMode) 
1196     {
1197       /* Set up machine vs. machine match */
1198       if (appData.noChessProgram) 
1199         {
1200           DisplayFatalError(_("Can't have a match with no chess programs"),
1201                             0, 2);
1202           return;
1203         }
1204       matchMode = TRUE;
1205       matchGame = 1;
1206       if (*appData.loadGameFile != NULLCHAR) 
1207         {
1208           int index = appData.loadGameIndex; // [HGM] autoinc
1209           if(index<0) lastIndex = index = 1;
1210           if (!LoadGameFromFile(appData.loadGameFile,
1211                                 index,
1212                                 appData.loadGameFile, FALSE)) 
1213             {
1214               DisplayFatalError(_("Bad game file"), 0, 1);
1215               return;
1216             }
1217         } 
1218       else if (*appData.loadPositionFile != NULLCHAR) 
1219         {
1220           int index = appData.loadPositionIndex; // [HGM] autoinc
1221           if(index<0) lastIndex = index = 1;
1222           if (!LoadPositionFromFile(appData.loadPositionFile,
1223                                     index,
1224                                     appData.loadPositionFile)) 
1225             {
1226               DisplayFatalError(_("Bad position file"), 0, 1);
1227               return;
1228             }
1229         }
1230       TwoMachinesEvent();
1231     } 
1232   else if (*appData.cmailGameName != NULLCHAR) 
1233     {
1234       /* Set up cmail mode */
1235       ReloadCmailMsgEvent(TRUE);
1236     } 
1237   else 
1238     {
1239       /* Set up other modes */
1240       if (initialMode == AnalyzeFile) 
1241         {
1242           if (*appData.loadGameFile == NULLCHAR) 
1243             {
1244               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1245               return;
1246             }
1247         }
1248       if (*appData.loadGameFile != NULLCHAR) 
1249         {
1250           (void) LoadGameFromFile(appData.loadGameFile,
1251                                   appData.loadGameIndex,
1252                                   appData.loadGameFile, TRUE);
1253         } 
1254       else if (*appData.loadPositionFile != NULLCHAR) 
1255         {
1256           (void) LoadPositionFromFile(appData.loadPositionFile,
1257                                       appData.loadPositionIndex,
1258                                       appData.loadPositionFile);
1259           /* [HGM] try to make self-starting even after FEN load */
1260           /* to allow automatic setup of fairy variants with wtm */
1261           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1262             {
1263               gameMode = BeginningOfGame;
1264               setboardSpoiledMachineBlack = 1;
1265             }
1266           /* [HGM] loadPos: make that every new game uses the setup */
1267           /* from file as long as we do not switch variant          */
1268           if(!blackPlaysFirst) 
1269             {
1270               startedFromPositionFile = TRUE;
1271               CopyBoard(filePosition, boards[0]);
1272             }
1273         }
1274       if (initialMode == AnalyzeMode) 
1275         {
1276           if (appData.noChessProgram) 
1277             {
1278               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1279               return;
1280             }
1281           if (appData.icsActive) 
1282             {
1283               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1284               return;
1285             }
1286           AnalyzeModeEvent();
1287         } 
1288       else if (initialMode == AnalyzeFile) 
1289         {
1290           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1291           ShowThinkingEvent();
1292           AnalyzeFileEvent();
1293           AnalysisPeriodicEvent(1);
1294         } 
1295       else if (initialMode == MachinePlaysWhite) 
1296         {
1297           if (appData.noChessProgram) 
1298             {
1299               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1300                                 0, 2);
1301               return;
1302             }
1303           if (appData.icsActive) 
1304             {
1305               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1306                                 0, 2);
1307               return;
1308             }
1309           MachineWhiteEvent();
1310         } 
1311       else if (initialMode == MachinePlaysBlack) 
1312         {
1313           if (appData.noChessProgram) 
1314             {
1315               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1316                                 0, 2);
1317               return;
1318             }
1319           if (appData.icsActive) 
1320             {
1321               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1322                                 0, 2);
1323               return;
1324             }
1325           MachineBlackEvent();
1326         } 
1327       else if (initialMode == TwoMachinesPlay) 
1328         {
1329           if (appData.noChessProgram) 
1330             {
1331               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1332                                 0, 2);
1333               return;
1334             }
1335           if (appData.icsActive) 
1336             {
1337               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1338                                 0, 2);
1339               return;
1340             }
1341           TwoMachinesEvent();
1342         } 
1343       else if (initialMode == EditGame) 
1344         {
1345           EditGameEvent();
1346         } 
1347       else if (initialMode == EditPosition) 
1348         {
1349           EditPositionEvent();
1350         } 
1351       else if (initialMode == Training) 
1352         {
1353           if (*appData.loadGameFile == NULLCHAR) 
1354             {
1355               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1356               return;
1357             }
1358           TrainingEvent();
1359         }
1360     }
1361   
1362   return;
1363 }
1364
1365 /*
1366  * Establish will establish a contact to a remote host.port.
1367  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1368  *  used to talk to the host.
1369  * Returns 0 if okay, error code if not.
1370  */
1371 int
1372 establish()
1373 {
1374     char buf[MSG_SIZ];
1375
1376     if (*appData.icsCommPort != NULLCHAR) {
1377         /* Talk to the host through a serial comm port */
1378         return OpenCommPort(appData.icsCommPort, &icsPR);
1379
1380     } else if (*appData.gateway != NULLCHAR) {
1381         if (*appData.remoteShell == NULLCHAR) {
1382             /* Use the rcmd protocol to run telnet program on a gateway host */
1383             snprintf(buf, sizeof(buf), "%s %s %s",
1384                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1385             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1386
1387         } else {
1388             /* Use the rsh program to run telnet program on a gateway host */
1389             if (*appData.remoteUser == NULLCHAR) {
1390                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1391                         appData.gateway, appData.telnetProgram,
1392                         appData.icsHost, appData.icsPort);
1393             } else {
1394                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1395                         appData.remoteShell, appData.gateway,
1396                         appData.remoteUser, appData.telnetProgram,
1397                         appData.icsHost, appData.icsPort);
1398             }
1399             return StartChildProcess(buf, "", &icsPR);
1400
1401         }
1402     } else if (appData.useTelnet) {
1403         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1404
1405     } else {
1406         /* TCP socket interface differs somewhat between
1407            Unix and NT; handle details in the front end.
1408            */
1409         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1410     }
1411 }
1412
1413 void
1414 show_bytes(fp, buf, count)
1415      FILE *fp;
1416      char *buf;
1417      int count;
1418 {
1419     while (count--) {
1420         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1421             fprintf(fp, "\\%03o", *buf & 0xff);
1422         } else {
1423             putc(*buf, fp);
1424         }
1425         buf++;
1426     }
1427     fflush(fp);
1428 }
1429
1430 /* Returns an errno value */
1431 int
1432 OutputMaybeTelnet(pr, message, count, outError)
1433      ProcRef pr;
1434      char *message;
1435      int count;
1436      int *outError;
1437 {
1438     char buf[8192], *p, *q, *buflim;
1439     int left, newcount, outcount;
1440
1441     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1442         *appData.gateway != NULLCHAR) {
1443         if (appData.debugMode) {
1444             fprintf(debugFP, ">ICS: ");
1445             show_bytes(debugFP, message, count);
1446             fprintf(debugFP, "\n");
1447         }
1448         return OutputToProcess(pr, message, count, outError);
1449     }
1450
1451     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1452     p = message;
1453     q = buf;
1454     left = count;
1455     newcount = 0;
1456     while (left) {
1457         if (q >= buflim) {
1458             if (appData.debugMode) {
1459                 fprintf(debugFP, ">ICS: ");
1460                 show_bytes(debugFP, buf, newcount);
1461                 fprintf(debugFP, "\n");
1462             }
1463             outcount = OutputToProcess(pr, buf, newcount, outError);
1464             if (outcount < newcount) return -1; /* to be sure */
1465             q = buf;
1466             newcount = 0;
1467         }
1468         if (*p == '\n') {
1469             *q++ = '\r';
1470             newcount++;
1471         } else if (((unsigned char) *p) == TN_IAC) {
1472             *q++ = (char) TN_IAC;
1473             newcount ++;
1474         }
1475         *q++ = *p++;
1476         newcount++;
1477         left--;
1478     }
1479     if (appData.debugMode) {
1480         fprintf(debugFP, ">ICS: ");
1481         show_bytes(debugFP, buf, newcount);
1482         fprintf(debugFP, "\n");
1483     }
1484     outcount = OutputToProcess(pr, buf, newcount, outError);
1485     if (outcount < newcount) return -1; /* to be sure */
1486     return count;
1487 }
1488
1489 void
1490 read_from_player(isr, closure, message, count, error)
1491      InputSourceRef isr;
1492      VOIDSTAR closure;
1493      char *message;
1494      int count;
1495      int error;
1496 {
1497     int outError, outCount;
1498     static int gotEof = 0;
1499
1500     /* Pass data read from player on to ICS */
1501     if (count > 0) {
1502         gotEof = 0;
1503         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1504         if (outCount < count) {
1505             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506         }
1507     } else if (count < 0) {
1508         RemoveInputSource(isr);
1509         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1510     } else if (gotEof++ > 0) {
1511         RemoveInputSource(isr);
1512         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1513     }
1514 }
1515
1516 void
1517 KeepAlive()
1518 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1519     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1520     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1521     SendToICS("date\n");
1522     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1523 }
1524
1525 /* added routine for printf style output to ics */
1526 void ics_printf(char *format, ...)
1527 {
1528     char buffer[MSG_SIZ];
1529     va_list args;
1530
1531     va_start(args, format);
1532     vsnprintf(buffer, sizeof(buffer), format, args);
1533     buffer[sizeof(buffer)-1] = '\0';
1534     SendToICS(buffer);
1535     va_end(args);
1536 }
1537
1538 void
1539 SendToICS(s)
1540      char *s;
1541 {
1542     int count, outCount, outError;
1543
1544     if (icsPR == NULL) return;
1545
1546     count = strlen(s);
1547     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1548     if (outCount < count) {
1549         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1550     }
1551 }
1552
1553 /* This is used for sending logon scripts to the ICS. Sending
1554    without a delay causes problems when using timestamp on ICC
1555    (at least on my machine). */
1556 void
1557 SendToICSDelayed(s,msdelay)
1558      char *s;
1559      long msdelay;
1560 {
1561     int count, outCount, outError;
1562
1563     if (icsPR == NULL) return;
1564
1565     count = strlen(s);
1566     if (appData.debugMode) {
1567         fprintf(debugFP, ">ICS: ");
1568         show_bytes(debugFP, s, count);
1569         fprintf(debugFP, "\n");
1570     }
1571     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1572                                       msdelay);
1573     if (outCount < count) {
1574         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1575     }
1576 }
1577
1578
1579 /* Remove all highlighting escape sequences in s
1580    Also deletes any suffix starting with '('
1581    */
1582 char *
1583 StripHighlightAndTitle(s)
1584      char *s;
1585 {
1586     static char retbuf[MSG_SIZ];
1587     char *p = retbuf;
1588
1589     while (*s != NULLCHAR) {
1590         while (*s == '\033') {
1591             while (*s != NULLCHAR && !isalpha(*s)) s++;
1592             if (*s != NULLCHAR) s++;
1593         }
1594         while (*s != NULLCHAR && *s != '\033') {
1595             if (*s == '(' || *s == '[') {
1596                 *p = NULLCHAR;
1597                 return retbuf;
1598             }
1599             *p++ = *s++;
1600         }
1601     }
1602     *p = NULLCHAR;
1603     return retbuf;
1604 }
1605
1606 /* Remove all highlighting escape sequences in s */
1607 char *
1608 StripHighlight(s)
1609      char *s;
1610 {
1611     static char retbuf[MSG_SIZ];
1612     char *p = retbuf;
1613
1614     while (*s != NULLCHAR) {
1615         while (*s == '\033') {
1616             while (*s != NULLCHAR && !isalpha(*s)) s++;
1617             if (*s != NULLCHAR) s++;
1618         }
1619         while (*s != NULLCHAR && *s != '\033') {
1620             *p++ = *s++;
1621         }
1622     }
1623     *p = NULLCHAR;
1624     return retbuf;
1625 }
1626
1627 char *variantNames[] = VARIANT_NAMES;
1628 char *
1629 VariantName(v)
1630      VariantClass v;
1631 {
1632     return variantNames[v];
1633 }
1634
1635
1636 /* Identify a variant from the strings the chess servers use or the
1637    PGN Variant tag names we use. */
1638 VariantClass
1639 StringToVariant(e)
1640      char *e;
1641 {
1642     char *p;
1643     int wnum = -1;
1644     VariantClass v = VariantNormal;
1645     int i, found = FALSE;
1646     char buf[MSG_SIZ];
1647
1648     if (!e) return v;
1649
1650     /* [HGM] skip over optional board-size prefixes */
1651     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1652         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1653         while( *e++ != '_');
1654     }
1655
1656     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1657         v = VariantNormal;
1658         found = TRUE;
1659     } else
1660     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1661       if (StrCaseStr(e, variantNames[i])) {
1662         v = (VariantClass) i;
1663         found = TRUE;
1664         break;
1665       }
1666     }
1667
1668     if (!found) {
1669       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1670           || StrCaseStr(e, "wild/fr")
1671           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1672         v = VariantFischeRandom;
1673       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1674                  (i = 1, p = StrCaseStr(e, "w"))) {
1675         p += i;
1676         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1677         if (isdigit(*p)) {
1678           wnum = atoi(p);
1679         } else {
1680           wnum = -1;
1681         }
1682         switch (wnum) {
1683         case 0: /* FICS only, actually */
1684         case 1:
1685           /* Castling legal even if K starts on d-file */
1686           v = VariantWildCastle;
1687           break;
1688         case 2:
1689         case 3:
1690         case 4:
1691           /* Castling illegal even if K & R happen to start in
1692              normal positions. */
1693           v = VariantNoCastle;
1694           break;
1695         case 5:
1696         case 7:
1697         case 8:
1698         case 10:
1699         case 11:
1700         case 12:
1701         case 13:
1702         case 14:
1703         case 15:
1704         case 18:
1705         case 19:
1706           /* Castling legal iff K & R start in normal positions */
1707           v = VariantNormal;
1708           break;
1709         case 6:
1710         case 20:
1711         case 21:
1712           /* Special wilds for position setup; unclear what to do here */
1713           v = VariantLoadable;
1714           break;
1715         case 9:
1716           /* Bizarre ICC game */
1717           v = VariantTwoKings;
1718           break;
1719         case 16:
1720           v = VariantKriegspiel;
1721           break;
1722         case 17:
1723           v = VariantLosers;
1724           break;
1725         case 22:
1726           v = VariantFischeRandom;
1727           break;
1728         case 23:
1729           v = VariantCrazyhouse;
1730           break;
1731         case 24:
1732           v = VariantBughouse;
1733           break;
1734         case 25:
1735           v = Variant3Check;
1736           break;
1737         case 26:
1738           /* Not quite the same as FICS suicide! */
1739           v = VariantGiveaway;
1740           break;
1741         case 27:
1742           v = VariantAtomic;
1743           break;
1744         case 28:
1745           v = VariantShatranj;
1746           break;
1747
1748         /* Temporary names for future ICC types.  The name *will* change in
1749            the next xboard/WinBoard release after ICC defines it. */
1750         case 29:
1751           v = Variant29;
1752           break;
1753         case 30:
1754           v = Variant30;
1755           break;
1756         case 31:
1757           v = Variant31;
1758           break;
1759         case 32:
1760           v = Variant32;
1761           break;
1762         case 33:
1763           v = Variant33;
1764           break;
1765         case 34:
1766           v = Variant34;
1767           break;
1768         case 35:
1769           v = Variant35;
1770           break;
1771         case 36:
1772           v = Variant36;
1773           break;
1774         case 37:
1775           v = VariantShogi;
1776           break;
1777         case 38:
1778           v = VariantXiangqi;
1779           break;
1780         case 39:
1781           v = VariantCourier;
1782           break;
1783         case 40:
1784           v = VariantGothic;
1785           break;
1786         case 41:
1787           v = VariantCapablanca;
1788           break;
1789         case 42:
1790           v = VariantKnightmate;
1791           break;
1792         case 43:
1793           v = VariantFairy;
1794           break;
1795         case 44:
1796           v = VariantCylinder;
1797           break;
1798         case 45:
1799           v = VariantFalcon;
1800           break;
1801         case 46:
1802           v = VariantCapaRandom;
1803           break;
1804         case 47:
1805           v = VariantBerolina;
1806           break;
1807         case 48:
1808           v = VariantJanus;
1809           break;
1810         case 49:
1811           v = VariantSuper;
1812           break;
1813         case 50:
1814           v = VariantGreat;
1815           break;
1816         case -1:
1817           /* Found "wild" or "w" in the string but no number;
1818              must assume it's normal chess. */
1819           v = VariantNormal;
1820           break;
1821         default:
1822           sprintf(buf, _("Unknown wild type %d"), wnum);
1823           DisplayError(buf, 0);
1824           v = VariantUnknown;
1825           break;
1826         }
1827       }
1828     }
1829     if (appData.debugMode) {
1830       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1831               e, wnum, VariantName(v));
1832     }
1833     return v;
1834 }
1835
1836 static int leftover_start = 0, leftover_len = 0;
1837 char star_match[STAR_MATCH_N][MSG_SIZ];
1838
1839 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1840    advance *index beyond it, and set leftover_start to the new value of
1841    *index; else return FALSE.  If pattern contains the character '*', it
1842    matches any sequence of characters not containing '\r', '\n', or the
1843    character following the '*' (if any), and the matched sequence(s) are
1844    copied into star_match.
1845    */
1846 int
1847 looking_at(buf, index, pattern)
1848      char *buf;
1849      int *index;
1850      char *pattern;
1851 {
1852     char *bufp = &buf[*index], *patternp = pattern;
1853     int star_count = 0;
1854     char *matchp = star_match[0];
1855
1856     for (;;) {
1857         if (*patternp == NULLCHAR) {
1858             *index = leftover_start = bufp - buf;
1859             *matchp = NULLCHAR;
1860             return TRUE;
1861         }
1862         if (*bufp == NULLCHAR) return FALSE;
1863         if (*patternp == '*') {
1864             if (*bufp == *(patternp + 1)) {
1865                 *matchp = NULLCHAR;
1866                 matchp = star_match[++star_count];
1867                 patternp += 2;
1868                 bufp++;
1869                 continue;
1870             } else if (*bufp == '\n' || *bufp == '\r') {
1871                 patternp++;
1872                 if (*patternp == NULLCHAR)
1873                   continue;
1874                 else
1875                   return FALSE;
1876             } else {
1877                 *matchp++ = *bufp++;
1878                 continue;
1879             }
1880         }
1881         if (*patternp != *bufp) return FALSE;
1882         patternp++;
1883         bufp++;
1884     }
1885 }
1886
1887 void
1888 SendToPlayer(data, length)
1889      char *data;
1890      int length;
1891 {
1892     int error, outCount;
1893     outCount = OutputToProcess(NoProc, data, length, &error);
1894     if (outCount < length) {
1895         DisplayFatalError(_("Error writing to display"), error, 1);
1896     }
1897 }
1898
1899 void
1900 PackHolding(packed, holding)
1901      char packed[];
1902      char *holding;
1903 {
1904     char *p = holding;
1905     char *q = packed;
1906     int runlength = 0;
1907     int curr = 9999;
1908     do {
1909         if (*p == curr) {
1910             runlength++;
1911         } else {
1912             switch (runlength) {
1913               case 0:
1914                 break;
1915               case 1:
1916                 *q++ = curr;
1917                 break;
1918               case 2:
1919                 *q++ = curr;
1920                 *q++ = curr;
1921                 break;
1922               default:
1923                 sprintf(q, "%d", runlength);
1924                 while (*q) q++;
1925                 *q++ = curr;
1926                 break;
1927             }
1928             runlength = 1;
1929             curr = *p;
1930         }
1931     } while (*p++);
1932     *q = NULLCHAR;
1933 }
1934
1935 /* Telnet protocol requests from the front end */
1936 void
1937 TelnetRequest(ddww, option)
1938      unsigned char ddww, option;
1939 {
1940     unsigned char msg[3];
1941     int outCount, outError;
1942
1943     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1944
1945     if (appData.debugMode) {
1946         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1947         switch (ddww) {
1948           case TN_DO:
1949             ddwwStr = "DO";
1950             break;
1951           case TN_DONT:
1952             ddwwStr = "DONT";
1953             break;
1954           case TN_WILL:
1955             ddwwStr = "WILL";
1956             break;
1957           case TN_WONT:
1958             ddwwStr = "WONT";
1959             break;
1960           default:
1961             ddwwStr = buf1;
1962             sprintf(buf1, "%d", ddww);
1963             break;
1964         }
1965         switch (option) {
1966           case TN_ECHO:
1967             optionStr = "ECHO";
1968             break;
1969           default:
1970             optionStr = buf2;
1971             sprintf(buf2, "%d", option);
1972             break;
1973         }
1974         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1975     }
1976     msg[0] = TN_IAC;
1977     msg[1] = ddww;
1978     msg[2] = option;
1979     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1980     if (outCount < 3) {
1981         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1982     }
1983 }
1984
1985 void
1986 DoEcho()
1987 {
1988     if (!appData.icsActive) return;
1989     TelnetRequest(TN_DO, TN_ECHO);
1990 }
1991
1992 void
1993 DontEcho()
1994 {
1995     if (!appData.icsActive) return;
1996     TelnetRequest(TN_DONT, TN_ECHO);
1997 }
1998
1999 void
2000 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2001 {
2002     /* put the holdings sent to us by the server on the board holdings area */
2003     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2004     char p;
2005     ChessSquare piece;
2006
2007     if(gameInfo.holdingsWidth < 2)  return;
2008     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2009         return; // prevent overwriting by pre-board holdings
2010
2011     if( (int)lowestPiece >= BlackPawn ) {
2012         holdingsColumn = 0;
2013         countsColumn = 1;
2014         holdingsStartRow = BOARD_HEIGHT-1;
2015         direction = -1;
2016     } else {
2017         holdingsColumn = BOARD_WIDTH-1;
2018         countsColumn = BOARD_WIDTH-2;
2019         holdingsStartRow = 0;
2020         direction = 1;
2021     }
2022
2023     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2024         board[i][holdingsColumn] = EmptySquare;
2025         board[i][countsColumn]   = (ChessSquare) 0;
2026     }
2027     while( (p=*holdings++) != NULLCHAR ) {
2028         piece = CharToPiece( ToUpper(p) );
2029         if(piece == EmptySquare) continue;
2030         /*j = (int) piece - (int) WhitePawn;*/
2031         j = PieceToNumber(piece);
2032         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2033         if(j < 0) continue;               /* should not happen */
2034         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2035         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2036         board[holdingsStartRow+j*direction][countsColumn]++;
2037     }
2038 }
2039
2040
2041 void
2042 VariantSwitch(Board board, VariantClass newVariant)
2043 {
2044    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2045    Board oldBoard;
2046
2047    startedFromPositionFile = FALSE;
2048    if(gameInfo.variant == newVariant) return;
2049
2050    /* [HGM] This routine is called each time an assignment is made to
2051     * gameInfo.variant during a game, to make sure the board sizes
2052     * are set to match the new variant. If that means adding or deleting
2053     * holdings, we shift the playing board accordingly
2054     * This kludge is needed because in ICS observe mode, we get boards
2055     * of an ongoing game without knowing the variant, and learn about the
2056     * latter only later. This can be because of the move list we requested,
2057     * in which case the game history is refilled from the beginning anyway,
2058     * but also when receiving holdings of a crazyhouse game. In the latter
2059     * case we want to add those holdings to the already received position.
2060     */
2061    
2062    if (appData.debugMode) {
2063      fprintf(debugFP, "Switch board from %s to %s\n",
2064              VariantName(gameInfo.variant), VariantName(newVariant));
2065      setbuf(debugFP, NULL);
2066    }
2067    shuffleOpenings = 0;       /* [HGM] shuffle */
2068    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2069    switch(newVariant) 
2070      {
2071      case VariantShogi:
2072        newWidth = 9;  newHeight = 9;
2073        gameInfo.holdingsSize = 7;
2074      case VariantBughouse:
2075      case VariantCrazyhouse:
2076        newHoldingsWidth = 2; break;
2077      case VariantGreat:
2078        newWidth = 10;
2079      case VariantSuper:
2080        newHoldingsWidth = 2;
2081        gameInfo.holdingsSize = 8;
2082        break;
2083      case VariantGothic:
2084      case VariantCapablanca:
2085      case VariantCapaRandom:
2086        newWidth = 10;
2087      default:
2088        newHoldingsWidth = gameInfo.holdingsSize = 0;
2089      };
2090    
2091    if(newWidth  != gameInfo.boardWidth  ||
2092       newHeight != gameInfo.boardHeight ||
2093       newHoldingsWidth != gameInfo.holdingsWidth ) {
2094      
2095      /* shift position to new playing area, if needed */
2096      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2097        for(i=0; i<BOARD_HEIGHT; i++) 
2098          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2099            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2100              board[i][j];
2101        for(i=0; i<newHeight; i++) {
2102          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2103          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2104        }
2105      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2106        for(i=0; i<BOARD_HEIGHT; i++)
2107          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2108            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2109              board[i][j];
2110      }
2111      gameInfo.boardWidth  = newWidth;
2112      gameInfo.boardHeight = newHeight;
2113      gameInfo.holdingsWidth = newHoldingsWidth;
2114      gameInfo.variant = newVariant;
2115      InitDrawingSizes(-2, 0);
2116    } else gameInfo.variant = newVariant;
2117    CopyBoard(oldBoard, board);   // remember correctly formatted board
2118      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2119    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2120 }
2121
2122 static int loggedOn = FALSE;
2123
2124 /*-- Game start info cache: --*/
2125 int gs_gamenum;
2126 char gs_kind[MSG_SIZ];
2127 static char player1Name[128] = "";
2128 static char player2Name[128] = "";
2129 static char cont_seq[] = "\n\\   ";
2130 static int player1Rating = -1;
2131 static int player2Rating = -1;
2132 /*----------------------------*/
2133
2134 ColorClass curColor = ColorNormal;
2135 int suppressKibitz = 0;
2136
2137 // [HGM] seekgraph
2138 Boolean soughtPending = FALSE;
2139 Boolean seekGraphUp;
2140 #define MAX_SEEK_ADS 200
2141 #define SQUARE 0x80
2142 char *seekAdList[MAX_SEEK_ADS];
2143 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2144 float tcList[MAX_SEEK_ADS];
2145 char colorList[MAX_SEEK_ADS];
2146 int nrOfSeekAds = 0;
2147 int minRating = 1010, maxRating = 2800;
2148 int hMargin = 10, vMargin = 20, h, w;
2149 extern int squareSize, lineGap;
2150
2151 void
2152 PlotSeekAd(int i)
2153 {
2154         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2155         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2156         if(r < minRating+100 && r >=0 ) r = minRating+100;
2157         if(r > maxRating) r = maxRating;
2158         if(tc < 1.) tc = 1.;
2159         if(tc > 95.) tc = 95.;
2160         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2161         y = ((double)r - minRating)/(maxRating - minRating)
2162             * (h-vMargin-squareSize/8-1) + vMargin;
2163         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2164         if(strstr(seekAdList[i], " u ")) color = 1;
2165         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2166            !strstr(seekAdList[i], "bullet") &&
2167            !strstr(seekAdList[i], "blitz") &&
2168            !strstr(seekAdList[i], "standard") ) color = 2;
2169         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2170         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2171 }
2172
2173 void
2174 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2175 {
2176         char buf[MSG_SIZ], *ext = "";
2177         VariantClass v = StringToVariant(type);
2178         if(strstr(type, "wild")) {
2179             ext = type + 4; // append wild number
2180             if(v == VariantFischeRandom) type = "chess960"; else
2181             if(v == VariantLoadable) type = "setup"; else
2182             type = VariantName(v);
2183         }
2184         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2185         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2186             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2187             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2188             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2189             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2190             seekNrList[nrOfSeekAds] = nr;
2191             zList[nrOfSeekAds] = 0;
2192             seekAdList[nrOfSeekAds++] = StrSave(buf);
2193             if(plot) PlotSeekAd(nrOfSeekAds-1);
2194         }
2195 }
2196
2197 void
2198 EraseSeekDot(int i)
2199 {
2200     int x = xList[i], y = yList[i], d=squareSize/4, k;
2201     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2202     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2203     // now replot every dot that overlapped
2204     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2205         int xx = xList[k], yy = yList[k];
2206         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2207             DrawSeekDot(xx, yy, colorList[k]);
2208     }
2209 }
2210
2211 void
2212 RemoveSeekAd(int nr)
2213 {
2214         int i;
2215         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2216             EraseSeekDot(i);
2217             if(seekAdList[i]) free(seekAdList[i]);
2218             seekAdList[i] = seekAdList[--nrOfSeekAds];
2219             seekNrList[i] = seekNrList[nrOfSeekAds];
2220             ratingList[i] = ratingList[nrOfSeekAds];
2221             colorList[i]  = colorList[nrOfSeekAds];
2222             tcList[i] = tcList[nrOfSeekAds];
2223             xList[i]  = xList[nrOfSeekAds];
2224             yList[i]  = yList[nrOfSeekAds];
2225             zList[i]  = zList[nrOfSeekAds];
2226             seekAdList[nrOfSeekAds] = NULL;
2227             break;
2228         }
2229 }
2230
2231 Boolean
2232 MatchSoughtLine(char *line)
2233 {
2234     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2235     int nr, base, inc, u=0; char dummy;
2236
2237     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2238        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2239        (u=1) &&
2240        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2241         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2242         // match: compact and save the line
2243         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2244         return TRUE;
2245     }
2246     return FALSE;
2247 }
2248
2249 int
2250 DrawSeekGraph()
2251 {
2252     if(!seekGraphUp) return FALSE;
2253     int i;
2254     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2255     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2256
2257     DrawSeekBackground(0, 0, w, h);
2258     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2259     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2260     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2261         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2262         yy = h-1-yy;
2263         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2264         if(i%500 == 0) {
2265             char buf[MSG_SIZ];
2266             sprintf(buf, "%d", i);
2267             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2268         }
2269     }
2270     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2271     for(i=1; i<100; i+=(i<10?1:5)) {
2272         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2273         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2274         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2275             char buf[MSG_SIZ];
2276             sprintf(buf, "%d", i);
2277             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2278         }
2279     }
2280     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2281     return TRUE;
2282 }
2283
2284 int SeekGraphClick(ClickType click, int x, int y, int moving)
2285 {
2286     static int lastDown = 0, displayed = 0, lastSecond;
2287     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2288         if(click == Release || moving) return FALSE;
2289         nrOfSeekAds = 0;
2290         soughtPending = TRUE;
2291         SendToICS(ics_prefix);
2292         SendToICS("sought\n"); // should this be "sought all"?
2293     } else { // issue challenge based on clicked ad
2294         int dist = 10000; int i, closest = 0, second = 0;
2295         for(i=0; i<nrOfSeekAds; i++) {
2296             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2297             if(d < dist) { dist = d; closest = i; }
2298             second += (d - zList[i] < 120); // count in-range ads
2299             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2300         }
2301         if(dist < 120) {
2302             char buf[MSG_SIZ];
2303             second = (second > 1);
2304             if(displayed != closest || second != lastSecond) {
2305                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2306                 lastSecond = second; displayed = closest;
2307             }
2308             sprintf(buf, "play %d\n", seekNrList[closest]);
2309             if(click == Press) {
2310                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2311                 lastDown = closest;
2312                 return TRUE;
2313             } // on press 'hit', only show info
2314             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2315             SendToICS(ics_prefix);
2316             SendToICS(buf); // should this be "sought all"?
2317         } else if(click == Release) { // release 'miss' is ignored
2318             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2319             if(moving == 2) { // right up-click
2320                 nrOfSeekAds = 0; // refresh graph
2321                 soughtPending = TRUE;
2322                 SendToICS(ics_prefix);
2323                 SendToICS("sought\n"); // should this be "sought all"?
2324             }
2325             return TRUE;
2326         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2327         // press miss or release hit 'pop down' seek graph
2328         seekGraphUp = FALSE;
2329         DrawPosition(TRUE, NULL);
2330     }
2331     return TRUE;
2332 }
2333
2334 void
2335 read_from_ics(isr, closure, data, count, error)
2336      InputSourceRef isr;
2337      VOIDSTAR closure;
2338      char *data;
2339      int count;
2340      int error;
2341 {
2342 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2343 #define STARTED_NONE 0
2344 #define STARTED_MOVES 1
2345 #define STARTED_BOARD 2
2346 #define STARTED_OBSERVE 3
2347 #define STARTED_HOLDINGS 4
2348 #define STARTED_CHATTER 5
2349 #define STARTED_COMMENT 6
2350 #define STARTED_MOVES_NOHIDE 7
2351
2352     static int started = STARTED_NONE;
2353     static char parse[20000];
2354     static int parse_pos = 0;
2355     static char buf[BUF_SIZE + 1];
2356     static int firstTime = TRUE, intfSet = FALSE;
2357     static ColorClass prevColor = ColorNormal;
2358     static int savingComment = FALSE;
2359     static int cmatch = 0; // continuation sequence match
2360     char *bp;
2361     char str[500];
2362     int i, oldi;
2363     int buf_len;
2364     int next_out;
2365     int tkind;
2366     int backup;    /* [DM] For zippy color lines */
2367     char *p;
2368     char talker[MSG_SIZ]; // [HGM] chat
2369     int channel;
2370
2371     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2372
2373     if (appData.debugMode) {
2374       if (!error) {
2375         fprintf(debugFP, "<ICS: ");
2376         show_bytes(debugFP, data, count);
2377         fprintf(debugFP, "\n");
2378       }
2379     }
2380
2381     if (appData.debugMode) { int f = forwardMostMove;
2382         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2383                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2384                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2385     }
2386     if (count > 0) {
2387         /* If last read ended with a partial line that we couldn't parse,
2388            prepend it to the new read and try again. */
2389         if (leftover_len > 0) {
2390             for (i=0; i<leftover_len; i++)
2391               buf[i] = buf[leftover_start + i];
2392         }
2393
2394     /* copy new characters into the buffer */
2395     bp = buf + leftover_len;
2396     buf_len=leftover_len;
2397     for (i=0; i<count; i++)
2398     {
2399         // ignore these
2400         if (data[i] == '\r')
2401             continue;
2402
2403         // join lines split by ICS?
2404         if (!appData.noJoin)
2405         {
2406             /*
2407                 Joining just consists of finding matches against the
2408                 continuation sequence, and discarding that sequence
2409                 if found instead of copying it.  So, until a match
2410                 fails, there's nothing to do since it might be the
2411                 complete sequence, and thus, something we don't want
2412                 copied.
2413             */
2414             if (data[i] == cont_seq[cmatch])
2415             {
2416                 cmatch++;
2417                 if (cmatch == strlen(cont_seq))
2418                 {
2419                     cmatch = 0; // complete match.  just reset the counter
2420
2421                     /*
2422                         it's possible for the ICS to not include the space
2423                         at the end of the last word, making our [correct]
2424                         join operation fuse two separate words.  the server
2425                         does this when the space occurs at the width setting.
2426                     */
2427                     if (!buf_len || buf[buf_len-1] != ' ')
2428                     {
2429                         *bp++ = ' ';
2430                         buf_len++;
2431                     }
2432                 }
2433                 continue;
2434             }
2435             else if (cmatch)
2436             {
2437                 /*
2438                     match failed, so we have to copy what matched before
2439                     falling through and copying this character.  In reality,
2440                     this will only ever be just the newline character, but
2441                     it doesn't hurt to be precise.
2442                 */
2443                 strncpy(bp, cont_seq, cmatch);
2444                 bp += cmatch;
2445                 buf_len += cmatch;
2446                 cmatch = 0;
2447             }
2448         }
2449
2450         // copy this char
2451         *bp++ = data[i];
2452         buf_len++;
2453     }
2454
2455         buf[buf_len] = NULLCHAR;
2456 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2457         next_out = 0;
2458         leftover_start = 0;
2459
2460         i = 0;
2461         while (i < buf_len) {
2462             /* Deal with part of the TELNET option negotiation
2463                protocol.  We refuse to do anything beyond the
2464                defaults, except that we allow the WILL ECHO option,
2465                which ICS uses to turn off password echoing when we are
2466                directly connected to it.  We reject this option
2467                if localLineEditing mode is on (always on in xboard)
2468                and we are talking to port 23, which might be a real
2469                telnet server that will try to keep WILL ECHO on permanently.
2470              */
2471             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2472                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2473                 unsigned char option;
2474                 oldi = i;
2475                 switch ((unsigned char) buf[++i]) {
2476                   case TN_WILL:
2477                     if (appData.debugMode)
2478                       fprintf(debugFP, "\n<WILL ");
2479                     switch (option = (unsigned char) buf[++i]) {
2480                       case TN_ECHO:
2481                         if (appData.debugMode)
2482                           fprintf(debugFP, "ECHO ");
2483                         /* Reply only if this is a change, according
2484                            to the protocol rules. */
2485                         if (remoteEchoOption) break;
2486                         if (appData.localLineEditing &&
2487                             atoi(appData.icsPort) == TN_PORT) {
2488                             TelnetRequest(TN_DONT, TN_ECHO);
2489                         } else {
2490                             EchoOff();
2491                             TelnetRequest(TN_DO, TN_ECHO);
2492                             remoteEchoOption = TRUE;
2493                         }
2494                         break;
2495                       default:
2496                         if (appData.debugMode)
2497                           fprintf(debugFP, "%d ", option);
2498                         /* Whatever this is, we don't want it. */
2499                         TelnetRequest(TN_DONT, option);
2500                         break;
2501                     }
2502                     break;
2503                   case TN_WONT:
2504                     if (appData.debugMode)
2505                       fprintf(debugFP, "\n<WONT ");
2506                     switch (option = (unsigned char) buf[++i]) {
2507                       case TN_ECHO:
2508                         if (appData.debugMode)
2509                           fprintf(debugFP, "ECHO ");
2510                         /* Reply only if this is a change, according
2511                            to the protocol rules. */
2512                         if (!remoteEchoOption) break;
2513                         EchoOn();
2514                         TelnetRequest(TN_DONT, TN_ECHO);
2515                         remoteEchoOption = FALSE;
2516                         break;
2517                       default:
2518                         if (appData.debugMode)
2519                           fprintf(debugFP, "%d ", (unsigned char) option);
2520                         /* Whatever this is, it must already be turned
2521                            off, because we never agree to turn on
2522                            anything non-default, so according to the
2523                            protocol rules, we don't reply. */
2524                         break;
2525                     }
2526                     break;
2527                   case TN_DO:
2528                     if (appData.debugMode)
2529                       fprintf(debugFP, "\n<DO ");
2530                     switch (option = (unsigned char) buf[++i]) {
2531                       default:
2532                         /* Whatever this is, we refuse to do it. */
2533                         if (appData.debugMode)
2534                           fprintf(debugFP, "%d ", option);
2535                         TelnetRequest(TN_WONT, option);
2536                         break;
2537                     }
2538                     break;
2539                   case TN_DONT:
2540                     if (appData.debugMode)
2541                       fprintf(debugFP, "\n<DONT ");
2542                     switch (option = (unsigned char) buf[++i]) {
2543                       default:
2544                         if (appData.debugMode)
2545                           fprintf(debugFP, "%d ", option);
2546                         /* Whatever this is, we are already not doing
2547                            it, because we never agree to do anything
2548                            non-default, so according to the protocol
2549                            rules, we don't reply. */
2550                         break;
2551                     }
2552                     break;
2553                   case TN_IAC:
2554                     if (appData.debugMode)
2555                       fprintf(debugFP, "\n<IAC ");
2556                     /* Doubled IAC; pass it through */
2557                     i--;
2558                     break;
2559                   default:
2560                     if (appData.debugMode)
2561                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2562                     /* Drop all other telnet commands on the floor */
2563                     break;
2564                 }
2565                 if (oldi > next_out)
2566                   SendToPlayer(&buf[next_out], oldi - next_out);
2567                 if (++i > next_out)
2568                   next_out = i;
2569                 continue;
2570             }
2571
2572             /* OK, this at least will *usually* work */
2573             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2574                 loggedOn = TRUE;
2575             }
2576
2577             if (loggedOn && !intfSet) {
2578                 if (ics_type == ICS_ICC) {
2579                   sprintf(str,
2580                           "/set-quietly interface %s\n/set-quietly style 12\n",
2581                           programVersion);
2582                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2583                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2584                 } else if (ics_type == ICS_CHESSNET) {
2585                   sprintf(str, "/style 12\n");
2586                 } else {
2587                   strcpy(str, "alias $ @\n$set interface ");
2588                   strcat(str, programVersion);
2589                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2590                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2591                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2592 #ifdef WIN32
2593                   strcat(str, "$iset nohighlight 1\n");
2594 #endif
2595                   strcat(str, "$iset lock 1\n$style 12\n");
2596                 }
2597                 SendToICS(str);
2598                 NotifyFrontendLogin();
2599                 intfSet = TRUE;
2600             }
2601
2602             if (started == STARTED_COMMENT) {
2603                 /* Accumulate characters in comment */
2604                 parse[parse_pos++] = buf[i];
2605                 if (buf[i] == '\n') {
2606                     parse[parse_pos] = NULLCHAR;
2607                     if(chattingPartner>=0) {
2608                         char mess[MSG_SIZ];
2609                         sprintf(mess, "%s%s", talker, parse);
2610                         OutputChatMessage(chattingPartner, mess);
2611                         chattingPartner = -1;
2612                     } else
2613                     if(!suppressKibitz) // [HGM] kibitz
2614                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2615                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2616                         int nrDigit = 0, nrAlph = 0, j;
2617                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2618                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2619                         parse[parse_pos] = NULLCHAR;
2620                         // try to be smart: if it does not look like search info, it should go to
2621                         // ICS interaction window after all, not to engine-output window.
2622                         for(j=0; j<parse_pos; j++) { // count letters and digits
2623                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2624                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2625                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2626                         }
2627                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2628                             int depth=0; float score;
2629                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2630                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2631                                 pvInfoList[forwardMostMove-1].depth = depth;
2632                                 pvInfoList[forwardMostMove-1].score = 100*score;
2633                             }
2634                             OutputKibitz(suppressKibitz, parse);
2635                             next_out = i+1; // [HGM] suppress printing in ICS window
2636                         } else {
2637                             char tmp[MSG_SIZ];
2638                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2639                             SendToPlayer(tmp, strlen(tmp));
2640                         }
2641                     }
2642                     started = STARTED_NONE;
2643                 } else {
2644                     /* Don't match patterns against characters in comment */
2645                     i++;
2646                     continue;
2647                 }
2648             }
2649             if (started == STARTED_CHATTER) {
2650                 if (buf[i] != '\n') {
2651                     /* Don't match patterns against characters in chatter */
2652                     i++;
2653                     continue;
2654                 }
2655                 started = STARTED_NONE;
2656             }
2657
2658             /* Kludge to deal with rcmd protocol */
2659             if (firstTime && looking_at(buf, &i, "\001*")) {
2660                 DisplayFatalError(&buf[1], 0, 1);
2661                 continue;
2662             } else {
2663                 firstTime = FALSE;
2664             }
2665
2666             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2667                 ics_type = ICS_ICC;
2668                 ics_prefix = "/";
2669                 if (appData.debugMode)
2670                   fprintf(debugFP, "ics_type %d\n", ics_type);
2671                 continue;
2672             }
2673             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2674                 ics_type = ICS_FICS;
2675                 ics_prefix = "$";
2676                 if (appData.debugMode)
2677                   fprintf(debugFP, "ics_type %d\n", ics_type);
2678                 continue;
2679             }
2680             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2681                 ics_type = ICS_CHESSNET;
2682                 ics_prefix = "/";
2683                 if (appData.debugMode)
2684                   fprintf(debugFP, "ics_type %d\n", ics_type);
2685                 continue;
2686             }
2687
2688             if (!loggedOn &&
2689                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2690                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2691                  looking_at(buf, &i, "will be \"*\""))) {
2692               strcpy(ics_handle, star_match[0]);
2693               continue;
2694             }
2695
2696             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2697               char buf[MSG_SIZ];
2698               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2699               DisplayIcsInteractionTitle(buf);
2700               have_set_title = TRUE;
2701             }
2702
2703             /* skip finger notes */
2704             if (started == STARTED_NONE &&
2705                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2706                  (buf[i] == '1' && buf[i+1] == '0')) &&
2707                 buf[i+2] == ':' && buf[i+3] == ' ') {
2708               started = STARTED_CHATTER;
2709               i += 3;
2710               continue;
2711             }
2712
2713             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2714             if(appData.seekGraph) {
2715                 if(soughtPending && MatchSoughtLine(buf+i)) {
2716                     i = strstr(buf+i, "rated") - buf;
2717                     next_out = leftover_start = i;
2718                     started = STARTED_CHATTER;
2719                     suppressKibitz = TRUE;
2720                     continue;
2721                 }
2722                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2723                         && looking_at(buf, &i, "* ads displayed")) {
2724                     soughtPending = FALSE;
2725                     seekGraphUp = TRUE;
2726                     DrawSeekGraph();
2727                     continue;
2728                 }
2729                 if(appData.autoRefresh) {
2730                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2731                         int s = (ics_type == ICS_ICC); // ICC format differs
2732                         if(seekGraphUp)
2733                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2734                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2735                         looking_at(buf, &i, "*% "); // eat prompt
2736                         next_out = i; // suppress
2737                         continue;
2738                     }
2739                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2740                         char *p = star_match[0];
2741                         while(*p) {
2742                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2743                             while(*p && *p++ != ' '); // next
2744                         }
2745                         looking_at(buf, &i, "*% "); // eat prompt
2746                         next_out = i;
2747                         continue;
2748                     }
2749                 }
2750             }
2751
2752             /* skip formula vars */
2753             if (started == STARTED_NONE &&
2754                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2755               started = STARTED_CHATTER;
2756               i += 3;
2757               continue;
2758             }
2759
2760             oldi = i;
2761             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2762             if (appData.autoKibitz && started == STARTED_NONE &&
2763                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2764                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2765                 if(looking_at(buf, &i, "* kibitzes: ") &&
2766                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2767                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2768                         suppressKibitz = TRUE;
2769                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2770                                 && (gameMode == IcsPlayingWhite)) ||
2771                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2772                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2773                             started = STARTED_CHATTER; // own kibitz we simply discard
2774                         else {
2775                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2776                             parse_pos = 0; parse[0] = NULLCHAR;
2777                             savingComment = TRUE;
2778                             suppressKibitz = gameMode != IcsObserving ? 2 :
2779                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2780                         }
2781                         continue;
2782                 } else
2783                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2784                     // suppress the acknowledgements of our own autoKibitz
2785                     char *p;
2786                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2787                     SendToPlayer(star_match[0], strlen(star_match[0]));
2788                     looking_at(buf, &i, "*% "); // eat prompt
2789                     next_out = i;
2790                 }
2791             } // [HGM] kibitz: end of patch
2792
2793 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2794
2795             // [HGM] chat: intercept tells by users for which we have an open chat window
2796             channel = -1;
2797             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2798                                            looking_at(buf, &i, "* whispers:") ||
2799                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2800                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2801                 int p;
2802                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2803                 chattingPartner = -1;
2804
2805                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2806                 for(p=0; p<MAX_CHAT; p++) {
2807                     if(channel == atoi(chatPartner[p])) {
2808                     talker[0] = '['; strcat(talker, "] ");
2809                     chattingPartner = p; break;
2810                     }
2811                 } else
2812                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2813                 for(p=0; p<MAX_CHAT; p++) {
2814                     if(!strcmp("WHISPER", chatPartner[p])) {
2815                         talker[0] = '['; strcat(talker, "] ");
2816                         chattingPartner = p; break;
2817                     }
2818                 }
2819                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2820                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2821                     talker[0] = 0;
2822                     chattingPartner = p; break;
2823                 }
2824                 if(chattingPartner<0) i = oldi; else {
2825                     started = STARTED_COMMENT;
2826                     parse_pos = 0; parse[0] = NULLCHAR;
2827                     savingComment = 3 + chattingPartner; // counts as TRUE
2828                     suppressKibitz = TRUE;
2829                 }
2830             } // [HGM] chat: end of patch
2831
2832             if (appData.zippyTalk || appData.zippyPlay) {
2833                 /* [DM] Backup address for color zippy lines */
2834                 backup = i;
2835 #if ZIPPY
2836        #ifdef WIN32
2837                if (loggedOn == TRUE)
2838                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2839                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2840        #else
2841                 if (ZippyControl(buf, &i) ||
2842                     ZippyConverse(buf, &i) ||
2843                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2844                       loggedOn = TRUE;
2845                       if (!appData.colorize) continue;
2846                 }
2847        #endif
2848 #endif
2849             } // [DM] 'else { ' deleted
2850                 if (
2851                     /* Regular tells and says */
2852                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2853                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2854                     looking_at(buf, &i, "* says: ") ||
2855                     /* Don't color "message" or "messages" output */
2856                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2857                     looking_at(buf, &i, "*. * at *:*: ") ||
2858                     looking_at(buf, &i, "--* (*:*): ") ||
2859                     /* Message notifications (same color as tells) */
2860                     looking_at(buf, &i, "* has left a message ") ||
2861                     looking_at(buf, &i, "* just sent you a message:\n") ||
2862                     /* Whispers and kibitzes */
2863                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2864                     looking_at(buf, &i, "* kibitzes: ") ||
2865                     /* Channel tells */
2866                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2867
2868                   if (tkind == 1 && strchr(star_match[0], ':')) {
2869                       /* Avoid "tells you:" spoofs in channels */
2870                      tkind = 3;
2871                   }
2872                   if (star_match[0][0] == NULLCHAR ||
2873                       strchr(star_match[0], ' ') ||
2874                       (tkind == 3 && strchr(star_match[1], ' '))) {
2875                     /* Reject bogus matches */
2876                     i = oldi;
2877                   } else {
2878                     if (appData.colorize) {
2879                       if (oldi > next_out) {
2880                         SendToPlayer(&buf[next_out], oldi - next_out);
2881                         next_out = oldi;
2882                       }
2883                       switch (tkind) {
2884                       case 1:
2885                         Colorize(ColorTell, FALSE);
2886                         curColor = ColorTell;
2887                         break;
2888                       case 2:
2889                         Colorize(ColorKibitz, FALSE);
2890                         curColor = ColorKibitz;
2891                         break;
2892                       case 3:
2893                         p = strrchr(star_match[1], '(');
2894                         if (p == NULL) {
2895                           p = star_match[1];
2896                         } else {
2897                           p++;
2898                         }
2899                         if (atoi(p) == 1) {
2900                           Colorize(ColorChannel1, FALSE);
2901                           curColor = ColorChannel1;
2902                         } else {
2903                           Colorize(ColorChannel, FALSE);
2904                           curColor = ColorChannel;
2905                         }
2906                         break;
2907                       case 5:
2908                         curColor = ColorNormal;
2909                         break;
2910                       }
2911                     }
2912                     if (started == STARTED_NONE && appData.autoComment &&
2913                         (gameMode == IcsObserving ||
2914                          gameMode == IcsPlayingWhite ||
2915                          gameMode == IcsPlayingBlack)) {
2916                       parse_pos = i - oldi;
2917                       memcpy(parse, &buf[oldi], parse_pos);
2918                       parse[parse_pos] = NULLCHAR;
2919                       started = STARTED_COMMENT;
2920                       savingComment = TRUE;
2921                     } else {
2922                       started = STARTED_CHATTER;
2923                       savingComment = FALSE;
2924                     }
2925                     loggedOn = TRUE;
2926                     continue;
2927                   }
2928                 }
2929
2930                 if (looking_at(buf, &i, "* s-shouts: ") ||
2931                     looking_at(buf, &i, "* c-shouts: ")) {
2932                     if (appData.colorize) {
2933                         if (oldi > next_out) {
2934                             SendToPlayer(&buf[next_out], oldi - next_out);
2935                             next_out = oldi;
2936                         }
2937                         Colorize(ColorSShout, FALSE);
2938                         curColor = ColorSShout;
2939                     }
2940                     loggedOn = TRUE;
2941                     started = STARTED_CHATTER;
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "--->")) {
2946                     loggedOn = TRUE;
2947                     continue;
2948                 }
2949
2950                 if (looking_at(buf, &i, "* shouts: ") ||
2951                     looking_at(buf, &i, "--> ")) {
2952                     if (appData.colorize) {
2953                         if (oldi > next_out) {
2954                             SendToPlayer(&buf[next_out], oldi - next_out);
2955                             next_out = oldi;
2956                         }
2957                         Colorize(ColorShout, FALSE);
2958                         curColor = ColorShout;
2959                     }
2960                     loggedOn = TRUE;
2961                     started = STARTED_CHATTER;
2962                     continue;
2963                 }
2964
2965                 if (looking_at( buf, &i, "Challenge:")) {
2966                     if (appData.colorize) {
2967                         if (oldi > next_out) {
2968                             SendToPlayer(&buf[next_out], oldi - next_out);
2969                             next_out = oldi;
2970                         }
2971                         Colorize(ColorChallenge, FALSE);
2972                         curColor = ColorChallenge;
2973                     }
2974                     loggedOn = TRUE;
2975                     continue;
2976                 }
2977
2978                 if (looking_at(buf, &i, "* offers you") ||
2979                     looking_at(buf, &i, "* offers to be") ||
2980                     looking_at(buf, &i, "* would like to") ||
2981                     looking_at(buf, &i, "* requests to") ||
2982                     looking_at(buf, &i, "Your opponent offers") ||
2983                     looking_at(buf, &i, "Your opponent requests")) {
2984
2985                     if (appData.colorize) {
2986                         if (oldi > next_out) {
2987                             SendToPlayer(&buf[next_out], oldi - next_out);
2988                             next_out = oldi;
2989                         }
2990                         Colorize(ColorRequest, FALSE);
2991                         curColor = ColorRequest;
2992                     }
2993                     continue;
2994                 }
2995
2996                 if (looking_at(buf, &i, "* (*) seeking")) {
2997                     if (appData.colorize) {
2998                         if (oldi > next_out) {
2999                             SendToPlayer(&buf[next_out], oldi - next_out);
3000                             next_out = oldi;
3001                         }
3002                         Colorize(ColorSeek, FALSE);
3003                         curColor = ColorSeek;
3004                     }
3005                     continue;
3006             }
3007
3008             if (looking_at(buf, &i, "\\   ")) {
3009                 if (prevColor != ColorNormal) {
3010                     if (oldi > next_out) {
3011                         SendToPlayer(&buf[next_out], oldi - next_out);
3012                         next_out = oldi;
3013                     }
3014                     Colorize(prevColor, TRUE);
3015                     curColor = prevColor;
3016                 }
3017                 if (savingComment) {
3018                     parse_pos = i - oldi;
3019                     memcpy(parse, &buf[oldi], parse_pos);
3020                     parse[parse_pos] = NULLCHAR;
3021                     started = STARTED_COMMENT;
3022                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3023                         chattingPartner = savingComment - 3; // kludge to remember the box
3024                 } else {
3025                     started = STARTED_CHATTER;
3026                 }
3027                 continue;
3028             }
3029
3030             if (looking_at(buf, &i, "Black Strength :") ||
3031                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3032                 looking_at(buf, &i, "<10>") ||
3033                 looking_at(buf, &i, "#@#")) {
3034                 /* Wrong board style */
3035                 loggedOn = TRUE;
3036                 SendToICS(ics_prefix);
3037                 SendToICS("set style 12\n");
3038                 SendToICS(ics_prefix);
3039                 SendToICS("refresh\n");
3040                 continue;
3041             }
3042
3043             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3044                 ICSInitScript();
3045                 have_sent_ICS_logon = 1;
3046                 continue;
3047             }
3048
3049             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3050                 (looking_at(buf, &i, "\n<12> ") ||
3051                  looking_at(buf, &i, "<12> "))) {
3052                 loggedOn = TRUE;
3053                 if (oldi > next_out) {
3054                     SendToPlayer(&buf[next_out], oldi - next_out);
3055                 }
3056                 next_out = i;
3057                 started = STARTED_BOARD;
3058                 parse_pos = 0;
3059                 continue;
3060             }
3061
3062             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3063                 looking_at(buf, &i, "<b1> ")) {
3064                 if (oldi > next_out) {
3065                     SendToPlayer(&buf[next_out], oldi - next_out);
3066                 }
3067                 next_out = i;
3068                 started = STARTED_HOLDINGS;
3069                 parse_pos = 0;
3070                 continue;
3071             }
3072
3073             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3074                 loggedOn = TRUE;
3075                 /* Header for a move list -- first line */
3076
3077                 switch (ics_getting_history) {
3078                   case H_FALSE:
3079                     switch (gameMode) {
3080                       case IcsIdle:
3081                       case BeginningOfGame:
3082                         /* User typed "moves" or "oldmoves" while we
3083                            were idle.  Pretend we asked for these
3084                            moves and soak them up so user can step
3085                            through them and/or save them.
3086                            */
3087                         Reset(FALSE, TRUE);
3088                         gameMode = IcsObserving;
3089                         ModeHighlight();
3090                         ics_gamenum = -1;
3091                         ics_getting_history = H_GOT_UNREQ_HEADER;
3092                         break;
3093                       case EditGame: /*?*/
3094                       case EditPosition: /*?*/
3095                         /* Should above feature work in these modes too? */
3096                         /* For now it doesn't */
3097                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3098                         break;
3099                       default:
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                     }
3103                     break;
3104                   case H_REQUESTED:
3105                     /* Is this the right one? */
3106                     if (gameInfo.white && gameInfo.black &&
3107                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3108                         strcmp(gameInfo.black, star_match[2]) == 0) {
3109                         /* All is well */
3110                         ics_getting_history = H_GOT_REQ_HEADER;
3111                     }
3112                     break;
3113                   case H_GOT_REQ_HEADER:
3114                   case H_GOT_UNREQ_HEADER:
3115                   case H_GOT_UNWANTED_HEADER:
3116                   case H_GETTING_MOVES:
3117                     /* Should not happen */
3118                     DisplayError(_("Error gathering move list: two headers"), 0);
3119                     ics_getting_history = H_FALSE;
3120                     break;
3121                 }
3122
3123                 /* Save player ratings into gameInfo if needed */
3124                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3125                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3126                     (gameInfo.whiteRating == -1 ||
3127                      gameInfo.blackRating == -1)) {
3128
3129                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3130                     gameInfo.blackRating = string_to_rating(star_match[3]);
3131                     if (appData.debugMode)
3132                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3133                               gameInfo.whiteRating, gameInfo.blackRating);
3134                 }
3135                 continue;
3136             }
3137
3138             if (looking_at(buf, &i,
3139               "* * match, initial time: * minute*, increment: * second")) {
3140                 /* Header for a move list -- second line */
3141                 /* Initial board will follow if this is a wild game */
3142                 if (gameInfo.event != NULL) free(gameInfo.event);
3143                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3144                 gameInfo.event = StrSave(str);
3145                 /* [HGM] we switched variant. Translate boards if needed. */
3146                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3147                 continue;
3148             }
3149
3150             if (looking_at(buf, &i, "Move  ")) {
3151                 /* Beginning of a move list */
3152                 switch (ics_getting_history) {
3153                   case H_FALSE:
3154                     /* Normally should not happen */
3155                     /* Maybe user hit reset while we were parsing */
3156                     break;
3157                   case H_REQUESTED:
3158                     /* Happens if we are ignoring a move list that is not
3159                      * the one we just requested.  Common if the user
3160                      * tries to observe two games without turning off
3161                      * getMoveList */
3162                     break;
3163                   case H_GETTING_MOVES:
3164                     /* Should not happen */
3165                     DisplayError(_("Error gathering move list: nested"), 0);
3166                     ics_getting_history = H_FALSE;
3167                     break;
3168                   case H_GOT_REQ_HEADER:
3169                     ics_getting_history = H_GETTING_MOVES;
3170                     started = STARTED_MOVES;
3171                     parse_pos = 0;
3172                     if (oldi > next_out) {
3173                         SendToPlayer(&buf[next_out], oldi - next_out);
3174                     }
3175                     break;
3176                   case H_GOT_UNREQ_HEADER:
3177                     ics_getting_history = H_GETTING_MOVES;
3178                     started = STARTED_MOVES_NOHIDE;
3179                     parse_pos = 0;
3180                     break;
3181                   case H_GOT_UNWANTED_HEADER:
3182                     ics_getting_history = H_FALSE;
3183                     break;
3184                 }
3185                 continue;
3186             }
3187
3188             if (looking_at(buf, &i, "% ") ||
3189                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3190                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3191                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3192                     soughtPending = FALSE;
3193                     seekGraphUp = TRUE;
3194                     DrawSeekGraph();
3195                 }
3196                 if(suppressKibitz) next_out = i;
3197                 savingComment = FALSE;
3198                 suppressKibitz = 0;
3199                 switch (started) {
3200                   case STARTED_MOVES:
3201                   case STARTED_MOVES_NOHIDE:
3202                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3203                     parse[parse_pos + i - oldi] = NULLCHAR;
3204                     ParseGameHistory(parse);
3205 #if ZIPPY
3206                     if (appData.zippyPlay && first.initDone) {
3207                         FeedMovesToProgram(&first, forwardMostMove);
3208                         if (gameMode == IcsPlayingWhite) {
3209                             if (WhiteOnMove(forwardMostMove)) {
3210                                 if (first.sendTime) {
3211                                   if (first.useColors) {
3212                                     SendToProgram("black\n", &first);
3213                                   }
3214                                   SendTimeRemaining(&first, TRUE);
3215                                 }
3216                                 if (first.useColors) {
3217                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3218                                 }
3219                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3220                                 first.maybeThinking = TRUE;
3221                             } else {
3222                                 if (first.usePlayother) {
3223                                   if (first.sendTime) {
3224                                     SendTimeRemaining(&first, TRUE);
3225                                   }
3226                                   SendToProgram("playother\n", &first);
3227                                   firstMove = FALSE;
3228                                 } else {
3229                                   firstMove = TRUE;
3230                                 }
3231                             }
3232                         } else if (gameMode == IcsPlayingBlack) {
3233                             if (!WhiteOnMove(forwardMostMove)) {
3234                                 if (first.sendTime) {
3235                                   if (first.useColors) {
3236                                     SendToProgram("white\n", &first);
3237                                   }
3238                                   SendTimeRemaining(&first, FALSE);
3239                                 }
3240                                 if (first.useColors) {
3241                                   SendToProgram("black\n", &first);
3242                                 }
3243                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3244                                 first.maybeThinking = TRUE;
3245                             } else {
3246                                 if (first.usePlayother) {
3247                                   if (first.sendTime) {
3248                                     SendTimeRemaining(&first, FALSE);
3249                                   }
3250                                   SendToProgram("playother\n", &first);
3251                                   firstMove = FALSE;
3252                                 } else {
3253                                   firstMove = TRUE;
3254                                 }
3255                             }
3256                         }
3257                     }
3258 #endif
3259                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3260                         /* Moves came from oldmoves or moves command
3261                            while we weren't doing anything else.
3262                            */
3263                         currentMove = forwardMostMove;
3264                         ClearHighlights();/*!!could figure this out*/
3265                         flipView = appData.flipView;
3266                         DrawPosition(TRUE, boards[currentMove]);
3267                         DisplayBothClocks();
3268                         sprintf(str, "%s vs. %s",
3269                                 gameInfo.white, gameInfo.black);
3270                         DisplayTitle(str);
3271                         gameMode = IcsIdle;
3272                     } else {
3273                         /* Moves were history of an active game */
3274                         if (gameInfo.resultDetails != NULL) {
3275                             free(gameInfo.resultDetails);
3276                             gameInfo.resultDetails = NULL;
3277                         }
3278                     }
3279                     HistorySet(parseList, backwardMostMove,
3280                                forwardMostMove, currentMove-1);
3281                     DisplayMove(currentMove - 1);
3282                     if (started == STARTED_MOVES) next_out = i;
3283                     started = STARTED_NONE;
3284                     ics_getting_history = H_FALSE;
3285                     break;
3286
3287                   case STARTED_OBSERVE:
3288                     started = STARTED_NONE;
3289                     SendToICS(ics_prefix);
3290                     SendToICS("refresh\n");
3291                     break;
3292
3293                   default:
3294                     break;
3295                 }
3296                 if(bookHit) { // [HGM] book: simulate book reply
3297                     static char bookMove[MSG_SIZ]; // a bit generous?
3298
3299                     programStats.nodes = programStats.depth = programStats.time =
3300                     programStats.score = programStats.got_only_move = 0;
3301                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3302
3303                     strcpy(bookMove, "move ");
3304                     strcat(bookMove, bookHit);
3305                     HandleMachineMove(bookMove, &first);
3306                 }
3307                 continue;
3308             }
3309
3310             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3311                  started == STARTED_HOLDINGS ||
3312                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3313                 /* Accumulate characters in move list or board */
3314                 parse[parse_pos++] = buf[i];
3315             }
3316
3317             /* Start of game messages.  Mostly we detect start of game
3318                when the first board image arrives.  On some versions
3319                of the ICS, though, we need to do a "refresh" after starting
3320                to observe in order to get the current board right away. */
3321             if (looking_at(buf, &i, "Adding game * to observation list")) {
3322                 started = STARTED_OBSERVE;
3323                 continue;
3324             }
3325
3326             /* Handle auto-observe */
3327             if (appData.autoObserve &&
3328                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3329                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3330                 char *player;
3331                 /* Choose the player that was highlighted, if any. */
3332                 if (star_match[0][0] == '\033' ||
3333                     star_match[1][0] != '\033') {
3334                     player = star_match[0];
3335                 } else {
3336                     player = star_match[2];
3337                 }
3338                 sprintf(str, "%sobserve %s\n",
3339                         ics_prefix, StripHighlightAndTitle(player));
3340                 SendToICS(str);
3341
3342                 /* Save ratings from notify string */
3343                 strcpy(player1Name, star_match[0]);
3344                 player1Rating = string_to_rating(star_match[1]);
3345                 strcpy(player2Name, star_match[2]);
3346                 player2Rating = string_to_rating(star_match[3]);
3347
3348                 if (appData.debugMode)
3349                   fprintf(debugFP,
3350                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3351                           player1Name, player1Rating,
3352                           player2Name, player2Rating);
3353
3354                 continue;
3355             }
3356
3357             /* Deal with automatic examine mode after a game,
3358                and with IcsObserving -> IcsExamining transition */
3359             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3360                 looking_at(buf, &i, "has made you an examiner of game *")) {
3361
3362                 int gamenum = atoi(star_match[0]);
3363                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3364                     gamenum == ics_gamenum) {
3365                     /* We were already playing or observing this game;
3366                        no need to refetch history */
3367                     gameMode = IcsExamining;
3368                     if (pausing) {
3369                         pauseExamForwardMostMove = forwardMostMove;
3370                     } else if (currentMove < forwardMostMove) {
3371                         ForwardInner(forwardMostMove);
3372                     }
3373                 } else {
3374                     /* I don't think this case really can happen */
3375                     SendToICS(ics_prefix);
3376                     SendToICS("refresh\n");
3377                 }
3378                 continue;
3379             }
3380
3381             /* Error messages */
3382 //          if (ics_user_moved) {
3383             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3384                 if (looking_at(buf, &i, "Illegal move") ||
3385                     looking_at(buf, &i, "Not a legal move") ||
3386                     looking_at(buf, &i, "Your king is in check") ||
3387                     looking_at(buf, &i, "It isn't your turn") ||
3388                     looking_at(buf, &i, "It is not your move")) {
3389                     /* Illegal move */
3390                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3391                         currentMove = --forwardMostMove;
3392                         DisplayMove(currentMove - 1); /* before DMError */
3393                         DrawPosition(FALSE, boards[currentMove]);
3394                         SwitchClocks();
3395                         DisplayBothClocks();
3396                     }
3397                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3398                     ics_user_moved = 0;
3399                     continue;
3400                 }
3401             }
3402
3403             if (looking_at(buf, &i, "still have time") ||
3404                 looking_at(buf, &i, "not out of time") ||
3405                 looking_at(buf, &i, "either player is out of time") ||
3406                 looking_at(buf, &i, "has timeseal; checking")) {
3407                 /* We must have called his flag a little too soon */
3408                 whiteFlag = blackFlag = FALSE;
3409                 continue;
3410             }
3411
3412             if (looking_at(buf, &i, "added * seconds to") ||
3413                 looking_at(buf, &i, "seconds were added to")) {
3414                 /* Update the clocks */
3415                 SendToICS(ics_prefix);
3416                 SendToICS("refresh\n");
3417                 continue;
3418             }
3419
3420             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3421                 ics_clock_paused = TRUE;
3422                 StopClocks();
3423                 continue;
3424             }
3425
3426             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3427                 ics_clock_paused = FALSE;
3428                 StartClocks();
3429                 continue;
3430             }
3431
3432             /* Grab player ratings from the Creating: message.
3433                Note we have to check for the special case when
3434                the ICS inserts things like [white] or [black]. */
3435             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3436                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3437                 /* star_matches:
3438                    0    player 1 name (not necessarily white)
3439                    1    player 1 rating
3440                    2    empty, white, or black (IGNORED)
3441                    3    player 2 name (not necessarily black)
3442                    4    player 2 rating
3443
3444                    The names/ratings are sorted out when the game
3445                    actually starts (below).
3446                 */
3447                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3448                 player1Rating = string_to_rating(star_match[1]);
3449                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3450                 player2Rating = string_to_rating(star_match[4]);
3451
3452                 if (appData.debugMode)
3453                   fprintf(debugFP,
3454                           "Ratings from 'Creating:' %s %d, %s %d\n",
3455                           player1Name, player1Rating,
3456                           player2Name, player2Rating);
3457
3458                 continue;
3459             }
3460
3461             /* Improved generic start/end-of-game messages */
3462             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3463                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3464                 /* If tkind == 0: */
3465                 /* star_match[0] is the game number */
3466                 /*           [1] is the white player's name */
3467                 /*           [2] is the black player's name */
3468                 /* For end-of-game: */
3469                 /*           [3] is the reason for the game end */
3470                 /*           [4] is a PGN end game-token, preceded by " " */
3471                 /* For start-of-game: */
3472                 /*           [3] begins with "Creating" or "Continuing" */
3473                 /*           [4] is " *" or empty (don't care). */
3474                 int gamenum = atoi(star_match[0]);
3475                 char *whitename, *blackname, *why, *endtoken;
3476                 ChessMove endtype = (ChessMove) 0;
3477
3478                 if (tkind == 0) {
3479                   whitename = star_match[1];
3480                   blackname = star_match[2];
3481                   why = star_match[3];
3482                   endtoken = star_match[4];
3483                 } else {
3484                   whitename = star_match[1];
3485                   blackname = star_match[3];
3486                   why = star_match[5];
3487                   endtoken = star_match[6];
3488                 }
3489
3490                 /* Game start messages */
3491                 if (strncmp(why, "Creating ", 9) == 0 ||
3492                     strncmp(why, "Continuing ", 11) == 0) {
3493                     gs_gamenum = gamenum;
3494                     strcpy(gs_kind, strchr(why, ' ') + 1);
3495                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3496 #if ZIPPY
3497                     if (appData.zippyPlay) {
3498                         ZippyGameStart(whitename, blackname);
3499                     }
3500 #endif /*ZIPPY*/
3501                     continue;
3502                 }
3503
3504                 /* Game end messages */
3505                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3506                     ics_gamenum != gamenum) {
3507                     continue;
3508                 }
3509                 while (endtoken[0] == ' ') endtoken++;
3510                 switch (endtoken[0]) {
3511                   case '*':
3512                   default:
3513                     endtype = GameUnfinished;
3514                     break;
3515                   case '0':
3516                     endtype = BlackWins;
3517                     break;
3518                   case '1':
3519                     if (endtoken[1] == '/')
3520                       endtype = GameIsDrawn;
3521                     else
3522                       endtype = WhiteWins;
3523                     break;
3524                 }
3525                 GameEnds(endtype, why, GE_ICS);
3526 #if ZIPPY
3527                 if (appData.zippyPlay && first.initDone) {
3528                     ZippyGameEnd(endtype, why);
3529                     if (first.pr == NULL) {
3530                       /* Start the next process early so that we'll
3531                          be ready for the next challenge */
3532                       StartChessProgram(&first);
3533                     }
3534                     /* Send "new" early, in case this command takes
3535                        a long time to finish, so that we'll be ready
3536                        for the next challenge. */
3537                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3538                     Reset(TRUE, TRUE);
3539                 }
3540 #endif /*ZIPPY*/
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "Removing game * from observation") ||
3545                 looking_at(buf, &i, "no longer observing game *") ||
3546                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3547                 if (gameMode == IcsObserving &&
3548                     atoi(star_match[0]) == ics_gamenum)
3549                   {
3550                       /* icsEngineAnalyze */
3551                       if (appData.icsEngineAnalyze) {
3552                             ExitAnalyzeMode();
3553                             ModeHighlight();
3554                       }
3555                       StopClocks();
3556                       gameMode = IcsIdle;
3557                       ics_gamenum = -1;
3558                       ics_user_moved = FALSE;
3559                   }
3560                 continue;
3561             }
3562
3563             if (looking_at(buf, &i, "no longer examining game *")) {
3564                 if (gameMode == IcsExamining &&
3565                     atoi(star_match[0]) == ics_gamenum)
3566                   {
3567                       gameMode = IcsIdle;
3568                       ics_gamenum = -1;
3569                       ics_user_moved = FALSE;
3570                   }
3571                 continue;
3572             }
3573
3574             /* Advance leftover_start past any newlines we find,
3575                so only partial lines can get reparsed */
3576             if (looking_at(buf, &i, "\n")) {
3577                 prevColor = curColor;
3578                 if (curColor != ColorNormal) {
3579                     if (oldi > next_out) {
3580                         SendToPlayer(&buf[next_out], oldi - next_out);
3581                         next_out = oldi;
3582                     }
3583                     Colorize(ColorNormal, FALSE);
3584                     curColor = ColorNormal;
3585                 }
3586                 if (started == STARTED_BOARD) {
3587                     started = STARTED_NONE;
3588                     parse[parse_pos] = NULLCHAR;
3589                     ParseBoard12(parse);
3590                     ics_user_moved = 0;
3591
3592                     /* Send premove here */
3593                     if (appData.premove) {
3594                       char str[MSG_SIZ];
3595                       if (currentMove == 0 &&
3596                           gameMode == IcsPlayingWhite &&
3597                           appData.premoveWhite) {
3598                         sprintf(str, "%s\n", appData.premoveWhiteText);
3599                         if (appData.debugMode)
3600                           fprintf(debugFP, "Sending premove:\n");
3601                         SendToICS(str);
3602                       } else if (currentMove == 1 &&
3603                                  gameMode == IcsPlayingBlack &&
3604                                  appData.premoveBlack) {
3605                         sprintf(str, "%s\n", appData.premoveBlackText);
3606                         if (appData.debugMode)
3607                           fprintf(debugFP, "Sending premove:\n");
3608                         SendToICS(str);
3609                       } else if (gotPremove) {
3610                         gotPremove = 0;
3611                         ClearPremoveHighlights();
3612                         if (appData.debugMode)
3613                           fprintf(debugFP, "Sending premove:\n");
3614                           UserMoveEvent(premoveFromX, premoveFromY,
3615                                         premoveToX, premoveToY,
3616                                         premovePromoChar);
3617                       }
3618                     }
3619
3620                     /* Usually suppress following prompt */
3621                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3622                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3623                         if (looking_at(buf, &i, "*% ")) {
3624                             savingComment = FALSE;
3625                             suppressKibitz = 0;
3626                         }
3627                     }
3628                     next_out = i;
3629                 } else if (started == STARTED_HOLDINGS) {
3630                     int gamenum;
3631                     char new_piece[MSG_SIZ];
3632                     started = STARTED_NONE;
3633                     parse[parse_pos] = NULLCHAR;
3634                     if (appData.debugMode)
3635                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3636                                                         parse, currentMove);
3637                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3638                         gamenum == ics_gamenum) {
3639                         if (gameInfo.variant == VariantNormal) {
3640                           /* [HGM] We seem to switch variant during a game!
3641                            * Presumably no holdings were displayed, so we have
3642                            * to move the position two files to the right to
3643                            * create room for them!
3644                            */
3645                           VariantClass newVariant;
3646                           switch(gameInfo.boardWidth) { // base guess on board width
3647                                 case 9:  newVariant = VariantShogi; break;
3648                                 case 10: newVariant = VariantGreat; break;
3649                                 default: newVariant = VariantCrazyhouse; break;
3650                           }
3651                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3652                           /* Get a move list just to see the header, which
3653                              will tell us whether this is really bug or zh */
3654                           if (ics_getting_history == H_FALSE) {
3655                             ics_getting_history = H_REQUESTED;
3656                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3657                             SendToICS(str);
3658                           }
3659                         }
3660                         new_piece[0] = NULLCHAR;
3661                         sscanf(parse, "game %d white [%s black [%s <- %s",
3662                                &gamenum, white_holding, black_holding,
3663                                new_piece);
3664                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3665                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3666                         /* [HGM] copy holdings to board holdings area */
3667                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3668                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3669                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3670 #if ZIPPY
3671                         if (appData.zippyPlay && first.initDone) {
3672                             ZippyHoldings(white_holding, black_holding,
3673                                           new_piece);
3674                         }
3675 #endif /*ZIPPY*/
3676                         if (tinyLayout || smallLayout) {
3677                             char wh[16], bh[16];
3678                             PackHolding(wh, white_holding);
3679                             PackHolding(bh, black_holding);
3680                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3681                                     gameInfo.white, gameInfo.black);
3682                         } else {
3683                             sprintf(str, "%s [%s] vs. %s [%s]",
3684                                     gameInfo.white, white_holding,
3685                                     gameInfo.black, black_holding);
3686                         }
3687
3688                         DrawPosition(FALSE, boards[currentMove]);
3689                         DisplayTitle(str);
3690                     }
3691                     /* Suppress following prompt */
3692                     if (looking_at(buf, &i, "*% ")) {
3693                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3694                         savingComment = FALSE;
3695                         suppressKibitz = 0;
3696                     }
3697                     next_out = i;
3698                 }
3699                 continue;
3700             }
3701
3702             i++;                /* skip unparsed character and loop back */
3703         }
3704         
3705         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3706 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3707 //          SendToPlayer(&buf[next_out], i - next_out);
3708             started != STARTED_HOLDINGS && leftover_start > next_out) {
3709             SendToPlayer(&buf[next_out], leftover_start - next_out);
3710             next_out = i;
3711         }
3712
3713         leftover_len = buf_len - leftover_start;
3714         /* if buffer ends with something we couldn't parse,
3715            reparse it after appending the next read */
3716
3717     } else if (count == 0) {
3718         RemoveInputSource(isr);
3719         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3720     } else {
3721         DisplayFatalError(_("Error reading from ICS"), error, 1);
3722     }
3723 }
3724
3725
3726 /* Board style 12 looks like this:
3727
3728    <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
3729
3730  * The "<12> " is stripped before it gets to this routine.  The two
3731  * trailing 0's (flip state and clock ticking) are later addition, and
3732  * some chess servers may not have them, or may have only the first.
3733  * Additional trailing fields may be added in the future.
3734  */
3735
3736 #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"
3737
3738 #define RELATION_OBSERVING_PLAYED    0
3739 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3740 #define RELATION_PLAYING_MYMOVE      1
3741 #define RELATION_PLAYING_NOTMYMOVE  -1
3742 #define RELATION_EXAMINING           2
3743 #define RELATION_ISOLATED_BOARD     -3
3744 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3745
3746 void
3747 ParseBoard12(string)
3748      char *string;
3749 {
3750     GameMode newGameMode;
3751     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3752     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3753     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3754     char to_play, board_chars[200];
3755     char move_str[500], str[500], elapsed_time[500];
3756     char black[32], white[32];
3757     Board board;
3758     int prevMove = currentMove;
3759     int ticking = 2;
3760     ChessMove moveType;
3761     int fromX, fromY, toX, toY;
3762     char promoChar;
3763     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3764     char *bookHit = NULL; // [HGM] book
3765     Boolean weird = FALSE, reqFlag = FALSE;
3766
3767     fromX = fromY = toX = toY = -1;
3768
3769     newGame = FALSE;
3770
3771     if (appData.debugMode)
3772       fprintf(debugFP, _("Parsing board: %s\n"), string);
3773
3774     move_str[0] = NULLCHAR;
3775     elapsed_time[0] = NULLCHAR;
3776     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3777         int  i = 0, j;
3778         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3779             if(string[i] == ' ') { ranks++; files = 0; }
3780             else files++;
3781             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3782             i++;
3783         }
3784         for(j = 0; j <i; j++) board_chars[j] = string[j];
3785         board_chars[i] = '\0';
3786         string += i + 1;
3787     }
3788     n = sscanf(string, PATTERN, &to_play, &double_push,
3789                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3790                &gamenum, white, black, &relation, &basetime, &increment,
3791                &white_stren, &black_stren, &white_time, &black_time,
3792                &moveNum, str, elapsed_time, move_str, &ics_flip,
3793                &ticking);
3794
3795     if (n < 21) {
3796         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3797         DisplayError(str, 0);
3798         return;
3799     }
3800
3801     /* Convert the move number to internal form */
3802     moveNum = (moveNum - 1) * 2;
3803     if (to_play == 'B') moveNum++;
3804     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3805       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3806                         0, 1);
3807       return;
3808     }
3809
3810     switch (relation) {
3811       case RELATION_OBSERVING_PLAYED:
3812       case RELATION_OBSERVING_STATIC:
3813         if (gamenum == -1) {
3814             /* Old ICC buglet */
3815             relation = RELATION_OBSERVING_STATIC;
3816         }
3817         newGameMode = IcsObserving;
3818         break;
3819       case RELATION_PLAYING_MYMOVE:
3820       case RELATION_PLAYING_NOTMYMOVE:
3821         newGameMode =
3822           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3823             IcsPlayingWhite : IcsPlayingBlack;
3824         break;
3825       case RELATION_EXAMINING:
3826         newGameMode = IcsExamining;
3827         break;
3828       case RELATION_ISOLATED_BOARD:
3829       default:
3830         /* Just display this board.  If user was doing something else,
3831            we will forget about it until the next board comes. */
3832         newGameMode = IcsIdle;
3833         break;
3834       case RELATION_STARTING_POSITION:
3835         newGameMode = gameMode;
3836         break;
3837     }
3838
3839     /* Modify behavior for initial board display on move listing
3840        of wild games.
3841        */
3842     switch (ics_getting_history) {
3843       case H_FALSE:
3844       case H_REQUESTED:
3845         break;
3846       case H_GOT_REQ_HEADER:
3847       case H_GOT_UNREQ_HEADER:
3848         /* This is the initial position of the current game */
3849         gamenum = ics_gamenum;
3850         moveNum = 0;            /* old ICS bug workaround */
3851         if (to_play == 'B') {
3852           startedFromSetupPosition = TRUE;
3853           blackPlaysFirst = TRUE;
3854           moveNum = 1;
3855           if (forwardMostMove == 0) forwardMostMove = 1;
3856           if (backwardMostMove == 0) backwardMostMove = 1;
3857           if (currentMove == 0) currentMove = 1;
3858         }
3859         newGameMode = gameMode;
3860         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3861         break;
3862       case H_GOT_UNWANTED_HEADER:
3863         /* This is an initial board that we don't want */
3864         return;
3865       case H_GETTING_MOVES:
3866         /* Should not happen */
3867         DisplayError(_("Error gathering move list: extra board"), 0);
3868         ics_getting_history = H_FALSE;
3869         return;
3870     }
3871
3872    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3873                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3874      /* [HGM] We seem to have switched variant unexpectedly
3875       * Try to guess new variant from board size
3876       */
3877           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3878           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3879           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3880           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3881           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3882           if(!weird) newVariant = VariantNormal;
3883           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3884           /* Get a move list just to see the header, which
3885              will tell us whether this is really bug or zh */
3886           if (ics_getting_history == H_FALSE) {
3887             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3888             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3889             SendToICS(str);
3890           }
3891     }
3892     
3893     /* Take action if this is the first board of a new game, or of a
3894        different game than is currently being displayed.  */
3895     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3896         relation == RELATION_ISOLATED_BOARD) {
3897
3898         /* Forget the old game and get the history (if any) of the new one */
3899         if (gameMode != BeginningOfGame) {
3900           Reset(TRUE, TRUE);
3901         }
3902         newGame = TRUE;
3903         if (appData.autoRaiseBoard) BoardToTop();
3904         prevMove = -3;
3905         if (gamenum == -1) {
3906             newGameMode = IcsIdle;
3907         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3908                    appData.getMoveList && !reqFlag) {
3909             /* Need to get game history */
3910             ics_getting_history = H_REQUESTED;
3911             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3912             SendToICS(str);
3913         }
3914
3915         /* Initially flip the board to have black on the bottom if playing
3916            black or if the ICS flip flag is set, but let the user change
3917            it with the Flip View button. */
3918         flipView = appData.autoFlipView ?
3919           (newGameMode == IcsPlayingBlack) || ics_flip :
3920           appData.flipView;
3921
3922         /* Done with values from previous mode; copy in new ones */
3923         gameMode = newGameMode;
3924         ModeHighlight();
3925         ics_gamenum = gamenum;
3926         if (gamenum == gs_gamenum) {
3927             int klen = strlen(gs_kind);
3928             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3929             sprintf(str, "ICS %s", gs_kind);
3930             gameInfo.event = StrSave(str);
3931         } else {
3932             gameInfo.event = StrSave("ICS game");
3933         }
3934         gameInfo.site = StrSave(appData.icsHost);
3935         gameInfo.date = PGNDate();
3936         gameInfo.round = StrSave("-");
3937         gameInfo.white = StrSave(white);
3938         gameInfo.black = StrSave(black);
3939         timeControl = basetime * 60 * 1000;
3940         timeControl_2 = 0;
3941         timeIncrement = increment * 1000;
3942         movesPerSession = 0;
3943         gameInfo.timeControl = TimeControlTagValue();
3944         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3945   if (appData.debugMode) {
3946     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3947     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3948     setbuf(debugFP, NULL);
3949   }
3950
3951         gameInfo.outOfBook = NULL;
3952
3953         /* Do we have the ratings? */
3954         if (strcmp(player1Name, white) == 0 &&
3955             strcmp(player2Name, black) == 0) {
3956             if (appData.debugMode)
3957               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3958                       player1Rating, player2Rating);
3959             gameInfo.whiteRating = player1Rating;
3960             gameInfo.blackRating = player2Rating;
3961         } else if (strcmp(player2Name, white) == 0 &&
3962                    strcmp(player1Name, black) == 0) {
3963             if (appData.debugMode)
3964               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3965                       player2Rating, player1Rating);
3966             gameInfo.whiteRating = player2Rating;
3967             gameInfo.blackRating = player1Rating;
3968         }
3969         player1Name[0] = player2Name[0] = NULLCHAR;
3970
3971         /* Silence shouts if requested */
3972         if (appData.quietPlay &&
3973             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3974             SendToICS(ics_prefix);
3975             SendToICS("set shout 0\n");
3976         }
3977     }
3978
3979     /* Deal with midgame name changes */
3980     if (!newGame) {
3981         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3982             if (gameInfo.white) free(gameInfo.white);
3983             gameInfo.white = StrSave(white);
3984         }
3985         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3986             if (gameInfo.black) free(gameInfo.black);
3987             gameInfo.black = StrSave(black);
3988         }
3989     }
3990
3991     /* Throw away game result if anything actually changes in examine mode */
3992     if (gameMode == IcsExamining && !newGame) {
3993         gameInfo.result = GameUnfinished;
3994         if (gameInfo.resultDetails != NULL) {
3995             free(gameInfo.resultDetails);
3996             gameInfo.resultDetails = NULL;
3997         }
3998     }
3999
4000     /* In pausing && IcsExamining mode, we ignore boards coming
4001        in if they are in a different variation than we are. */
4002     if (pauseExamInvalid) return;
4003     if (pausing && gameMode == IcsExamining) {
4004         if (moveNum <= pauseExamForwardMostMove) {
4005             pauseExamInvalid = TRUE;
4006             forwardMostMove = pauseExamForwardMostMove;
4007             return;
4008         }
4009     }
4010
4011   if (appData.debugMode) {
4012     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4013   }
4014     /* Parse the board */
4015     for (k = 0; k < ranks; k++) {
4016       for (j = 0; j < files; j++)
4017         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4018       if(gameInfo.holdingsWidth > 1) {
4019            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4020            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4021       }
4022     }
4023     CopyBoard(boards[moveNum], board);
4024     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4025     if (moveNum == 0) {
4026         startedFromSetupPosition =
4027           !CompareBoards(board, initialPosition);
4028         if(startedFromSetupPosition)
4029             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4030     }
4031
4032     /* [HGM] Set castling rights. Take the outermost Rooks,
4033        to make it also work for FRC opening positions. Note that board12
4034        is really defective for later FRC positions, as it has no way to
4035        indicate which Rook can castle if they are on the same side of King.
4036        For the initial position we grant rights to the outermost Rooks,
4037        and remember thos rights, and we then copy them on positions
4038        later in an FRC game. This means WB might not recognize castlings with
4039        Rooks that have moved back to their original position as illegal,
4040        but in ICS mode that is not its job anyway.
4041     */
4042     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4043     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4044
4045         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4046             if(board[0][i] == WhiteRook) j = i;
4047         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4048         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4049             if(board[0][i] == WhiteRook) j = i;
4050         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4051         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4052             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4053         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4054         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4055             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4056         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4057
4058         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4059         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4060             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4061         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4062             if(board[BOARD_HEIGHT-1][k] == bKing)
4063                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4064         if(gameInfo.variant == VariantTwoKings) {
4065             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4066             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4067             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4068         }
4069     } else { int r;
4070         r = boards[moveNum][CASTLING][0] = initialRights[0];
4071         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4072         r = boards[moveNum][CASTLING][1] = initialRights[1];
4073         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4074         r = boards[moveNum][CASTLING][3] = initialRights[3];
4075         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4076         r = boards[moveNum][CASTLING][4] = initialRights[4];
4077         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4078         /* wildcastle kludge: always assume King has rights */
4079         r = boards[moveNum][CASTLING][2] = initialRights[2];
4080         r = boards[moveNum][CASTLING][5] = initialRights[5];
4081     }
4082     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4083     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4084
4085
4086     if (ics_getting_history == H_GOT_REQ_HEADER ||
4087         ics_getting_history == H_GOT_UNREQ_HEADER) {
4088         /* This was an initial position from a move list, not
4089            the current position */
4090         return;
4091     }
4092
4093     /* Update currentMove and known move number limits */
4094     newMove = newGame || moveNum > forwardMostMove;
4095
4096     if (newGame) {
4097         forwardMostMove = backwardMostMove = currentMove = moveNum;
4098         if (gameMode == IcsExamining && moveNum == 0) {
4099           /* Workaround for ICS limitation: we are not told the wild
4100              type when starting to examine a game.  But if we ask for
4101              the move list, the move list header will tell us */
4102             ics_getting_history = H_REQUESTED;
4103             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4104             SendToICS(str);
4105         }
4106     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4107                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4108 #if ZIPPY
4109         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4110         /* [HGM] applied this also to an engine that is silently watching        */
4111         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4112             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4113             gameInfo.variant == currentlyInitializedVariant) {
4114           takeback = forwardMostMove - moveNum;
4115           for (i = 0; i < takeback; i++) {
4116             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4117             SendToProgram("undo\n", &first);
4118           }
4119         }
4120 #endif
4121
4122         forwardMostMove = moveNum;
4123         if (!pausing || currentMove > forwardMostMove)
4124           currentMove = forwardMostMove;
4125     } else {
4126         /* New part of history that is not contiguous with old part */
4127         if (pausing && gameMode == IcsExamining) {
4128             pauseExamInvalid = TRUE;
4129             forwardMostMove = pauseExamForwardMostMove;
4130             return;
4131         }
4132         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4133 #if ZIPPY
4134             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4135                 // [HGM] when we will receive the move list we now request, it will be
4136                 // fed to the engine from the first move on. So if the engine is not
4137                 // in the initial position now, bring it there.
4138                 InitChessProgram(&first, 0);
4139             }
4140 #endif
4141             ics_getting_history = H_REQUESTED;
4142             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4143             SendToICS(str);
4144         }
4145         forwardMostMove = backwardMostMove = currentMove = moveNum;
4146     }
4147
4148     /* Update the clocks */
4149     if (strchr(elapsed_time, '.')) {
4150       /* Time is in ms */
4151       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4152       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4153     } else {
4154       /* Time is in seconds */
4155       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4156       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4157     }
4158
4159
4160 #if ZIPPY
4161     if (appData.zippyPlay && newGame &&
4162         gameMode != IcsObserving && gameMode != IcsIdle &&
4163         gameMode != IcsExamining)
4164       ZippyFirstBoard(moveNum, basetime, increment);
4165 #endif
4166
4167     /* Put the move on the move list, first converting
4168        to canonical algebraic form. */
4169     if (moveNum > 0) {
4170   if (appData.debugMode) {
4171     if (appData.debugMode) { int f = forwardMostMove;
4172         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4173                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4174                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4175     }
4176     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4177     fprintf(debugFP, "moveNum = %d\n", moveNum);
4178     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4179     setbuf(debugFP, NULL);
4180   }
4181         if (moveNum <= backwardMostMove) {
4182             /* We don't know what the board looked like before
4183                this move.  Punt. */
4184             strcpy(parseList[moveNum - 1], move_str);
4185             strcat(parseList[moveNum - 1], " ");
4186             strcat(parseList[moveNum - 1], elapsed_time);
4187             moveList[moveNum - 1][0] = NULLCHAR;
4188         } else if (strcmp(move_str, "none") == 0) {
4189             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4190             /* Again, we don't know what the board looked like;
4191                this is really the start of the game. */
4192             parseList[moveNum - 1][0] = NULLCHAR;
4193             moveList[moveNum - 1][0] = NULLCHAR;
4194             backwardMostMove = moveNum;
4195             startedFromSetupPosition = TRUE;
4196             fromX = fromY = toX = toY = -1;
4197         } else {
4198           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4199           //                 So we parse the long-algebraic move string in stead of the SAN move
4200           int valid; char buf[MSG_SIZ], *prom;
4201
4202           // str looks something like "Q/a1-a2"; kill the slash
4203           if(str[1] == '/')
4204                 sprintf(buf, "%c%s", str[0], str+2);
4205           else  strcpy(buf, str); // might be castling
4206           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4207                 strcat(buf, prom); // long move lacks promo specification!
4208           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4209                 if(appData.debugMode)
4210                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4211                 strcpy(move_str, buf);
4212           }
4213           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4214                                 &fromX, &fromY, &toX, &toY, &promoChar)
4215                || ParseOneMove(buf, moveNum - 1, &moveType,
4216                                 &fromX, &fromY, &toX, &toY, &promoChar);
4217           // end of long SAN patch
4218           if (valid) {
4219             (void) CoordsToAlgebraic(boards[moveNum - 1],
4220                                      PosFlags(moveNum - 1),
4221                                      fromY, fromX, toY, toX, promoChar,
4222                                      parseList[moveNum-1]);
4223             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4224               case MT_NONE:
4225               case MT_STALEMATE:
4226               default:
4227                 break;
4228               case MT_CHECK:
4229                 if(gameInfo.variant != VariantShogi)
4230                     strcat(parseList[moveNum - 1], "+");
4231                 break;
4232               case MT_CHECKMATE:
4233               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4234                 strcat(parseList[moveNum - 1], "#");
4235                 break;
4236             }
4237             strcat(parseList[moveNum - 1], " ");
4238             strcat(parseList[moveNum - 1], elapsed_time);
4239             /* currentMoveString is set as a side-effect of ParseOneMove */
4240             strcpy(moveList[moveNum - 1], currentMoveString);
4241             strcat(moveList[moveNum - 1], "\n");
4242           } else {
4243             /* Move from ICS was illegal!?  Punt. */
4244   if (appData.debugMode) {
4245     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4247   }
4248             strcpy(parseList[moveNum - 1], move_str);
4249             strcat(parseList[moveNum - 1], " ");
4250             strcat(parseList[moveNum - 1], elapsed_time);
4251             moveList[moveNum - 1][0] = NULLCHAR;
4252             fromX = fromY = toX = toY = -1;
4253           }
4254         }
4255   if (appData.debugMode) {
4256     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4257     setbuf(debugFP, NULL);
4258   }
4259
4260 #if ZIPPY
4261         /* Send move to chess program (BEFORE animating it). */
4262         if (appData.zippyPlay && !newGame && newMove &&
4263            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4264
4265             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4266                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4267                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4268                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4269                             move_str);
4270                     DisplayError(str, 0);
4271                 } else {
4272                     if (first.sendTime) {
4273                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4274                     }
4275                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4276                     if (firstMove && !bookHit) {
4277                         firstMove = FALSE;
4278                         if (first.useColors) {
4279                           SendToProgram(gameMode == IcsPlayingWhite ?
4280                                         "white\ngo\n" :
4281                                         "black\ngo\n", &first);
4282                         } else {
4283                           SendToProgram("go\n", &first);
4284                         }
4285                         first.maybeThinking = TRUE;
4286                     }
4287                 }
4288             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4289               if (moveList[moveNum - 1][0] == NULLCHAR) {
4290                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4291                 DisplayError(str, 0);
4292               } else {
4293                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4294                 SendMoveToProgram(moveNum - 1, &first);
4295               }
4296             }
4297         }
4298 #endif
4299     }
4300
4301     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4302         /* If move comes from a remote source, animate it.  If it
4303            isn't remote, it will have already been animated. */
4304         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4305             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4306         }
4307         if (!pausing && appData.highlightLastMove) {
4308             SetHighlights(fromX, fromY, toX, toY);
4309         }
4310     }
4311
4312     /* Start the clocks */
4313     whiteFlag = blackFlag = FALSE;
4314     appData.clockMode = !(basetime == 0 && increment == 0);
4315     if (ticking == 0) {
4316       ics_clock_paused = TRUE;
4317       StopClocks();
4318     } else if (ticking == 1) {
4319       ics_clock_paused = FALSE;
4320     }
4321     if (gameMode == IcsIdle ||
4322         relation == RELATION_OBSERVING_STATIC ||
4323         relation == RELATION_EXAMINING ||
4324         ics_clock_paused)
4325       DisplayBothClocks();
4326     else
4327       StartClocks();
4328
4329     /* Display opponents and material strengths */
4330     if (gameInfo.variant != VariantBughouse &&
4331         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4332         if (tinyLayout || smallLayout) {
4333             if(gameInfo.variant == VariantNormal)
4334                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4335                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4336                     basetime, increment);
4337             else
4338                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4339                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4340                     basetime, increment, (int) gameInfo.variant);
4341         } else {
4342             if(gameInfo.variant == VariantNormal)
4343                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4344                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4345                     basetime, increment);
4346             else
4347                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4348                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4349                     basetime, increment, VariantName(gameInfo.variant));
4350         }
4351         DisplayTitle(str);
4352   if (appData.debugMode) {
4353     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4354   }
4355     }
4356
4357
4358     /* Display the board */
4359     if (!pausing && !appData.noGUI) {
4360       if (appData.premove)
4361           if (!gotPremove ||
4362              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4363              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4364               ClearPremoveHighlights();
4365
4366       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4367       DrawPosition(j, boards[currentMove]);
4368
4369       DisplayMove(moveNum - 1);
4370       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4371             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4372               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4373         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4374       }
4375     }
4376
4377     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4378 #if ZIPPY
4379     if(bookHit) { // [HGM] book: simulate book reply
4380         static char bookMove[MSG_SIZ]; // a bit generous?
4381
4382         programStats.nodes = programStats.depth = programStats.time =
4383         programStats.score = programStats.got_only_move = 0;
4384         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4385
4386         strcpy(bookMove, "move ");
4387         strcat(bookMove, bookHit);
4388         HandleMachineMove(bookMove, &first);
4389     }
4390 #endif
4391 }
4392
4393 void
4394 GetMoveListEvent()
4395 {
4396     char buf[MSG_SIZ];
4397     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4398         ics_getting_history = H_REQUESTED;
4399         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4400         SendToICS(buf);
4401     }
4402 }
4403
4404 void
4405 AnalysisPeriodicEvent(force)
4406      int force;
4407 {
4408     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4409          && !force) || !appData.periodicUpdates)
4410       return;
4411
4412     /* Send . command to Crafty to collect stats */
4413     SendToProgram(".\n", &first);
4414
4415     /* Don't send another until we get a response (this makes
4416        us stop sending to old Crafty's which don't understand
4417        the "." command (sending illegal cmds resets node count & time,
4418        which looks bad)) */
4419     programStats.ok_to_send = 0;
4420 }
4421
4422 void ics_update_width(new_width)
4423         int new_width;
4424 {
4425         ics_printf("set width %d\n", new_width);
4426 }
4427
4428 void
4429 SendMoveToProgram(moveNum, cps)
4430      int moveNum;
4431      ChessProgramState *cps;
4432 {
4433     char buf[MSG_SIZ];
4434
4435     if (cps->useUsermove) {
4436       SendToProgram("usermove ", cps);
4437     }
4438     if (cps->useSAN) {
4439       char *space;
4440       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4441         int len = space - parseList[moveNum];
4442         memcpy(buf, parseList[moveNum], len);
4443         buf[len++] = '\n';
4444         buf[len] = NULLCHAR;
4445       } else {
4446         sprintf(buf, "%s\n", parseList[moveNum]);
4447       }
4448       SendToProgram(buf, cps);
4449     } else {
4450       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4451         AlphaRank(moveList[moveNum], 4);
4452         SendToProgram(moveList[moveNum], cps);
4453         AlphaRank(moveList[moveNum], 4); // and back
4454       } else
4455       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4456        * the engine. It would be nice to have a better way to identify castle
4457        * moves here. */
4458       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4459                                                                          && cps->useOOCastle) {
4460         int fromX = moveList[moveNum][0] - AAA;
4461         int fromY = moveList[moveNum][1] - ONE;
4462         int toX = moveList[moveNum][2] - AAA;
4463         int toY = moveList[moveNum][3] - ONE;
4464         if((boards[moveNum][fromY][fromX] == WhiteKing
4465             && boards[moveNum][toY][toX] == WhiteRook)
4466            || (boards[moveNum][fromY][fromX] == BlackKing
4467                && boards[moveNum][toY][toX] == BlackRook)) {
4468           if(toX > fromX) SendToProgram("O-O\n", cps);
4469           else SendToProgram("O-O-O\n", cps);
4470         }
4471         else SendToProgram(moveList[moveNum], cps);
4472       }
4473       else SendToProgram(moveList[moveNum], cps);
4474       /* End of additions by Tord */
4475     }
4476
4477     /* [HGM] setting up the opening has brought engine in force mode! */
4478     /*       Send 'go' if we are in a mode where machine should play. */
4479     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4480         (gameMode == TwoMachinesPlay   ||
4481 #ifdef ZIPPY
4482          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4483 #endif
4484          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4485         SendToProgram("go\n", cps);
4486   if (appData.debugMode) {
4487     fprintf(debugFP, "(extra)\n");
4488   }
4489     }
4490     setboardSpoiledMachineBlack = 0;
4491 }
4492
4493 void
4494 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4495      ChessMove moveType;
4496      int fromX, fromY, toX, toY;
4497 {
4498     char user_move[MSG_SIZ];
4499
4500     switch (moveType) {
4501       default:
4502         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4503                 (int)moveType, fromX, fromY, toX, toY);
4504         DisplayError(user_move + strlen("say "), 0);
4505         break;
4506       case WhiteKingSideCastle:
4507       case BlackKingSideCastle:
4508       case WhiteQueenSideCastleWild:
4509       case BlackQueenSideCastleWild:
4510       /* PUSH Fabien */
4511       case WhiteHSideCastleFR:
4512       case BlackHSideCastleFR:
4513       /* POP Fabien */
4514         sprintf(user_move, "o-o\n");
4515         break;
4516       case WhiteQueenSideCastle:
4517       case BlackQueenSideCastle:
4518       case WhiteKingSideCastleWild:
4519       case BlackKingSideCastleWild:
4520       /* PUSH Fabien */
4521       case WhiteASideCastleFR:
4522       case BlackASideCastleFR:
4523       /* POP Fabien */
4524         sprintf(user_move, "o-o-o\n");
4525         break;
4526       case WhitePromotionQueen:
4527       case BlackPromotionQueen:
4528       case WhitePromotionRook:
4529       case BlackPromotionRook:
4530       case WhitePromotionBishop:
4531       case BlackPromotionBishop:
4532       case WhitePromotionKnight:
4533       case BlackPromotionKnight:
4534       case WhitePromotionKing:
4535       case BlackPromotionKing:
4536       case WhitePromotionChancellor:
4537       case BlackPromotionChancellor:
4538       case WhitePromotionArchbishop:
4539       case BlackPromotionArchbishop:
4540         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4541             sprintf(user_move, "%c%c%c%c=%c\n",
4542                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4543                 PieceToChar(WhiteFerz));
4544         else if(gameInfo.variant == VariantGreat)
4545             sprintf(user_move, "%c%c%c%c=%c\n",
4546                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4547                 PieceToChar(WhiteMan));
4548         else
4549             sprintf(user_move, "%c%c%c%c=%c\n",
4550                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4551                 PieceToChar(PromoPiece(moveType)));
4552         break;
4553       case WhiteDrop:
4554       case BlackDrop:
4555         sprintf(user_move, "%c@%c%c\n",
4556                 ToUpper(PieceToChar((ChessSquare) fromX)),
4557                 AAA + toX, ONE + toY);
4558         break;
4559       case NormalMove:
4560       case WhiteCapturesEnPassant:
4561       case BlackCapturesEnPassant:
4562       case IllegalMove:  /* could be a variant we don't quite understand */
4563         sprintf(user_move, "%c%c%c%c\n",
4564                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4565         break;
4566     }
4567     SendToICS(user_move);
4568     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4569         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4570 }
4571
4572 void
4573 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4574      int rf, ff, rt, ft;
4575      char promoChar;
4576      char move[7];
4577 {
4578     if (rf == DROP_RANK) {
4579         sprintf(move, "%c@%c%c\n",
4580                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4581     } else {
4582         if (promoChar == 'x' || promoChar == NULLCHAR) {
4583             sprintf(move, "%c%c%c%c\n",
4584                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4585         } else {
4586             sprintf(move, "%c%c%c%c%c\n",
4587                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4588         }
4589     }
4590 }
4591
4592 void
4593 ProcessICSInitScript(f)
4594      FILE *f;
4595 {
4596     char buf[MSG_SIZ];
4597
4598     while (fgets(buf, MSG_SIZ, f)) {
4599         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4600     }
4601
4602     fclose(f);
4603 }
4604
4605
4606 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4607 void
4608 AlphaRank(char *move, int n)
4609 {
4610 //    char *p = move, c; int x, y;
4611
4612     if (appData.debugMode) {
4613         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4614     }
4615
4616     if(move[1]=='*' &&
4617        move[2]>='0' && move[2]<='9' &&
4618        move[3]>='a' && move[3]<='x'    ) {
4619         move[1] = '@';
4620         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4621         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4622     } else
4623     if(move[0]>='0' && move[0]<='9' &&
4624        move[1]>='a' && move[1]<='x' &&
4625        move[2]>='0' && move[2]<='9' &&
4626        move[3]>='a' && move[3]<='x'    ) {
4627         /* input move, Shogi -> normal */
4628         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4629         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4630         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4631         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4632     } else
4633     if(move[1]=='@' &&
4634        move[3]>='0' && move[3]<='9' &&
4635        move[2]>='a' && move[2]<='x'    ) {
4636         move[1] = '*';
4637         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4638         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4639     } else
4640     if(
4641        move[0]>='a' && move[0]<='x' &&
4642        move[3]>='0' && move[3]<='9' &&
4643        move[2]>='a' && move[2]<='x'    ) {
4644          /* output move, normal -> Shogi */
4645         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4646         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4647         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4648         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4649         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4650     }
4651     if (appData.debugMode) {
4652         fprintf(debugFP, "   out = '%s'\n", move);
4653     }
4654 }
4655
4656 /* Parser for moves from gnuchess, ICS, or user typein box */
4657 Boolean
4658 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4659      char *move;
4660      int moveNum;
4661      ChessMove *moveType;
4662      int *fromX, *fromY, *toX, *toY;
4663      char *promoChar;
4664 {
4665     if (appData.debugMode) {
4666         fprintf(debugFP, "move to parse: %s\n", move);
4667     }
4668     *moveType = yylexstr(moveNum, move);
4669
4670     switch (*moveType) {
4671       case WhitePromotionChancellor:
4672       case BlackPromotionChancellor:
4673       case WhitePromotionArchbishop:
4674       case BlackPromotionArchbishop:
4675       case WhitePromotionQueen:
4676       case BlackPromotionQueen:
4677       case WhitePromotionRook:
4678       case BlackPromotionRook:
4679       case WhitePromotionBishop:
4680       case BlackPromotionBishop:
4681       case WhitePromotionKnight:
4682       case BlackPromotionKnight:
4683       case WhitePromotionKing:
4684       case BlackPromotionKing:
4685       case NormalMove:
4686       case WhiteCapturesEnPassant:
4687       case BlackCapturesEnPassant:
4688       case WhiteKingSideCastle:
4689       case WhiteQueenSideCastle:
4690       case BlackKingSideCastle:
4691       case BlackQueenSideCastle:
4692       case WhiteKingSideCastleWild:
4693       case WhiteQueenSideCastleWild:
4694       case BlackKingSideCastleWild:
4695       case BlackQueenSideCastleWild:
4696       /* Code added by Tord: */
4697       case WhiteHSideCastleFR:
4698       case WhiteASideCastleFR:
4699       case BlackHSideCastleFR:
4700       case BlackASideCastleFR:
4701       /* End of code added by Tord */
4702       case IllegalMove:         /* bug or odd chess variant */
4703         *fromX = currentMoveString[0] - AAA;
4704         *fromY = currentMoveString[1] - ONE;
4705         *toX = currentMoveString[2] - AAA;
4706         *toY = currentMoveString[3] - ONE;
4707         *promoChar = currentMoveString[4];
4708         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4709             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4710     if (appData.debugMode) {
4711         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4712     }
4713             *fromX = *fromY = *toX = *toY = 0;
4714             return FALSE;
4715         }
4716         if (appData.testLegality) {
4717           return (*moveType != IllegalMove);
4718         } else {
4719           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4720                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4721         }
4722
4723       case WhiteDrop:
4724       case BlackDrop:
4725         *fromX = *moveType == WhiteDrop ?
4726           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4727           (int) CharToPiece(ToLower(currentMoveString[0]));
4728         *fromY = DROP_RANK;
4729         *toX = currentMoveString[2] - AAA;
4730         *toY = currentMoveString[3] - ONE;
4731         *promoChar = NULLCHAR;
4732         return TRUE;
4733
4734       case AmbiguousMove:
4735       case ImpossibleMove:
4736       case (ChessMove) 0:       /* end of file */
4737       case ElapsedTime:
4738       case Comment:
4739       case PGNTag:
4740       case NAG:
4741       case WhiteWins:
4742       case BlackWins:
4743       case GameIsDrawn:
4744       default:
4745     if (appData.debugMode) {
4746         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4747     }
4748         /* bug? */
4749         *fromX = *fromY = *toX = *toY = 0;
4750         *promoChar = NULLCHAR;
4751         return FALSE;
4752     }
4753 }
4754
4755
4756 void
4757 ParsePV(char *pv)
4758 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4759   int fromX, fromY, toX, toY; char promoChar;
4760   ChessMove moveType;
4761   Boolean valid;
4762   int nr = 0;
4763
4764   endPV = forwardMostMove;
4765   do {
4766     while(*pv == ' ') pv++;
4767     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4768     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4769 if(appData.debugMode){
4770 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4771 }
4772     if(!valid && nr == 0 &&
4773        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4774         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4775     }
4776     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4777     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4778     nr++;
4779     if(endPV+1 > framePtr) break; // no space, truncate
4780     if(!valid) break;
4781     endPV++;
4782     CopyBoard(boards[endPV], boards[endPV-1]);
4783     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4784     moveList[endPV-1][0] = fromX + AAA;
4785     moveList[endPV-1][1] = fromY + ONE;
4786     moveList[endPV-1][2] = toX + AAA;
4787     moveList[endPV-1][3] = toY + ONE;
4788     parseList[endPV-1][0] = NULLCHAR;
4789   } while(valid);
4790   currentMove = endPV;
4791   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4792   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4793                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4794   DrawPosition(TRUE, boards[currentMove]);
4795 }
4796
4797 static int lastX, lastY;
4798
4799 Boolean
4800 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4801 {
4802         int startPV;
4803
4804         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4805         lastX = x; lastY = y;
4806         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4807         startPV = index;
4808       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4809       index = startPV;
4810         while(buf[index] && buf[index] != '\n') index++;
4811         buf[index] = 0;
4812         ParsePV(buf+startPV);
4813         *start = startPV; *end = index-1;
4814         return TRUE;
4815 }
4816
4817 Boolean
4818 LoadPV(int x, int y)
4819 { // called on right mouse click to load PV
4820   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4821   lastX = x; lastY = y;
4822   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4823   return TRUE;
4824 }
4825
4826 void
4827 UnLoadPV()
4828 {
4829   if(endPV < 0) return;
4830   endPV = -1;
4831   currentMove = forwardMostMove;
4832   ClearPremoveHighlights();
4833   DrawPosition(TRUE, boards[currentMove]);
4834 }
4835
4836 void
4837 MovePV(int x, int y, int h)
4838 { // step through PV based on mouse coordinates (called on mouse move)
4839   int margin = h>>3, step = 0;
4840
4841   if(endPV < 0) return;
4842   // we must somehow check if right button is still down (might be released off board!)
4843   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4844   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4845   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4846   if(!step) return;
4847   lastX = x; lastY = y;
4848   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4849   currentMove += step;
4850   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4851   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4852                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4853   DrawPosition(FALSE, boards[currentMove]);
4854 }
4855
4856
4857 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4858 // All positions will have equal probability, but the current method will not provide a unique
4859 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4860 #define DARK 1
4861 #define LITE 2
4862 #define ANY 3
4863
4864 int squaresLeft[4];
4865 int piecesLeft[(int)BlackPawn];
4866 int seed, nrOfShuffles;
4867
4868 void GetPositionNumber()
4869 {       // sets global variable seed
4870         int i;
4871
4872         seed = appData.defaultFrcPosition;
4873         if(seed < 0) { // randomize based on time for negative FRC position numbers
4874                 for(i=0; i<50; i++) seed += random();
4875                 seed = random() ^ random() >> 8 ^ random() << 8;
4876                 if(seed<0) seed = -seed;
4877         }
4878 }
4879
4880 int put(Board board, int pieceType, int rank, int n, int shade)
4881 // put the piece on the (n-1)-th empty squares of the given shade
4882 {
4883         int i;
4884
4885         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4886                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4887                         board[rank][i] = (ChessSquare) pieceType;
4888                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4889                         squaresLeft[ANY]--;
4890                         piecesLeft[pieceType]--;
4891                         return i;
4892                 }
4893         }
4894         return -1;
4895 }
4896
4897
4898 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4899 // calculate where the next piece goes, (any empty square), and put it there
4900 {
4901         int i;
4902
4903         i = seed % squaresLeft[shade];
4904         nrOfShuffles *= squaresLeft[shade];
4905         seed /= squaresLeft[shade];
4906         put(board, pieceType, rank, i, shade);
4907 }
4908
4909 void AddTwoPieces(Board board, int pieceType, int rank)
4910 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4911 {
4912         int i, n=squaresLeft[ANY], j=n-1, k;
4913
4914         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4915         i = seed % k;  // pick one
4916         nrOfShuffles *= k;
4917         seed /= k;
4918         while(i >= j) i -= j--;
4919         j = n - 1 - j; i += j;
4920         put(board, pieceType, rank, j, ANY);
4921         put(board, pieceType, rank, i, ANY);
4922 }
4923
4924 void SetUpShuffle(Board board, int number)
4925 {
4926         int i, p, first=1;
4927
4928         GetPositionNumber(); nrOfShuffles = 1;
4929
4930         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4931         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4932         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4933
4934         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4935
4936         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4937             p = (int) board[0][i];
4938             if(p < (int) BlackPawn) piecesLeft[p] ++;
4939             board[0][i] = EmptySquare;
4940         }
4941
4942         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4943             // shuffles restricted to allow normal castling put KRR first
4944             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4945                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4946             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4947                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4948             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4949                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4950             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4951                 put(board, WhiteRook, 0, 0, ANY);
4952             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4953         }
4954
4955         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4956             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4957             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4958                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4959                 while(piecesLeft[p] >= 2) {
4960                     AddOnePiece(board, p, 0, LITE);
4961                     AddOnePiece(board, p, 0, DARK);
4962                 }
4963                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4964             }
4965
4966         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4967             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4968             // but we leave King and Rooks for last, to possibly obey FRC restriction
4969             if(p == (int)WhiteRook) continue;
4970             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4971             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4972         }
4973
4974         // now everything is placed, except perhaps King (Unicorn) and Rooks
4975
4976         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4977             // Last King gets castling rights
4978             while(piecesLeft[(int)WhiteUnicorn]) {
4979                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4980                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4981             }
4982
4983             while(piecesLeft[(int)WhiteKing]) {
4984                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4985                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4986             }
4987
4988
4989         } else {
4990             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4991             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4992         }
4993
4994         // Only Rooks can be left; simply place them all
4995         while(piecesLeft[(int)WhiteRook]) {
4996                 i = put(board, WhiteRook, 0, 0, ANY);
4997                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4998                         if(first) {
4999                                 first=0;
5000                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5001                         }
5002                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5003                 }
5004         }
5005         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5006             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5007         }
5008
5009         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5010 }
5011
5012 int SetCharTable( char *table, const char * map )
5013 /* [HGM] moved here from winboard.c because of its general usefulness */
5014 /*       Basically a safe strcpy that uses the last character as King */
5015 {
5016     int result = FALSE; int NrPieces;
5017
5018     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5019                     && NrPieces >= 12 && !(NrPieces&1)) {
5020         int i; /* [HGM] Accept even length from 12 to 34 */
5021
5022         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5023         for( i=0; i<NrPieces/2-1; i++ ) {
5024             table[i] = map[i];
5025             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5026         }
5027         table[(int) WhiteKing]  = map[NrPieces/2-1];
5028         table[(int) BlackKing]  = map[NrPieces-1];
5029
5030         result = TRUE;
5031     }
5032
5033     return result;
5034 }
5035
5036 void Prelude(Board board)
5037 {       // [HGM] superchess: random selection of exo-pieces
5038         int i, j, k; ChessSquare p;
5039         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5040
5041         GetPositionNumber(); // use FRC position number
5042
5043         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5044             SetCharTable(pieceToChar, appData.pieceToCharTable);
5045             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5046                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5047         }
5048
5049         j = seed%4;                 seed /= 4;
5050         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5051         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5052         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5053         j = seed%3 + (seed%3 >= j); seed /= 3;
5054         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5055         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5056         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5057         j = seed%3;                 seed /= 3;
5058         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5059         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5060         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5061         j = seed%2 + (seed%2 >= j); seed /= 2;
5062         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5063         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5064         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5065         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5066         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5067         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5068         put(board, exoPieces[0],    0, 0, ANY);
5069         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5070 }
5071
5072 void
5073 InitPosition(redraw)
5074      int redraw;
5075 {
5076     ChessSquare (* pieces)[BOARD_FILES];
5077     int i, j, pawnRow, overrule,
5078     oldx = gameInfo.boardWidth,
5079     oldy = gameInfo.boardHeight,
5080     oldh = gameInfo.holdingsWidth,
5081     oldv = gameInfo.variant;
5082
5083     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5084
5085     /* [AS] Initialize pv info list [HGM] and game status */
5086     {
5087         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5088             pvInfoList[i].depth = 0;
5089             boards[i][EP_STATUS] = EP_NONE;
5090             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5091         }
5092
5093         initialRulePlies = 0; /* 50-move counter start */
5094
5095         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5096         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5097     }
5098
5099
5100     /* [HGM] logic here is completely changed. In stead of full positions */
5101     /* the initialized data only consist of the two backranks. The switch */
5102     /* selects which one we will use, which is than copied to the Board   */
5103     /* initialPosition, which for the rest is initialized by Pawns and    */
5104     /* empty squares. This initial position is then copied to boards[0],  */
5105     /* possibly after shuffling, so that it remains available.            */
5106
5107     gameInfo.holdingsWidth = 0; /* default board sizes */
5108     gameInfo.boardWidth    = 8;
5109     gameInfo.boardHeight   = 8;
5110     gameInfo.holdingsSize  = 0;
5111     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5112     for(i=0; i<BOARD_FILES-2; i++)
5113       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5114     initialPosition[EP_STATUS] = EP_NONE;
5115     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5116
5117     switch (gameInfo.variant) {
5118     case VariantFischeRandom:
5119       shuffleOpenings = TRUE;
5120     default:
5121       pieces = FIDEArray;
5122       break;
5123     case VariantShatranj:
5124       pieces = ShatranjArray;
5125       nrCastlingRights = 0;
5126       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5127       break;
5128     case VariantMakruk:
5129       pieces = makrukArray;
5130       nrCastlingRights = 0;
5131       startedFromSetupPosition = TRUE;
5132       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5133       break;
5134     case VariantTwoKings:
5135       pieces = twoKingsArray;
5136       break;
5137     case VariantCapaRandom:
5138       shuffleOpenings = TRUE;
5139     case VariantCapablanca:
5140       pieces = CapablancaArray;
5141       gameInfo.boardWidth = 10;
5142       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5143       break;
5144     case VariantGothic:
5145       pieces = GothicArray;
5146       gameInfo.boardWidth = 10;
5147       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5148       break;
5149     case VariantJanus:
5150       pieces = JanusArray;
5151       gameInfo.boardWidth = 10;
5152       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5153       nrCastlingRights = 6;
5154         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5155         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5156         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5157         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5158         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5159         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5160       break;
5161     case VariantFalcon:
5162       pieces = FalconArray;
5163       gameInfo.boardWidth = 10;
5164       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5165       break;
5166     case VariantXiangqi:
5167       pieces = XiangqiArray;
5168       gameInfo.boardWidth  = 9;
5169       gameInfo.boardHeight = 10;
5170       nrCastlingRights = 0;
5171       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5172       break;
5173     case VariantShogi:
5174       pieces = ShogiArray;
5175       gameInfo.boardWidth  = 9;
5176       gameInfo.boardHeight = 9;
5177       gameInfo.holdingsSize = 7;
5178       nrCastlingRights = 0;
5179       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5180       break;
5181     case VariantCourier:
5182       pieces = CourierArray;
5183       gameInfo.boardWidth  = 12;
5184       nrCastlingRights = 0;
5185       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5186       break;
5187     case VariantKnightmate:
5188       pieces = KnightmateArray;
5189       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5190       break;
5191     case VariantFairy:
5192       pieces = fairyArray;
5193       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5194       break;
5195     case VariantGreat:
5196       pieces = GreatArray;
5197       gameInfo.boardWidth = 10;
5198       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5199       gameInfo.holdingsSize = 8;
5200       break;
5201     case VariantSuper:
5202       pieces = FIDEArray;
5203       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5204       gameInfo.holdingsSize = 8;
5205       startedFromSetupPosition = TRUE;
5206       break;
5207     case VariantCrazyhouse:
5208     case VariantBughouse:
5209       pieces = FIDEArray;
5210       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5211       gameInfo.holdingsSize = 5;
5212       break;
5213     case VariantWildCastle:
5214       pieces = FIDEArray;
5215       /* !!?shuffle with kings guaranteed to be on d or e file */
5216       shuffleOpenings = 1;
5217       break;
5218     case VariantNoCastle:
5219       pieces = FIDEArray;
5220       nrCastlingRights = 0;
5221       /* !!?unconstrained back-rank shuffle */
5222       shuffleOpenings = 1;
5223       break;
5224     }
5225
5226     overrule = 0;
5227     if(appData.NrFiles >= 0) {
5228         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5229         gameInfo.boardWidth = appData.NrFiles;
5230     }
5231     if(appData.NrRanks >= 0) {
5232         gameInfo.boardHeight = appData.NrRanks;
5233     }
5234     if(appData.holdingsSize >= 0) {
5235         i = appData.holdingsSize;
5236         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5237         gameInfo.holdingsSize = i;
5238     }
5239     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5240     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5241         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5242
5243     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5244     if(pawnRow < 1) pawnRow = 1;
5245     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5246
5247     /* User pieceToChar list overrules defaults */
5248     if(appData.pieceToCharTable != NULL)
5249         SetCharTable(pieceToChar, appData.pieceToCharTable);
5250
5251     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5252
5253         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5254             s = (ChessSquare) 0; /* account holding counts in guard band */
5255         for( i=0; i<BOARD_HEIGHT; i++ )
5256             initialPosition[i][j] = s;
5257
5258         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5259         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5260         initialPosition[pawnRow][j] = WhitePawn;
5261         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5262         if(gameInfo.variant == VariantXiangqi) {
5263             if(j&1) {
5264                 initialPosition[pawnRow][j] =
5265                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5266                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5267                    initialPosition[2][j] = WhiteCannon;
5268                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5269                 }
5270             }
5271         }
5272         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5273     }
5274     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5275
5276             j=BOARD_LEFT+1;
5277             initialPosition[1][j] = WhiteBishop;
5278             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5279             j=BOARD_RGHT-2;
5280             initialPosition[1][j] = WhiteRook;
5281             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5282     }
5283
5284     if( nrCastlingRights == -1) {
5285         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5286         /*       This sets default castling rights from none to normal corners   */
5287         /* Variants with other castling rights must set them themselves above    */
5288         nrCastlingRights = 6;
5289         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5290         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5291         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5292         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5293         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5294         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5295      }
5296
5297      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5298      if(gameInfo.variant == VariantGreat) { // promotion commoners
5299         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5300         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5301         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5302         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5303      }
5304   if (appData.debugMode) {
5305     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5306   }
5307     if(shuffleOpenings) {
5308         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5309         startedFromSetupPosition = TRUE;
5310     }
5311     if(startedFromPositionFile) {
5312       /* [HGM] loadPos: use PositionFile for every new game */
5313       CopyBoard(initialPosition, filePosition);
5314       for(i=0; i<nrCastlingRights; i++)
5315           initialRights[i] = filePosition[CASTLING][i];
5316       startedFromSetupPosition = TRUE;
5317     }
5318
5319     CopyBoard(boards[0], initialPosition);
5320     if(oldx != gameInfo.boardWidth ||
5321        oldy != gameInfo.boardHeight ||
5322        oldh != gameInfo.holdingsWidth
5323 #ifdef GOTHIC
5324        || oldv == VariantGothic ||        // For licensing popups
5325        gameInfo.variant == VariantGothic
5326 #endif
5327 #ifdef FALCON
5328        || oldv == VariantFalcon ||
5329        gameInfo.variant == VariantFalcon
5330 #endif
5331                                          )
5332       {
5333             InitDrawingSizes(-2 ,0);
5334       }
5335
5336     if (redraw)
5337       DrawPosition(TRUE, boards[currentMove]);
5338
5339 }
5340
5341 void
5342 SendBoard(cps, moveNum)
5343      ChessProgramState *cps;
5344      int moveNum;
5345 {
5346     char message[MSG_SIZ];
5347
5348     if (cps->useSetboard) {
5349       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5350       sprintf(message, "setboard %s\n", fen);
5351       SendToProgram(message, cps);
5352       free(fen);
5353
5354     } else {
5355       ChessSquare *bp;
5356       int i, j;
5357       /* Kludge to set black to move, avoiding the troublesome and now
5358        * deprecated "black" command.
5359        */
5360       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5361
5362       SendToProgram("edit\n", cps);
5363       SendToProgram("#\n", cps);
5364       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5365         bp = &boards[moveNum][i][BOARD_LEFT];
5366         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5367           if ((int) *bp < (int) BlackPawn) {
5368             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5369                     AAA + j, ONE + i);
5370             if(message[0] == '+' || message[0] == '~') {
5371                 sprintf(message, "%c%c%c+\n",
5372                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5373                         AAA + j, ONE + i);
5374             }
5375             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5376                 message[1] = BOARD_RGHT   - 1 - j + '1';
5377                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5378             }
5379             SendToProgram(message, cps);
5380           }
5381         }
5382       }
5383
5384       SendToProgram("c\n", cps);
5385       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5386         bp = &boards[moveNum][i][BOARD_LEFT];
5387         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5388           if (((int) *bp != (int) EmptySquare)
5389               && ((int) *bp >= (int) BlackPawn)) {
5390             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5391                     AAA + j, ONE + i);
5392             if(message[0] == '+' || message[0] == '~') {
5393                 sprintf(message, "%c%c%c+\n",
5394                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5395                         AAA + j, ONE + i);
5396             }
5397             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5398                 message[1] = BOARD_RGHT   - 1 - j + '1';
5399                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5400             }
5401             SendToProgram(message, cps);
5402           }
5403         }
5404       }
5405
5406       SendToProgram(".\n", cps);
5407     }
5408     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5409 }
5410
5411 int
5412 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5413 {
5414     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5415     /* [HGM] add Shogi promotions */
5416     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5417     ChessSquare piece;
5418     ChessMove moveType;
5419     Boolean premove;
5420
5421     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5422     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5423
5424     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5425       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5426         return FALSE;
5427
5428     piece = boards[currentMove][fromY][fromX];
5429     if(gameInfo.variant == VariantShogi) {
5430         promotionZoneSize = 3;
5431         highestPromotingPiece = (int)WhiteFerz;
5432     } else if(gameInfo.variant == VariantMakruk) {
5433         promotionZoneSize = 3;
5434     }
5435
5436     // next weed out all moves that do not touch the promotion zone at all
5437     if((int)piece >= BlackPawn) {
5438         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5439              return FALSE;
5440         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5441     } else {
5442         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5443            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5444     }
5445
5446     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5447
5448     // weed out mandatory Shogi promotions
5449     if(gameInfo.variant == VariantShogi) {
5450         if(piece >= BlackPawn) {
5451             if(toY == 0 && piece == BlackPawn ||
5452                toY == 0 && piece == BlackQueen ||
5453                toY <= 1 && piece == BlackKnight) {
5454                 *promoChoice = '+';
5455                 return FALSE;
5456             }
5457         } else {
5458             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5459                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5460                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5461                 *promoChoice = '+';
5462                 return FALSE;
5463             }
5464         }
5465     }
5466
5467     // weed out obviously illegal Pawn moves
5468     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5469         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5470         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5471         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5472         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5473         // note we are not allowed to test for valid (non-)capture, due to premove
5474     }
5475
5476     // we either have a choice what to promote to, or (in Shogi) whether to promote
5477     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5478         *promoChoice = PieceToChar(BlackFerz);  // no choice
5479         return FALSE;
5480     }
5481     if(appData.alwaysPromoteToQueen) { // predetermined
5482         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5483              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5484         else *promoChoice = PieceToChar(BlackQueen);
5485         return FALSE;
5486     }
5487
5488     // suppress promotion popup on illegal moves that are not premoves
5489     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5490               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5491     if(appData.testLegality && !premove) {
5492         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5493                         fromY, fromX, toY, toX, NULLCHAR);
5494         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5495            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5496             return FALSE;
5497     }
5498
5499     return TRUE;
5500 }
5501
5502 int
5503 InPalace(row, column)
5504      int row, column;
5505 {   /* [HGM] for Xiangqi */
5506     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5507          column < (BOARD_WIDTH + 4)/2 &&
5508          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5509     return FALSE;
5510 }
5511
5512 int
5513 PieceForSquare (x, y)
5514      int x;
5515      int y;
5516 {
5517   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5518      return -1;
5519   else
5520      return boards[currentMove][y][x];
5521 }
5522
5523 int
5524 OKToStartUserMove(x, y)
5525      int x, y;
5526 {
5527     ChessSquare from_piece;
5528     int white_piece;
5529
5530     if (matchMode) return FALSE;
5531     if (gameMode == EditPosition) return TRUE;
5532
5533     if (x >= 0 && y >= 0)
5534       from_piece = boards[currentMove][y][x];
5535     else
5536       from_piece = EmptySquare;
5537
5538     if (from_piece == EmptySquare) return FALSE;
5539
5540     white_piece = (int)from_piece >= (int)WhitePawn &&
5541       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5542
5543     switch (gameMode) {
5544       case PlayFromGameFile:
5545       case AnalyzeFile:
5546       case TwoMachinesPlay:
5547       case EndOfGame:
5548         return FALSE;
5549
5550       case IcsObserving:
5551       case IcsIdle:
5552         return FALSE;
5553
5554       case MachinePlaysWhite:
5555       case IcsPlayingBlack:
5556         if (appData.zippyPlay) return FALSE;
5557         if (white_piece) {
5558             DisplayMoveError(_("You are playing Black"));
5559             return FALSE;
5560         }
5561         break;
5562
5563       case MachinePlaysBlack:
5564       case IcsPlayingWhite:
5565         if (appData.zippyPlay) return FALSE;
5566         if (!white_piece) {
5567             DisplayMoveError(_("You are playing White"));
5568             return FALSE;
5569         }
5570         break;
5571
5572       case EditGame:
5573         if (!white_piece && WhiteOnMove(currentMove)) {
5574             DisplayMoveError(_("It is White's turn"));
5575             return FALSE;
5576         }
5577         if (white_piece && !WhiteOnMove(currentMove)) {
5578             DisplayMoveError(_("It is Black's turn"));
5579             return FALSE;
5580         }
5581         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5582             /* Editing correspondence game history */
5583             /* Could disallow this or prompt for confirmation */
5584             cmailOldMove = -1;
5585         }
5586         break;
5587
5588       case BeginningOfGame:
5589         if (appData.icsActive) return FALSE;
5590         if (!appData.noChessProgram) {
5591             if (!white_piece) {
5592                 DisplayMoveError(_("You are playing White"));
5593                 return FALSE;
5594             }
5595         }
5596         break;
5597
5598       case Training:
5599         if (!white_piece && WhiteOnMove(currentMove)) {
5600             DisplayMoveError(_("It is White's turn"));
5601             return FALSE;
5602         }
5603         if (white_piece && !WhiteOnMove(currentMove)) {
5604             DisplayMoveError(_("It is Black's turn"));
5605             return FALSE;
5606         }
5607         break;
5608
5609       default:
5610       case IcsExamining:
5611         break;
5612     }
5613     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5614         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5615         && gameMode != AnalyzeFile && gameMode != Training) {
5616         DisplayMoveError(_("Displayed position is not current"));
5617         return FALSE;
5618     }
5619     return TRUE;
5620 }
5621
5622 Boolean
5623 OnlyMove(int *x, int *y) {
5624     DisambiguateClosure cl;
5625     if (appData.zippyPlay) return FALSE;
5626     switch(gameMode) {
5627       case MachinePlaysBlack:
5628       case IcsPlayingWhite:
5629       case BeginningOfGame:
5630         if(!WhiteOnMove(currentMove)) return FALSE;
5631         break;
5632       case MachinePlaysWhite:
5633       case IcsPlayingBlack:
5634         if(WhiteOnMove(currentMove)) return FALSE;
5635         break;
5636       default:
5637         return FALSE;
5638     }
5639     cl.pieceIn = EmptySquare; 
5640     cl.rfIn = *y;
5641     cl.ffIn = *x;
5642     cl.rtIn = -1;
5643     cl.ftIn = -1;
5644     cl.promoCharIn = NULLCHAR;
5645     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5646     if(cl.kind == NormalMove) {
5647       fromX = cl.ff;
5648       fromY = cl.rf;
5649       *x = cl.ft;
5650       *y = cl.rt;
5651       return TRUE;
5652     }
5653     if(cl.kind != ImpossibleMove) return FALSE;
5654     cl.pieceIn = EmptySquare;
5655     cl.rfIn = -1;
5656     cl.ffIn = -1;
5657     cl.rtIn = *y;
5658     cl.ftIn = *x;
5659     cl.promoCharIn = NULLCHAR;
5660     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5661     if(cl.kind == NormalMove) {
5662       fromX = cl.ff;
5663       fromY = cl.rf;
5664       *x = cl.ft;
5665       *y = cl.rt;
5666       return TRUE;
5667     }
5668     return FALSE;
5669 }
5670
5671 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5672 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5673 int lastLoadGameUseList = FALSE;
5674 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5675 ChessMove lastLoadGameStart = (ChessMove) 0;
5676
5677 ChessMove
5678 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5679      int fromX, fromY, toX, toY;
5680      int promoChar;
5681      Boolean captureOwn;
5682 {
5683     ChessMove moveType;
5684     ChessSquare pdown, pup;
5685
5686     /* Check if the user is playing in turn.  This is complicated because we
5687        let the user "pick up" a piece before it is his turn.  So the piece he
5688        tried to pick up may have been captured by the time he puts it down!
5689        Therefore we use the color the user is supposed to be playing in this
5690        test, not the color of the piece that is currently on the starting
5691        square---except in EditGame mode, where the user is playing both
5692        sides; fortunately there the capture race can't happen.  (It can
5693        now happen in IcsExamining mode, but that's just too bad.  The user
5694        will get a somewhat confusing message in that case.)
5695        */
5696
5697     switch (gameMode) {
5698       case PlayFromGameFile:
5699       case AnalyzeFile:
5700       case TwoMachinesPlay:
5701       case EndOfGame:
5702       case IcsObserving:
5703       case IcsIdle:
5704         /* We switched into a game mode where moves are not accepted,
5705            perhaps while the mouse button was down. */
5706         return ImpossibleMove;
5707
5708       case MachinePlaysWhite:
5709         /* User is moving for Black */
5710         if (WhiteOnMove(currentMove)) {
5711             DisplayMoveError(_("It is White's turn"));
5712             return ImpossibleMove;
5713         }
5714         break;
5715
5716       case MachinePlaysBlack:
5717         /* User is moving for White */
5718         if (!WhiteOnMove(currentMove)) {
5719             DisplayMoveError(_("It is Black's turn"));
5720             return ImpossibleMove;
5721         }
5722         break;
5723
5724       case EditGame:
5725       case IcsExamining:
5726       case BeginningOfGame:
5727       case AnalyzeMode:
5728       case Training:
5729         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5730             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5731             /* User is moving for Black */
5732             if (WhiteOnMove(currentMove)) {
5733                 DisplayMoveError(_("It is White's turn"));
5734                 return ImpossibleMove;
5735             }
5736         } else {
5737             /* User is moving for White */
5738             if (!WhiteOnMove(currentMove)) {
5739                 DisplayMoveError(_("It is Black's turn"));
5740                 return ImpossibleMove;
5741             }
5742         }
5743         break;
5744
5745       case IcsPlayingBlack:
5746         /* User is moving for Black */
5747         if (WhiteOnMove(currentMove)) {
5748             if (!appData.premove) {
5749                 DisplayMoveError(_("It is White's turn"));
5750             } else if (toX >= 0 && toY >= 0) {
5751                 premoveToX = toX;
5752                 premoveToY = toY;
5753                 premoveFromX = fromX;
5754                 premoveFromY = fromY;
5755                 premovePromoChar = promoChar;
5756                 gotPremove = 1;
5757                 if (appData.debugMode)
5758                     fprintf(debugFP, "Got premove: fromX %d,"
5759                             "fromY %d, toX %d, toY %d\n",
5760                             fromX, fromY, toX, toY);
5761             }
5762             return ImpossibleMove;
5763         }
5764         break;
5765
5766       case IcsPlayingWhite:
5767         /* User is moving for White */
5768         if (!WhiteOnMove(currentMove)) {
5769             if (!appData.premove) {
5770                 DisplayMoveError(_("It is Black's turn"));
5771             } else if (toX >= 0 && toY >= 0) {
5772                 premoveToX = toX;
5773                 premoveToY = toY;
5774                 premoveFromX = fromX;
5775                 premoveFromY = fromY;
5776                 premovePromoChar = promoChar;
5777                 gotPremove = 1;
5778                 if (appData.debugMode)
5779                     fprintf(debugFP, "Got premove: fromX %d,"
5780                             "fromY %d, toX %d, toY %d\n",
5781                             fromX, fromY, toX, toY);
5782             }
5783             return ImpossibleMove;
5784         }
5785         break;
5786
5787       default:
5788         break;
5789
5790       case EditPosition:
5791         /* EditPosition, empty square, or different color piece;
5792            click-click move is possible */
5793         if (toX == -2 || toY == -2) {
5794             boards[0][fromY][fromX] = EmptySquare;
5795             rightsBoard[fromY][fromX] = 0;
5796             return AmbiguousMove;
5797         } else if (toX >= 0 && toY >= 0) {
5798             boards[0][toY][toX] = boards[0][fromY][fromX];
5799             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5800                 if(boards[0][fromY][0] != EmptySquare) {
5801                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5802                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5803                 }
5804             } else
5805             if(fromX == BOARD_RGHT+1) {
5806                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5807                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5808                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5809                 }
5810             } else
5811             boards[0][fromY][fromX] = EmptySquare;
5812             rightsBoard[fromY][fromX] = rightsBoard[toY][toX] = 0;
5813             return AmbiguousMove;
5814         }
5815         return ImpossibleMove;
5816     }
5817
5818     if(toX < 0 || toY < 0) return ImpossibleMove;
5819     pdown = boards[currentMove][fromY][fromX];
5820     pup = boards[currentMove][toY][toX];
5821
5822     /* [HGM] If move started in holdings, it means a drop */
5823     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5824          if( pup != EmptySquare ) return ImpossibleMove;
5825          if(appData.testLegality) {
5826              /* it would be more logical if LegalityTest() also figured out
5827               * which drops are legal. For now we forbid pawns on back rank.
5828               * Shogi is on its own here...
5829               */
5830              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5831                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5832                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5833          }
5834          return WhiteDrop; /* Not needed to specify white or black yet */
5835     }
5836
5837
5838     /* [HGM] always test for legality, to get promotion info */
5839     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5840                                          fromY, fromX, toY, toX, promoChar);
5841     /* [HGM] but possibly ignore an IllegalMove result */
5842     if (appData.testLegality) {
5843         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5844             DisplayMoveError(_("Illegal move"));
5845             return ImpossibleMove;
5846         }
5847     }
5848
5849     return moveType;
5850     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5851        function is made into one that returns an OK move type if FinishMove
5852        should be called. This to give the calling driver routine the
5853        opportunity to finish the userMove input with a promotion popup,
5854        without bothering the user with this for invalid or illegal moves */
5855
5856 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5857 }
5858
5859 /* Common tail of UserMoveEvent and DropMenuEvent */
5860 int
5861 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5862      ChessMove moveType;
5863      int fromX, fromY, toX, toY;
5864      /*char*/int promoChar;
5865 {
5866   char *bookHit = 0;
5867
5868   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5869     {
5870       // [HGM] superchess: suppress promotions to non-available piece
5871       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5872       if(WhiteOnMove(currentMove))
5873         {
5874           if(!boards[currentMove][k][BOARD_WIDTH-2])
5875             return 0;
5876         }
5877       else
5878         {
5879           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5880             return 0;
5881         }
5882     }
5883   
5884   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5885      move type in caller when we know the move is a legal promotion */
5886   if(moveType == NormalMove && promoChar)
5887     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5888   
5889   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5890      move type in caller when we know the move is a legal promotion */
5891   if(moveType == NormalMove && promoChar)
5892     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5893   
5894   /* [HGM] convert drag-and-drop piece drops to standard form */
5895   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5896     {
5897       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5898       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5899                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5900       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5901       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5902       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5903       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5904       fromY = DROP_RANK;
5905     }
5906   
5907   /* [HGM] <popupFix> The following if has been moved here from
5908      UserMoveEvent(). Because it seemed to belong here (why not allow
5909      piece drops in training games?), and because it can only be
5910      performed after it is known to what we promote. */
5911   if (gameMode == Training) 
5912     {
5913       /* compare the move played on the board to the next move in the
5914        * game. If they match, display the move and the opponent's response.
5915        * If they don't match, display an error message.
5916        */
5917       int saveAnimate;
5918       Board testBoard;
5919       CopyBoard(testBoard, boards[currentMove]);
5920       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5921
5922       if (CompareBoards(testBoard, boards[currentMove+1]))
5923         {
5924           ForwardInner(currentMove+1);
5925
5926           /* Autoplay the opponent's response.
5927            * if appData.animate was TRUE when Training mode was entered,
5928            * the response will be animated.
5929            */
5930           saveAnimate = appData.animate;
5931           appData.animate = animateTraining;
5932           ForwardInner(currentMove+1);
5933           appData.animate = saveAnimate;
5934
5935           /* check for the end of the game */
5936           if (currentMove >= forwardMostMove)
5937             {
5938               gameMode = PlayFromGameFile;
5939               ModeHighlight();
5940               SetTrainingModeOff();
5941               DisplayInformation(_("End of game"));
5942             }
5943         }
5944       else
5945         {
5946           DisplayError(_("Incorrect move"), 0);
5947         }
5948       return 1;
5949     }
5950
5951   /* Ok, now we know that the move is good, so we can kill
5952      the previous line in Analysis Mode */
5953   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5954                                 && currentMove < forwardMostMove) {
5955     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5956   }
5957
5958   /* If we need the chess program but it's dead, restart it */
5959   ResurrectChessProgram();
5960
5961   /* A user move restarts a paused game*/
5962   if (pausing)
5963     PauseEvent();
5964
5965   thinkOutput[0] = NULLCHAR;
5966
5967   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5968
5969
5970  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5971
5972 if (gameMode == BeginningOfGame)
5973     {
5974       if (appData.noChessProgram)
5975         {
5976           gameMode = EditGame;
5977           SetGameInfo();
5978         }
5979       else
5980         {
5981           char buf[MSG_SIZ];
5982           gameMode = MachinePlaysBlack;
5983           StartClocks();
5984           SetGameInfo();
5985           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5986           DisplayTitle(buf);
5987           if (first.sendName)
5988             {
5989               sprintf(buf, "name %s\n", gameInfo.white);
5990               SendToProgram(buf, &first);
5991             }
5992           StartClocks();
5993         }
5994       ModeHighlight();
5995
5996     }
5997
5998   /* Relay move to ICS or chess engine */
5999
6000   if (appData.icsActive) {
6001     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6002         gameMode == IcsExamining) {
6003       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6004         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6005         SendToICS("draw ");
6006         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6007       }
6008       // also send plain move, in case ICS does not understand atomic claims
6009       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010       ics_user_moved = 1;
6011     }
6012   } else {
6013     if (first.sendTime && (gameMode == BeginningOfGame ||
6014                            gameMode == MachinePlaysWhite ||
6015                            gameMode == MachinePlaysBlack)) {
6016       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6017     }
6018     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6019          // [HGM] book: if program might be playing, let it use book
6020         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6021         first.maybeThinking = TRUE;
6022     } else SendMoveToProgram(forwardMostMove-1, &first);
6023     if (currentMove == cmailOldMove + 1) {
6024       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6025     }
6026     }
6027
6028   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6029
6030   switch (gameMode) 
6031     {
6032     case EditGame:
6033       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
6034         {
6035         case MT_NONE:
6036         case MT_CHECK:
6037           break;
6038         case MT_CHECKMATE:
6039         case MT_STAINMATE:
6040           if (WhiteOnMove(currentMove)) {
6041             GameEnds(BlackWins, "Black mates", GE_PLAYER);
6042           } else {
6043             GameEnds(WhiteWins, "White mates", GE_PLAYER);
6044           }
6045           break;
6046         case MT_STALEMATE:
6047           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6048           break;
6049         }
6050       break;
6051       
6052     case MachinePlaysBlack:
6053     case MachinePlaysWhite:
6054       /* disable certain menu options while machine is thinking */
6055       SetMachineThinkingEnables();
6056       break;
6057       
6058     default:
6059       break;
6060     }
6061   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6062         
6063   if(bookHit)
6064     { // [HGM] book: simulate book reply
6065         static char bookMove[MSG_SIZ]; // a bit generous?
6066
6067
6068       programStats.nodes = programStats.depth = programStats.time =
6069         programStats.score = programStats.got_only_move = 0;
6070       sprintf(programStats.movelist, "%s (xbook)", bookHit);
6071
6072       strcpy(bookMove, "move ");
6073       strcat(bookMove, bookHit);
6074       HandleMachineMove(bookMove, &first);
6075     }
6076
6077   return 1;
6078 }
6079
6080 void
6081 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6082      int fromX, fromY, toX, toY;
6083      int promoChar;
6084 {
6085     /* [HGM] This routine was added to allow calling of its two logical
6086        parts from other modules in the old way. Before, UserMoveEvent()
6087        automatically called FinishMove() if the move was OK, and returned
6088        otherwise. I separated the two, in order to make it possible to
6089        slip a promotion popup in between. But that it always needs two
6090        calls, to the first part, (now called UserMoveTest() ), and to
6091        FinishMove if the first part succeeded. Calls that do not need
6092        to do anything in between, can call this routine the old way.
6093     */
6094   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6095   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6096   if(moveType == AmbiguousMove)
6097     DrawPosition(FALSE, boards[currentMove]);
6098   else if(moveType != ImpossibleMove && moveType != Comment)
6099     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6100 }
6101
6102 int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 };
6103 int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 };
6104 Board promoBoard;
6105 int promotionChoice = 0;
6106
6107 void
6108 PiecePopUp(int x, int y)
6109 {
6110     int i, j, h, w, nWhite=0, nBlack=0;
6111     ChessSquare list[EmptySquare];
6112     for(i=0; i<EmptySquare/2; i++) {
6113         if(PieceToChar(i) != '.') list[nWhite++] = i;
6114         if(PieceToChar(i+EmptySquare/2) != '.') list[EmptySquare - ++nBlack] = i + EmptySquare/2;
6115     }
6116     CopyBoard(promoBoard, boards[currentMove]);
6117     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6118     j = nWhite + nBlack + 1;
6119     h = sqrt((j+1)/2 + 1.); w = (j+h-1)/h;
6120     if(w>BOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; }
6121     for(i=0; i<nWhite; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[nWhite-1-i];
6122     if(h==2 && nWhite == nBlack)
6123         for(i=0; i<nWhite; i++) promoBoard[1][BOARD_LEFT+i%w] = list[EmptySquare-nBlack+i];
6124     else
6125         for(i=0; i<nBlack; i++) promoBoard[h-1-i/w][BOARD_LEFT+w-1-i%w] = list[EmptySquare-nBlack+i];
6126     promotionChoice = 3;
6127     ClearHighlights();
6128     PromoDialog(h, w, promoBoard, TRUE, _("Select piece:"), x, y);
6129 }
6130
6131 void
6132 PromoPopUp(ChessSquare piece)
6133 {   // determine the layout of the piece-choice dialog
6134     int w, h, i, j, nr;
6135     ChessSquare list[EmptySquare];
6136
6137     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6138     if(gameInfo.variant == VariantShogi) {
6139         // non-Pawn promotes; must be shogi
6140         h = 1; w = 1; promoBoard[0][BOARD_LEFT+0] = piece;
6141         if(PieceToChar(PROMOTED piece) != '.') {
6142             // promoted version is enabled
6143             w = 2; promoBoard[0][BOARD_LEFT+1] = PROMOTED piece;
6144         }
6145     } else {
6146         // Pawn, promotes to any enabled other piece
6147         h = 1; w = nr = 0;
6148         for(i=1; i<EmptySquare/2; i++) {
6149             if(PieceToChar(piece+i) != '.' 
6150            && PieceToChar(piece + i) != '~' // suppress bughouse true pieces
6151                                                         ) {
6152                 list[w++] = piece+i; nr++;
6153             }
6154         }
6155         if(appData.testLegality && gameInfo.variant != VariantSuicide
6156                         && gameInfo.variant != VariantGiveaway) nr--,w--; // remove King
6157         h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6158         for(i=0; i < nr; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[i]; // layout
6159     }
6160     promotionChoice = 2; // wait for click on board
6161     PromoDialog(h, w, promoBoard, FALSE, _("Promote to:"), -1, -1);
6162 }
6163
6164 void
6165 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6166      Board board;
6167      int flags;
6168      ChessMove kind;
6169      int rf, ff, rt, ft;
6170      VOIDSTAR closure;
6171 {
6172     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6173     Markers *m = (Markers *) closure;
6174     if(rf == fromY && ff == fromX)
6175         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6176                          || kind == WhiteCapturesEnPassant
6177                          || kind == BlackCapturesEnPassant);
6178     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6179 }
6180
6181 void
6182 MarkTargetSquares(int clear)
6183 {
6184   int x, y;
6185   if(!appData.markers || !appData.highlightDragging || 
6186      !appData.testLegality || gameMode == EditPosition) return;
6187   if(clear) {
6188     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6189   } else {
6190     int capt = 0;
6191     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6192     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6193       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6194       if(capt)
6195       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6196     }
6197   }
6198   DrawPosition(TRUE, NULL);
6199 }
6200
6201 void LeftClick(ClickType clickType, int xPix, int yPix)
6202 {
6203     int x, y;
6204     Boolean saveAnimate;
6205     static int second = 0;
6206     char promoChoice = NULLCHAR;
6207
6208     if(appData.seekGraph && appData.icsActive && loggedOn &&
6209         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6210         SeekGraphClick(clickType, xPix, yPix, 0);
6211         return;
6212     }
6213
6214     if (clickType == Press) ErrorPopDown();
6215     MarkTargetSquares(1);
6216
6217     x = EventToSquare(xPix, BOARD_WIDTH);
6218     y = EventToSquare(yPix, BOARD_HEIGHT);
6219     if (!flipView && y >= 0) {
6220         y = BOARD_HEIGHT - 1 - y;
6221     }
6222     if (flipView && x >= 0) {
6223         x = BOARD_WIDTH - 1 - x;
6224     }
6225
6226     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6227         ChessSquare p = EmptySquare; Boolean inHoldings;
6228         if(promotionChoice == 3) {
6229             if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6230             else if(clickType == Release) promotionChoice = 0;
6231             fromX = fromY = -1;
6232             return;
6233         }
6234         if(clickType == Release) return; // ignore upclick of click-click destination
6235         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6236         inHoldings = gameInfo.holdingsWidth &&
6237                 (WhiteOnMove(currentMove) 
6238                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6239                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6240             // click in right holdings, for determining promotion piece
6241         if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6242             p = promoBoard[y][x];
6243             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6244             if(p != EmptySquare) {
6245                 char promoChar = PieceToChar(p);
6246                 if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '=';
6247                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar));
6248                 fromX = fromY = -1;
6249                 promotionChoice = 0;
6250                 return;
6251             }
6252         }
6253         promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6254         DrawPosition(FALSE, boards[currentMove]);
6255         return;
6256     }
6257
6258     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6259     if(clickType == Press
6260             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6261               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6262               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6263         return;
6264
6265     if (fromX == -1) {
6266       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6267         if (clickType == Press) {
6268             /* First square */
6269             if (OKToStartUserMove(x, y)) {
6270                 fromX = x;
6271                 fromY = y;
6272                 second = 0;
6273                 MarkTargetSquares(0);
6274                 DragPieceBegin(xPix, yPix);
6275                 if (appData.highlightDragging) {
6276                     SetHighlights(x, y, -1, -1);
6277                 }
6278             }
6279         }
6280         return;
6281       }
6282     }
6283
6284     /* fromX != -1 */
6285     if (clickType == Press && gameMode != EditPosition) {
6286         ChessSquare fromP;
6287         ChessSquare toP;
6288         int frc;
6289
6290         // ignore off-board to clicks
6291         if(y < 0 || x < 0) return;
6292
6293         /* Check if clicking again on the same color piece */
6294         fromP = boards[currentMove][fromY][fromX];
6295         toP = boards[currentMove][y][x];
6296         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6297         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6298              WhitePawn <= toP && toP <= WhiteKing &&
6299              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6300              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6301             (BlackPawn <= fromP && fromP <= BlackKing && 
6302              BlackPawn <= toP && toP <= BlackKing &&
6303              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6304              !(fromP == BlackKing && toP == BlackRook && frc))) {
6305             /* Clicked again on same color piece -- changed his mind */
6306             second = (x == fromX && y == fromY);
6307             if (appData.highlightDragging) {
6308                 SetHighlights(x, y, -1, -1);
6309             } else {
6310                 ClearHighlights();
6311             }
6312             if (OKToStartUserMove(x, y)) {
6313                 fromX = x;
6314                 fromY = y;
6315                 MarkTargetSquares(0);
6316                 DragPieceBegin(xPix, yPix);
6317             }
6318             return;
6319         }
6320         // ignore clicks on holdings
6321         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6322     }
6323
6324     if (clickType == Release && x == fromX && y == fromY) {
6325         DragPieceEnd(xPix, yPix);
6326         if (appData.animateDragging) {
6327             /* Undo animation damage if any */
6328             DrawPosition(FALSE, NULL);
6329         }
6330         if (second) {
6331             /* Second up/down in same square; just abort move */
6332             second = 0;
6333             fromX = fromY = -1;
6334             ClearHighlights();
6335             gotPremove = 0;
6336             ClearPremoveHighlights();
6337         } else {
6338             /* First upclick in same square; start click-click mode */
6339             SetHighlights(x, y, -1, -1);
6340         }
6341         return;
6342     }
6343
6344     /* we now have a different from- and (possibly off-board) to-square */
6345     /* Completed move */
6346     toX = x;
6347     toY = y;
6348     saveAnimate = appData.animate;
6349     if (clickType == Press) {
6350         /* Finish clickclick move */
6351         if (appData.animate || appData.highlightLastMove) {
6352             SetHighlights(fromX, fromY, toX, toY);
6353         } else {
6354             ClearHighlights();
6355         }
6356     } else {
6357         /* Finish drag move */
6358         if (appData.highlightLastMove) {
6359             SetHighlights(fromX, fromY, toX, toY);
6360         } else {
6361             ClearHighlights();
6362         }
6363         DragPieceEnd(xPix, yPix);
6364         /* Don't animate move and drag both */
6365         appData.animate = FALSE;
6366     }
6367
6368     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6369     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6370         ChessSquare piece = boards[currentMove][fromY][fromX];
6371         if(gameMode == EditPosition && piece != EmptySquare &&
6372            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6373             int n;
6374              
6375             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6376                 n = PieceToNumber(piece - (int)BlackPawn);
6377                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6378                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6379                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6380             } else
6381             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6382                 n = PieceToNumber(piece);
6383                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6384                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6385                 boards[currentMove][n][BOARD_WIDTH-2]++;
6386             }
6387             boards[currentMove][fromY][fromX] = EmptySquare;
6388         }
6389         ClearHighlights();
6390         fromX = fromY = -1;
6391         DrawPosition(TRUE, boards[currentMove]);
6392         return;
6393     }
6394
6395     // off-board moves should not be highlighted
6396     if(x < 0 || x < 0) ClearHighlights();
6397
6398     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6399         SetHighlights(fromX, fromY, toX, toY);
6400         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6401             // [HGM] super: promotion to captured piece selected from holdings
6402             ChessSquare p = boards[currentMove][fromY][fromX];
6403             promotionChoice = 1;
6404             CopyBoard(promoBoard, boards[currentMove]);
6405             // kludge follows to temporarily execute move on display, without promoting yet
6406             promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6407             promoBoard[toY][toX] = p;
6408             DrawPosition(FALSE, promoBoard);
6409             DisplayMessage("Click in holdings to choose piece", "");
6410             return;
6411         }
6412         CopyBoard(promoBoard, boards[currentMove]);
6413         PromoPopUp(boards[currentMove][fromY][fromX]);
6414     } else {
6415         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6416         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6417         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6418         fromX = fromY = -1;
6419     }
6420     appData.animate = saveAnimate;
6421     if (appData.animate || appData.animateDragging) {
6422         /* Undo animation damage if needed */
6423         DrawPosition(FALSE, NULL);
6424     }
6425 }
6426
6427 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6428 {   // front-end-free part taken out of PieceMenuPopup
6429     int whichMenu; int xSqr, ySqr;
6430
6431     if(seekGraphUp) { // [HGM] seekgraph
6432         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6433         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6434         return -2;
6435     }
6436
6437     xSqr = EventToSquare(x, BOARD_WIDTH);
6438     ySqr = EventToSquare(y, BOARD_HEIGHT);
6439     if (flipView)
6440       xSqr = BOARD_WIDTH - 1 - xSqr;
6441     else
6442       ySqr = BOARD_HEIGHT - 1 - ySqr;
6443     if(promotionChoice == 3 && action == Release) {
6444         EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6445         fromX = fromY = -1;
6446         promotionChoice = 0;
6447         return -1;
6448     }
6449     if (action == Release) UnLoadPV(); // [HGM] pv
6450     if (action != Press) return -2; // return code to be ignored
6451     switch (gameMode) {
6452       case IcsExamining:
6453         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6454       case EditPosition:
6455         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6456         if (xSqr < 0 || ySqr < 0) return -1;
6457         whichMenu = 0; // edit-position menu
6458         break;
6459       case IcsObserving:
6460         if(!appData.icsEngineAnalyze) return -1;
6461       case IcsPlayingWhite:
6462       case IcsPlayingBlack:
6463         if(!appData.zippyPlay) goto noZip;
6464       case AnalyzeMode:
6465       case AnalyzeFile:
6466       case MachinePlaysWhite:
6467       case MachinePlaysBlack:
6468       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6469         if (!appData.dropMenu) {
6470           LoadPV(x, y);
6471           return 2; // flag front-end to grab mouse events
6472         }
6473         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6474            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6475       case EditGame:
6476       noZip:
6477         if (xSqr < 0 || ySqr < 0) return -1;
6478         if (!appData.dropMenu || appData.testLegality &&
6479             gameInfo.variant != VariantBughouse &&
6480             gameInfo.variant != VariantCrazyhouse) return -1;
6481         whichMenu = 1; // drop menu
6482         break;
6483       default:
6484         return -1;
6485     }
6486
6487     if (((*xx = xSqr) < 0) ||
6488         ((*yy = ySqr) < 0)) {
6489         *xx = *yy = -1;
6490         return -1;
6491     }
6492
6493     fromX = *xx; fromY = *yy;
6494     if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu
6495
6496     return whichMenu;
6497 }
6498
6499 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6500 {
6501 //    char * hint = lastHint;
6502     FrontEndProgramStats stats;
6503
6504     stats.which = cps == &first ? 0 : 1;
6505     stats.depth = cpstats->depth;
6506     stats.nodes = cpstats->nodes;
6507     stats.score = cpstats->score;
6508     stats.time = cpstats->time;
6509     stats.pv = cpstats->movelist;
6510     stats.hint = lastHint;
6511     stats.an_move_index = 0;
6512     stats.an_move_count = 0;
6513
6514     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6515         stats.hint = cpstats->move_name;
6516         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6517         stats.an_move_count = cpstats->nr_moves;
6518     }
6519
6520     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6521
6522     SetProgramStats( &stats );
6523 }
6524
6525 int
6526 Adjudicate(ChessProgramState *cps)
6527 {       // [HGM] some adjudications useful with buggy engines
6528         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6529         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6530         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6531         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6532         int k, count = 0; static int bare = 1;
6533         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6534         Boolean canAdjudicate = !appData.icsActive;
6535
6536         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6537         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6538             if( appData.testLegality )
6539             {   /* [HGM] Some more adjudications for obstinate engines */
6540                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6541                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6542                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6543                 static int moveCount = 6;
6544                 ChessMove result;
6545                 char *reason = NULL;
6546
6547
6548                 /* Count what is on board. */
6549                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6550                 {   ChessSquare p = boards[forwardMostMove][i][j];
6551                     int m=i;
6552
6553                     switch((int) p)
6554                     {   /* count B,N,R and other of each side */
6555                         case WhiteKing:
6556                         case BlackKing:
6557                              NrK++; break; // [HGM] atomic: count Kings
6558                         case WhiteKnight:
6559                              NrWN++; break;
6560                         case WhiteBishop:
6561                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6562                              bishopsColor |= 1 << ((i^j)&1);
6563                              NrWB++; break;
6564                         case BlackKnight:
6565                              NrBN++; break;
6566                         case BlackBishop:
6567                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6568                              bishopsColor |= 1 << ((i^j)&1);
6569                              NrBB++; break;
6570                         case WhiteRook:
6571                              NrWR++; break;
6572                         case BlackRook:
6573                              NrBR++; break;
6574                         case WhiteQueen:
6575                              NrWQ++; break;
6576                         case BlackQueen:
6577                              NrBQ++; break;
6578                         case EmptySquare: 
6579                              break;
6580                         case BlackPawn:
6581                              m = 7-i;
6582                         case WhitePawn:
6583                              PawnAdvance += m; NrPawns++;
6584                     }
6585                     NrPieces += (p != EmptySquare);
6586                     NrW += ((int)p < (int)BlackPawn);
6587                     if(gameInfo.variant == VariantXiangqi && 
6588                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6589                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6590                         NrW -= ((int)p < (int)BlackPawn);
6591                     }
6592                 }
6593
6594                 /* Some material-based adjudications that have to be made before stalemate test */
6595                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6596                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6597                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6598                      if(canAdjudicate && appData.checkMates) {
6599                          if(engineOpponent)
6600                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6601                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6602                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6603                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6604                          return 1;
6605                      }
6606                 }
6607
6608                 /* Bare King in Shatranj (loses) or Losers (wins) */
6609                 if( NrW == 1 || NrPieces - NrW == 1) {
6610                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6611                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6612                      if(canAdjudicate && appData.checkMates) {
6613                          if(engineOpponent)
6614                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6615                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6616                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6617                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6618                          return 1;
6619                      }
6620                   } else
6621                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6622                   {    /* bare King */
6623                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6624                         if(canAdjudicate && appData.checkMates) {
6625                             /* but only adjudicate if adjudication enabled */
6626                             if(engineOpponent)
6627                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6628                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6629                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6630                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6631                             return 1;
6632                         }
6633                   }
6634                 } else bare = 1;
6635
6636
6637             // don't wait for engine to announce game end if we can judge ourselves
6638             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6639               case MT_CHECK:
6640                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6641                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6642                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6643                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6644                             checkCnt++;
6645                         if(checkCnt >= 2) {
6646                             reason = "Xboard adjudication: 3rd check";
6647                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6648                             break;
6649                         }
6650                     }
6651                 }
6652               case MT_NONE:
6653               default:
6654                 break;
6655               case MT_STALEMATE:
6656               case MT_STAINMATE:
6657                 reason = "Xboard adjudication: Stalemate";
6658                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6659                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6660                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6661                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6662                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6663                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6664                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6665                                                                         EP_CHECKMATE : EP_WINS);
6666                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6667                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6668                 }
6669                 break;
6670               case MT_CHECKMATE:
6671                 reason = "Xboard adjudication: Checkmate";
6672                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6673                 break;
6674             }
6675
6676                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6677                     case EP_STALEMATE:
6678                         result = GameIsDrawn; break;
6679                     case EP_CHECKMATE:
6680                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6681                     case EP_WINS:
6682                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6683                     default:
6684                         result = (ChessMove) 0;
6685                 }
6686                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6687                     if(engineOpponent)
6688                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6689                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6690                     GameEnds( result, reason, GE_XBOARD );
6691                     return 1;
6692                 }
6693
6694                 /* Next absolutely insufficient mating material. */
6695                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6696                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6697                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6698                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6699                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6700
6701                      /* always flag draws, for judging claims */
6702                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6703
6704                      if(canAdjudicate && appData.materialDraws) {
6705                          /* but only adjudicate them if adjudication enabled */
6706                          if(engineOpponent) {
6707                            SendToProgram("force\n", engineOpponent); // suppress reply
6708                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6709                          }
6710                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6712                          return 1;
6713                      }
6714                 }
6715
6716                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6717                 if(NrPieces == 4 && 
6718                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6719                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6720                    || NrWN==2 || NrBN==2     /* KNNK */
6721                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6722                   ) ) {
6723                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6724                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6725                           if(engineOpponent) {
6726                             SendToProgram("force\n", engineOpponent); // suppress reply
6727                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6728                           }
6729                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6730                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6731                           return 1;
6732                      }
6733                 } else moveCount = 6;
6734             }
6735         }
6736           
6737         if (appData.debugMode) { int i;
6738             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6739                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6740                     appData.drawRepeats);
6741             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6742               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6743             
6744         }
6745
6746         // Repetition draws and 50-move rule can be applied independently of legality testing
6747
6748                 /* Check for rep-draws */
6749                 count = 0;
6750                 for(k = forwardMostMove-2;
6751                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6752                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6753                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6754                     k-=2)
6755                 {   int rights=0;
6756                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6757                         /* compare castling rights */
6758                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6759                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6760                                 rights++; /* King lost rights, while rook still had them */
6761                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6762                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6763                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6764                                    rights++; /* but at least one rook lost them */
6765                         }
6766                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6767                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6768                                 rights++; 
6769                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6770                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6771                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6772                                    rights++;
6773                         }
6774                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6775                             && appData.drawRepeats > 1) {
6776                              /* adjudicate after user-specified nr of repeats */
6777                              if(engineOpponent) {
6778                                SendToProgram("force\n", engineOpponent); // suppress reply
6779                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6780                              }
6781                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6782                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6783                                 // [HGM] xiangqi: check for forbidden perpetuals
6784                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6785                                 for(m=forwardMostMove; m>k; m-=2) {
6786                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6787                                         ourPerpetual = 0; // the current mover did not always check
6788                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6789                                         hisPerpetual = 0; // the opponent did not always check
6790                                 }
6791                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6792                                                                         ourPerpetual, hisPerpetual);
6793                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6794                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6795                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6796                                     return 1;
6797                                 }
6798                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6799                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6800                                 // Now check for perpetual chases
6801                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6802                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6803                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6804                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6805                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6806                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6807                                         return 1;
6808                                     }
6809                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6810                                         break; // Abort repetition-checking loop.
6811                                 }
6812                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6813                              }
6814                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6815                              return 1;
6816                         }
6817                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6818                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6819                     }
6820                 }
6821
6822                 /* Now we test for 50-move draws. Determine ply count */
6823                 count = forwardMostMove;
6824                 /* look for last irreversble move */
6825                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6826                     count--;
6827                 /* if we hit starting position, add initial plies */
6828                 if( count == backwardMostMove )
6829                     count -= initialRulePlies;
6830                 count = forwardMostMove - count; 
6831                 if( count >= 100)
6832                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6833                          /* this is used to judge if draw claims are legal */
6834                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6835                          if(engineOpponent) {
6836                            SendToProgram("force\n", engineOpponent); // suppress reply
6837                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6838                          }
6839                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6840                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6841                          return 1;
6842                 }
6843
6844                 /* if draw offer is pending, treat it as a draw claim
6845                  * when draw condition present, to allow engines a way to
6846                  * claim draws before making their move to avoid a race
6847                  * condition occurring after their move
6848                  */
6849                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6850                          char *p = NULL;
6851                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6852                              p = "Draw claim: 50-move rule";
6853                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6854                              p = "Draw claim: 3-fold repetition";
6855                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6856                              p = "Draw claim: insufficient mating material";
6857                          if( p != NULL && canAdjudicate) {
6858                              if(engineOpponent) {
6859                                SendToProgram("force\n", engineOpponent); // suppress reply
6860                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6861                              }
6862                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6863                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6864                              return 1;
6865                          }
6866                 }
6867
6868                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6869                     if(engineOpponent) {
6870                       SendToProgram("force\n", engineOpponent); // suppress reply
6871                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6872                     }
6873                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6874                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6875                     return 1;
6876                 }
6877         return 0;
6878 }
6879
6880 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6881 {   // [HGM] book: this routine intercepts moves to simulate book replies
6882     char *bookHit = NULL;
6883
6884     //first determine if the incoming move brings opponent into his book
6885     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6886         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6887     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6888     if(bookHit != NULL && !cps->bookSuspend) {
6889         // make sure opponent is not going to reply after receiving move to book position
6890         SendToProgram("force\n", cps);
6891         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6892     }
6893     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6894     // now arrange restart after book miss
6895     if(bookHit) {
6896         // after a book hit we never send 'go', and the code after the call to this routine
6897         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6898         char buf[MSG_SIZ];
6899         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6900         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6901         SendToProgram(buf, cps);
6902         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6903     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6904         SendToProgram("go\n", cps);
6905         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6906     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6907         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6908             SendToProgram("go\n", cps); 
6909         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6910     }
6911     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6912 }
6913
6914 char *savedMessage;
6915 ChessProgramState *savedState;
6916 void DeferredBookMove(void)
6917 {
6918         if(savedState->lastPing != savedState->lastPong)
6919                     ScheduleDelayedEvent(DeferredBookMove, 10);
6920         else
6921         HandleMachineMove(savedMessage, savedState);
6922 }
6923
6924 void
6925 HandleMachineMove(message, cps)
6926      char *message;
6927      ChessProgramState *cps;
6928 {
6929     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6930     char realname[MSG_SIZ];
6931     int fromX, fromY, toX, toY;
6932     ChessMove moveType;
6933     char promoChar;
6934     char *p;
6935     int machineWhite;
6936     char *bookHit;
6937
6938     cps->userError = 0;
6939
6940 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6941     /*
6942      * Kludge to ignore BEL characters
6943      */
6944     while (*message == '\007') message++;
6945
6946     /*
6947      * [HGM] engine debug message: ignore lines starting with '#' character
6948      */
6949     if(cps->debug && *message == '#') return;
6950
6951     /*
6952      * Look for book output
6953      */
6954     if (cps == &first && bookRequested) {
6955         if (message[0] == '\t' || message[0] == ' ') {
6956             /* Part of the book output is here; append it */
6957             strcat(bookOutput, message);
6958             strcat(bookOutput, "  \n");
6959             return;
6960         } else if (bookOutput[0] != NULLCHAR) {
6961             /* All of book output has arrived; display it */
6962             char *p = bookOutput;
6963             while (*p != NULLCHAR) {
6964                 if (*p == '\t') *p = ' ';
6965                 p++;
6966             }
6967             DisplayInformation(bookOutput);
6968             bookRequested = FALSE;
6969             /* Fall through to parse the current output */
6970         }
6971     }
6972
6973     /*
6974      * Look for machine move.
6975      */
6976     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6977         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6978     {
6979         /* This method is only useful on engines that support ping */
6980         if (cps->lastPing != cps->lastPong) {
6981           if (gameMode == BeginningOfGame) {
6982             /* Extra move from before last new; ignore */
6983             if (appData.debugMode) {
6984                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6985             }
6986           } else {
6987             if (appData.debugMode) {
6988                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6989                         cps->which, gameMode);
6990             }
6991
6992             SendToProgram("undo\n", cps);
6993           }
6994           return;
6995         }
6996
6997         switch (gameMode) {
6998           case BeginningOfGame:
6999             /* Extra move from before last reset; ignore */
7000             if (appData.debugMode) {
7001                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7002             }
7003             return;
7004
7005           case EndOfGame:
7006           case IcsIdle:
7007           default:
7008             /* Extra move after we tried to stop.  The mode test is
7009                not a reliable way of detecting this problem, but it's
7010                the best we can do on engines that don't support ping.
7011             */
7012             if (appData.debugMode) {
7013                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7014                         cps->which, gameMode);
7015             }
7016             SendToProgram("undo\n", cps);
7017             return;
7018
7019           case MachinePlaysWhite:
7020           case IcsPlayingWhite:
7021             machineWhite = TRUE;
7022             break;
7023
7024           case MachinePlaysBlack:
7025           case IcsPlayingBlack:
7026             machineWhite = FALSE;
7027             break;
7028
7029           case TwoMachinesPlay:
7030             machineWhite = (cps->twoMachinesColor[0] == 'w');
7031             break;
7032         }
7033         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7034             if (appData.debugMode) {
7035                 fprintf(debugFP,
7036                         "Ignoring move out of turn by %s, gameMode %d"
7037                         ", forwardMost %d\n",
7038                         cps->which, gameMode, forwardMostMove);
7039             }
7040             return;
7041         }
7042
7043     if (appData.debugMode) { int f = forwardMostMove;
7044         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7045                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7046                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7047     }
7048         if(cps->alphaRank) AlphaRank(machineMove, 4);
7049         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7050                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7051             /* Machine move could not be parsed; ignore it. */
7052             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7053                     machineMove, cps->which);
7054             DisplayError(buf1, 0);
7055             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7056                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7057             if (gameMode == TwoMachinesPlay) {
7058               GameEnds(machineWhite ? BlackWins : WhiteWins,
7059                        buf1, GE_XBOARD);
7060             }
7061             return;
7062         }
7063
7064         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7065         /* So we have to redo legality test with true e.p. status here,  */
7066         /* to make sure an illegal e.p. capture does not slip through,   */
7067         /* to cause a forfeit on a justified illegal-move complaint      */
7068         /* of the opponent.                                              */
7069         if( gameMode==TwoMachinesPlay && appData.testLegality
7070             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7071                                                               ) {
7072            ChessMove moveType;
7073            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7074                              fromY, fromX, toY, toX, promoChar);
7075             if (appData.debugMode) {
7076                 int i;
7077                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7078                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7079                 fprintf(debugFP, "castling rights\n");
7080             }
7081             if(moveType == IllegalMove) {
7082                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7083                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7084                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7085                            buf1, GE_XBOARD);
7086                 return;
7087            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7088            /* [HGM] Kludge to handle engines that send FRC-style castling
7089               when they shouldn't (like TSCP-Gothic) */
7090            switch(moveType) {
7091              case WhiteASideCastleFR:
7092              case BlackASideCastleFR:
7093                toX+=2;
7094                currentMoveString[2]++;
7095                break;
7096              case WhiteHSideCastleFR:
7097              case BlackHSideCastleFR:
7098                toX--;
7099                currentMoveString[2]--;
7100                break;
7101              default: ; // nothing to do, but suppresses warning of pedantic compilers
7102            }
7103         }
7104         hintRequested = FALSE;
7105         lastHint[0] = NULLCHAR;
7106         bookRequested = FALSE;
7107         /* Program may be pondering now */
7108         cps->maybeThinking = TRUE;
7109         if (cps->sendTime == 2) cps->sendTime = 1;
7110         if (cps->offeredDraw) cps->offeredDraw--;
7111
7112         /* currentMoveString is set as a side-effect of ParseOneMove */
7113         strcpy(machineMove, currentMoveString);
7114         strcat(machineMove, "\n");
7115         strcpy(moveList[forwardMostMove], machineMove);
7116
7117         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7118
7119         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7120         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7121             int count = 0;
7122
7123             while( count < adjudicateLossPlies ) {
7124                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7125
7126                 if( count & 1 ) {
7127                     score = -score; /* Flip score for winning side */
7128                 }
7129
7130                 if( score > adjudicateLossThreshold ) {
7131                     break;
7132                 }
7133
7134                 count++;
7135             }
7136
7137             if( count >= adjudicateLossPlies ) {
7138                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7139
7140                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7141                     "Xboard adjudication", 
7142                     GE_XBOARD );
7143
7144                 return;
7145             }
7146         }
7147
7148         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7149
7150 #if ZIPPY
7151         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7152             first.initDone) {
7153           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7154                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7155                 SendToICS("draw ");
7156                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7157           }
7158           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7159           ics_user_moved = 1;
7160           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7161                 char buf[3*MSG_SIZ];
7162
7163                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7164                         programStats.score / 100.,
7165                         programStats.depth,
7166                         programStats.time / 100.,
7167                         (unsigned int)programStats.nodes,
7168                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7169                         programStats.movelist);
7170                 SendToICS(buf);
7171 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7172           }
7173         }
7174 #endif
7175
7176         /* [AS] Save move info and clear stats for next move */
7177         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7178         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7179         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7180         ClearProgramStats();
7181         thinkOutput[0] = NULLCHAR;
7182         hiddenThinkOutputState = 0;
7183
7184         bookHit = NULL;
7185         if (gameMode == TwoMachinesPlay) {
7186             /* [HGM] relaying draw offers moved to after reception of move */
7187             /* and interpreting offer as claim if it brings draw condition */
7188             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7189                 SendToProgram("draw\n", cps->other);
7190             }
7191             if (cps->other->sendTime) {
7192                 SendTimeRemaining(cps->other,
7193                                   cps->other->twoMachinesColor[0] == 'w');
7194             }
7195             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7196             if (firstMove && !bookHit) {
7197                 firstMove = FALSE;
7198                 if (cps->other->useColors) {
7199                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7200                 }
7201                 SendToProgram("go\n", cps->other);
7202             }
7203             cps->other->maybeThinking = TRUE;
7204         }
7205
7206         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7207
7208         if (!pausing && appData.ringBellAfterMoves) {
7209             RingBell();
7210         }
7211
7212         /*
7213          * Reenable menu items that were disabled while
7214          * machine was thinking
7215          */
7216         if (gameMode != TwoMachinesPlay)
7217             SetUserThinkingEnables();
7218
7219         // [HGM] book: after book hit opponent has received move and is now in force mode
7220         // force the book reply into it, and then fake that it outputted this move by jumping
7221         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7222         if(bookHit) {
7223                 static char bookMove[MSG_SIZ]; // a bit generous?
7224
7225                 strcpy(bookMove, "move ");
7226                 strcat(bookMove, bookHit);
7227                 message = bookMove;
7228                 cps = cps->other;
7229                 programStats.nodes = programStats.depth = programStats.time =
7230                 programStats.score = programStats.got_only_move = 0;
7231                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7232
7233                 if(cps->lastPing != cps->lastPong) {
7234                     savedMessage = message; // args for deferred call
7235                     savedState = cps;
7236                     ScheduleDelayedEvent(DeferredBookMove, 10);
7237                     return;
7238                 }
7239                 goto FakeBookMove;
7240         }
7241
7242         return;
7243     }
7244
7245     /* Set special modes for chess engines.  Later something general
7246      *  could be added here; for now there is just one kludge feature,
7247      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7248      *  when "xboard" is given as an interactive command.
7249      */
7250     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7251         cps->useSigint = FALSE;
7252         cps->useSigterm = FALSE;
7253     }
7254     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7255       ParseFeatures(message+8, cps);
7256       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7257     }
7258
7259     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7260      * want this, I was asked to put it in, and obliged.
7261      */
7262     if (!strncmp(message, "setboard ", 9)) {
7263         Board initial_position;
7264
7265         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7266
7267         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7268             DisplayError(_("Bad FEN received from engine"), 0);
7269             return ;
7270         } else {
7271            Reset(TRUE, FALSE);
7272            CopyBoard(boards[0], initial_position);
7273            initialRulePlies = FENrulePlies;
7274            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7275            else gameMode = MachinePlaysBlack;
7276            DrawPosition(FALSE, boards[currentMove]);
7277         }
7278         return;
7279     }
7280
7281     /*
7282      * Look for communication commands
7283      */
7284     if (!strncmp(message, "telluser ", 9)) {
7285         DisplayNote(message + 9);
7286         return;
7287     }
7288     if (!strncmp(message, "tellusererror ", 14)) {
7289         cps->userError = 1;
7290         DisplayError(message + 14, 0);
7291         return;
7292     }
7293     if (!strncmp(message, "tellopponent ", 13)) {
7294       if (appData.icsActive) {
7295         if (loggedOn) {
7296           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7297           SendToICS(buf1);
7298         }
7299       } else {
7300         DisplayNote(message + 13);
7301       }
7302       return;
7303     }
7304     if (!strncmp(message, "tellothers ", 11)) {
7305       if (appData.icsActive) {
7306         if (loggedOn) {
7307           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7308           SendToICS(buf1);
7309         }
7310       }
7311       return;
7312     }
7313     if (!strncmp(message, "tellall ", 8)) {
7314       if (appData.icsActive) {
7315         if (loggedOn) {
7316           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7317           SendToICS(buf1);
7318         }
7319       } else {
7320         DisplayNote(message + 8);
7321       }
7322       return;
7323     }
7324     if (strncmp(message, "warning", 7) == 0) {
7325         /* Undocumented feature, use tellusererror in new code */
7326         DisplayError(message, 0);
7327         return;
7328     }
7329     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7330         strcpy(realname, cps->tidy);
7331         strcat(realname, " query");
7332         AskQuestion(realname, buf2, buf1, cps->pr);
7333         return;
7334     }
7335     /* Commands from the engine directly to ICS.  We don't allow these to be
7336      *  sent until we are logged on. Crafty kibitzes have been known to
7337      *  interfere with the login process.
7338      */
7339     if (loggedOn) {
7340         if (!strncmp(message, "tellics ", 8)) {
7341             SendToICS(message + 8);
7342             SendToICS("\n");
7343             return;
7344         }
7345         if (!strncmp(message, "tellicsnoalias ", 15)) {
7346             SendToICS(ics_prefix);
7347             SendToICS(message + 15);
7348             SendToICS("\n");
7349             return;
7350         }
7351         /* The following are for backward compatibility only */
7352         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7353             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7354             SendToICS(ics_prefix);
7355             SendToICS(message);
7356             SendToICS("\n");
7357             return;
7358         }
7359     }
7360     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7361         return;
7362     }
7363     /*
7364      * If the move is illegal, cancel it and redraw the board.
7365      * Also deal with other error cases.  Matching is rather loose
7366      * here to accommodate engines written before the spec.
7367      */
7368     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7369         strncmp(message, "Error", 5) == 0) {
7370         if (StrStr(message, "name") ||
7371             StrStr(message, "rating") || StrStr(message, "?") ||
7372             StrStr(message, "result") || StrStr(message, "board") ||
7373             StrStr(message, "bk") || StrStr(message, "computer") ||
7374             StrStr(message, "variant") || StrStr(message, "hint") ||
7375             StrStr(message, "random") || StrStr(message, "depth") ||
7376             StrStr(message, "accepted")) {
7377             return;
7378         }
7379         if (StrStr(message, "protover")) {
7380           /* Program is responding to input, so it's apparently done
7381              initializing, and this error message indicates it is
7382              protocol version 1.  So we don't need to wait any longer
7383              for it to initialize and send feature commands. */
7384           FeatureDone(cps, 1);
7385           cps->protocolVersion = 1;
7386           return;
7387         }
7388         cps->maybeThinking = FALSE;
7389
7390         if (StrStr(message, "draw")) {
7391             /* Program doesn't have "draw" command */
7392             cps->sendDrawOffers = 0;
7393             return;
7394         }
7395         if (cps->sendTime != 1 &&
7396             (StrStr(message, "time") || StrStr(message, "otim"))) {
7397           /* Program apparently doesn't have "time" or "otim" command */
7398           cps->sendTime = 0;
7399           return;
7400         }
7401         if (StrStr(message, "analyze")) {
7402             cps->analysisSupport = FALSE;
7403             cps->analyzing = FALSE;
7404             Reset(FALSE, TRUE);
7405             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7406             DisplayError(buf2, 0);
7407             return;
7408         }
7409         if (StrStr(message, "(no matching move)st")) {
7410           /* Special kludge for GNU Chess 4 only */
7411           cps->stKludge = TRUE;
7412           SendTimeControl(cps, movesPerSession, timeControl,
7413                           timeIncrement, appData.searchDepth,
7414                           searchTime);
7415           return;
7416         }
7417         if (StrStr(message, "(no matching move)sd")) {
7418           /* Special kludge for GNU Chess 4 only */
7419           cps->sdKludge = TRUE;
7420           SendTimeControl(cps, movesPerSession, timeControl,
7421                           timeIncrement, appData.searchDepth,
7422                           searchTime);
7423           return;
7424         }
7425         if (!StrStr(message, "llegal")) {
7426             return;
7427         }
7428         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7429             gameMode == IcsIdle) return;
7430         if (forwardMostMove <= backwardMostMove) return;
7431         if (pausing) PauseEvent();
7432       if(appData.forceIllegal) {
7433             // [HGM] illegal: machine refused move; force position after move into it
7434           SendToProgram("force\n", cps);
7435           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7436                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7437                 // when black is to move, while there might be nothing on a2 or black
7438                 // might already have the move. So send the board as if white has the move.
7439                 // But first we must change the stm of the engine, as it refused the last move
7440                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7441                 if(WhiteOnMove(forwardMostMove)) {
7442                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7443                     SendBoard(cps, forwardMostMove); // kludgeless board
7444                 } else {
7445                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7446                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7447                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7448                 }
7449           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7450             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7451                  gameMode == TwoMachinesPlay)
7452               SendToProgram("go\n", cps);
7453             return;
7454       } else
7455         if (gameMode == PlayFromGameFile) {
7456             /* Stop reading this game file */
7457             gameMode = EditGame;
7458             ModeHighlight();
7459         }
7460         currentMove = --forwardMostMove;
7461         DisplayMove(currentMove-1); /* before DisplayMoveError */
7462         SwitchClocks();
7463         DisplayBothClocks();
7464         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7465                 parseList[currentMove], cps->which);
7466         DisplayMoveError(buf1);
7467         DrawPosition(FALSE, boards[currentMove]);
7468
7469         /* [HGM] illegal-move claim should forfeit game when Xboard */
7470         /* only passes fully legal moves                            */
7471         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7472             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7473                                 "False illegal-move claim", GE_XBOARD );
7474         }
7475         return;
7476     }
7477     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7478         /* Program has a broken "time" command that
7479            outputs a string not ending in newline.
7480            Don't use it. */
7481         cps->sendTime = 0;
7482     }
7483
7484     /*
7485      * If chess program startup fails, exit with an error message.
7486      * Attempts to recover here are futile.
7487      */
7488     if ((StrStr(message, "unknown host") != NULL)
7489         || (StrStr(message, "No remote directory") != NULL)
7490         || (StrStr(message, "not found") != NULL)
7491         || (StrStr(message, "No such file") != NULL)
7492         || (StrStr(message, "can't alloc") != NULL)
7493         || (StrStr(message, "Permission denied") != NULL)) {
7494
7495         cps->maybeThinking = FALSE;
7496         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7497                 cps->which, cps->program, cps->host, message);
7498         RemoveInputSource(cps->isr);
7499         DisplayFatalError(buf1, 0, 1);
7500         return;
7501     }
7502
7503     /*
7504      * Look for hint output
7505      */
7506     if (sscanf(message, "Hint: %s", buf1) == 1) {
7507         if (cps == &first && hintRequested) {
7508             hintRequested = FALSE;
7509             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7510                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7511                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7512                                     PosFlags(forwardMostMove),
7513                                     fromY, fromX, toY, toX, promoChar, buf1);
7514                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7515                 DisplayInformation(buf2);
7516             } else {
7517                 /* Hint move could not be parsed!? */
7518               snprintf(buf2, sizeof(buf2),
7519                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7520                         buf1, cps->which);
7521                 DisplayError(buf2, 0);
7522             }
7523         } else {
7524             strcpy(lastHint, buf1);
7525         }
7526         return;
7527     }
7528
7529     /*
7530      * Ignore other messages if game is not in progress
7531      */
7532     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7533         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7534
7535     /*
7536      * look for win, lose, draw, or draw offer
7537      */
7538     if (strncmp(message, "1-0", 3) == 0) {
7539         char *p, *q, *r = "";
7540         p = strchr(message, '{');
7541         if (p) {
7542             q = strchr(p, '}');
7543             if (q) {
7544                 *q = NULLCHAR;
7545                 r = p + 1;
7546             }
7547         }
7548         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7549         return;
7550     } else if (strncmp(message, "0-1", 3) == 0) {
7551         char *p, *q, *r = "";
7552         p = strchr(message, '{');
7553         if (p) {
7554             q = strchr(p, '}');
7555             if (q) {
7556                 *q = NULLCHAR;
7557                 r = p + 1;
7558             }
7559         }
7560         /* Kludge for Arasan 4.1 bug */
7561         if (strcmp(r, "Black resigns") == 0) {
7562             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7563             return;
7564         }
7565         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7566         return;
7567     } else if (strncmp(message, "1/2", 3) == 0) {
7568         char *p, *q, *r = "";
7569         p = strchr(message, '{');
7570         if (p) {
7571             q = strchr(p, '}');
7572             if (q) {
7573                 *q = NULLCHAR;
7574                 r = p + 1;
7575             }
7576         }
7577
7578         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7579         return;
7580
7581     } else if (strncmp(message, "White resign", 12) == 0) {
7582         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7583         return;
7584     } else if (strncmp(message, "Black resign", 12) == 0) {
7585         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7586         return;
7587     } else if (strncmp(message, "White matches", 13) == 0 ||
7588                strncmp(message, "Black matches", 13) == 0   ) {
7589         /* [HGM] ignore GNUShogi noises */
7590         return;
7591     } else if (strncmp(message, "White", 5) == 0 &&
7592                message[5] != '(' &&
7593                StrStr(message, "Black") == NULL) {
7594         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7595         return;
7596     } else if (strncmp(message, "Black", 5) == 0 &&
7597                message[5] != '(') {
7598         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7599         return;
7600     } else if (strcmp(message, "resign") == 0 ||
7601                strcmp(message, "computer resigns") == 0) {
7602         switch (gameMode) {
7603           case MachinePlaysBlack:
7604           case IcsPlayingBlack:
7605             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7606             break;
7607           case MachinePlaysWhite:
7608           case IcsPlayingWhite:
7609             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7610             break;
7611           case TwoMachinesPlay:
7612             if (cps->twoMachinesColor[0] == 'w')
7613               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7614             else
7615               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7616             break;
7617           default:
7618             /* can't happen */
7619             break;
7620         }
7621         return;
7622     } else if (strncmp(message, "opponent mates", 14) == 0) {
7623         switch (gameMode) {
7624           case MachinePlaysBlack:
7625           case IcsPlayingBlack:
7626             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7627             break;
7628           case MachinePlaysWhite:
7629           case IcsPlayingWhite:
7630             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7631             break;
7632           case TwoMachinesPlay:
7633             if (cps->twoMachinesColor[0] == 'w')
7634               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7635             else
7636               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7637             break;
7638           default:
7639             /* can't happen */
7640             break;
7641         }
7642         return;
7643     } else if (strncmp(message, "computer mates", 14) == 0) {
7644         switch (gameMode) {
7645           case MachinePlaysBlack:
7646           case IcsPlayingBlack:
7647             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7648             break;
7649           case MachinePlaysWhite:
7650           case IcsPlayingWhite:
7651             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7652             break;
7653           case TwoMachinesPlay:
7654             if (cps->twoMachinesColor[0] == 'w')
7655               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7656             else
7657               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7658             break;
7659           default:
7660             /* can't happen */
7661             break;
7662         }
7663         return;
7664     } else if (strncmp(message, "checkmate", 9) == 0) {
7665         if (WhiteOnMove(forwardMostMove)) {
7666             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7667         } else {
7668             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7669         }
7670         return;
7671     } else if (strstr(message, "Draw") != NULL ||
7672                strstr(message, "game is a draw") != NULL) {
7673         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7674         return;
7675     } else if (strstr(message, "offer") != NULL &&
7676                strstr(message, "draw") != NULL) {
7677 #if ZIPPY
7678         if (appData.zippyPlay && first.initDone) {
7679             /* Relay offer to ICS */
7680             SendToICS(ics_prefix);
7681             SendToICS("draw\n");
7682         }
7683 #endif
7684         cps->offeredDraw = 2; /* valid until this engine moves twice */
7685         if (gameMode == TwoMachinesPlay) {
7686             if (cps->other->offeredDraw) {
7687                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7688             /* [HGM] in two-machine mode we delay relaying draw offer      */
7689             /* until after we also have move, to see if it is really claim */
7690             }
7691         } else if (gameMode == MachinePlaysWhite ||
7692                    gameMode == MachinePlaysBlack) {
7693           if (userOfferedDraw) {
7694             DisplayInformation(_("Machine accepts your draw offer"));
7695             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7696           } else {
7697             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7698           }
7699         }
7700     }
7701
7702
7703     /*
7704      * Look for thinking output
7705      */
7706     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7707           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7708                                 ) {
7709         int plylev, mvleft, mvtot, curscore, time;
7710         char mvname[MOVE_LEN];
7711         u64 nodes; // [DM]
7712         char plyext;
7713         int ignore = FALSE;
7714         int prefixHint = FALSE;
7715         mvname[0] = NULLCHAR;
7716
7717         switch (gameMode) {
7718           case MachinePlaysBlack:
7719           case IcsPlayingBlack:
7720             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7721             break;
7722           case MachinePlaysWhite:
7723           case IcsPlayingWhite:
7724             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7725             break;
7726           case AnalyzeMode:
7727           case AnalyzeFile:
7728             break;
7729           case IcsObserving: /* [DM] icsEngineAnalyze */
7730             if (!appData.icsEngineAnalyze) ignore = TRUE;
7731             break;
7732           case TwoMachinesPlay:
7733             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7734                 ignore = TRUE;
7735             }
7736             break;
7737           default:
7738             ignore = TRUE;
7739             break;
7740         }
7741
7742         if (!ignore) {
7743             buf1[0] = NULLCHAR;
7744             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7745                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7746
7747                 if (plyext != ' ' && plyext != '\t') {
7748                     time *= 100;
7749                 }
7750
7751                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7752                 if( cps->scoreIsAbsolute && 
7753                     ( gameMode == MachinePlaysBlack ||
7754                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7755                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7756                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7757                      !WhiteOnMove(currentMove)
7758                     ) )
7759                 {
7760                     curscore = -curscore;
7761                 }
7762
7763
7764                 programStats.depth = plylev;
7765                 programStats.nodes = nodes;
7766                 programStats.time = time;
7767                 programStats.score = curscore;
7768                 programStats.got_only_move = 0;
7769
7770                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7771                         int ticklen;
7772
7773                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7774                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7775                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7776                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7777                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7778                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7779                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7780                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7781                 }
7782
7783                 /* Buffer overflow protection */
7784                 if (buf1[0] != NULLCHAR) {
7785                     if (strlen(buf1) >= sizeof(programStats.movelist)
7786                         && appData.debugMode) {
7787                         fprintf(debugFP,
7788                                 "PV is too long; using the first %u bytes.\n",
7789                                 (unsigned) sizeof(programStats.movelist) - 1);
7790                     }
7791
7792                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7793                 } else {
7794                     sprintf(programStats.movelist, " no PV\n");
7795                 }
7796
7797                 if (programStats.seen_stat) {
7798                     programStats.ok_to_send = 1;
7799                 }
7800
7801                 if (strchr(programStats.movelist, '(') != NULL) {
7802                     programStats.line_is_book = 1;
7803                     programStats.nr_moves = 0;
7804                     programStats.moves_left = 0;
7805                 } else {
7806                     programStats.line_is_book = 0;
7807                 }
7808
7809                 SendProgramStatsToFrontend( cps, &programStats );
7810
7811                 /*
7812                     [AS] Protect the thinkOutput buffer from overflow... this
7813                     is only useful if buf1 hasn't overflowed first!
7814                 */
7815                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7816                         plylev,
7817                         (gameMode == TwoMachinesPlay ?
7818                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7819                         ((double) curscore) / 100.0,
7820                         prefixHint ? lastHint : "",
7821                         prefixHint ? " " : "" );
7822
7823                 if( buf1[0] != NULLCHAR ) {
7824                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7825
7826                     if( strlen(buf1) > max_len ) {
7827                         if( appData.debugMode) {
7828                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7829                         }
7830                         buf1[max_len+1] = '\0';
7831                     }
7832
7833                     strcat( thinkOutput, buf1 );
7834                 }
7835
7836                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7837                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7838                     DisplayMove(currentMove - 1);
7839                 }
7840                 return;
7841
7842             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7843                 /* crafty (9.25+) says "(only move) <move>"
7844                  * if there is only 1 legal move
7845                  */
7846                 sscanf(p, "(only move) %s", buf1);
7847                 sprintf(thinkOutput, "%s (only move)", buf1);
7848                 sprintf(programStats.movelist, "%s (only move)", buf1);
7849                 programStats.depth = 1;
7850                 programStats.nr_moves = 1;
7851                 programStats.moves_left = 1;
7852                 programStats.nodes = 1;
7853                 programStats.time = 1;
7854                 programStats.got_only_move = 1;
7855
7856                 /* Not really, but we also use this member to
7857                    mean "line isn't going to change" (Crafty
7858                    isn't searching, so stats won't change) */
7859                 programStats.line_is_book = 1;
7860
7861                 SendProgramStatsToFrontend( cps, &programStats );
7862
7863                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7864                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7865                     DisplayMove(currentMove - 1);
7866                 }
7867                 return;
7868             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7869                               &time, &nodes, &plylev, &mvleft,
7870                               &mvtot, mvname) >= 5) {
7871                 /* The stat01: line is from Crafty (9.29+) in response
7872                    to the "." command */
7873                 programStats.seen_stat = 1;
7874                 cps->maybeThinking = TRUE;
7875
7876                 if (programStats.got_only_move || !appData.periodicUpdates)
7877                   return;
7878
7879                 programStats.depth = plylev;
7880                 programStats.time = time;
7881                 programStats.nodes = nodes;
7882                 programStats.moves_left = mvleft;
7883                 programStats.nr_moves = mvtot;
7884                 strcpy(programStats.move_name, mvname);
7885                 programStats.ok_to_send = 1;
7886                 programStats.movelist[0] = '\0';
7887
7888                 SendProgramStatsToFrontend( cps, &programStats );
7889
7890                 return;
7891
7892             } else if (strncmp(message,"++",2) == 0) {
7893                 /* Crafty 9.29+ outputs this */
7894                 programStats.got_fail = 2;
7895                 return;
7896
7897             } else if (strncmp(message,"--",2) == 0) {
7898                 /* Crafty 9.29+ outputs this */
7899                 programStats.got_fail = 1;
7900                 return;
7901
7902             } else if (thinkOutput[0] != NULLCHAR &&
7903                        strncmp(message, "    ", 4) == 0) {
7904                 unsigned message_len;
7905
7906                 p = message;
7907                 while (*p && *p == ' ') p++;
7908
7909                 message_len = strlen( p );
7910
7911                 /* [AS] Avoid buffer overflow */
7912                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7913                     strcat(thinkOutput, " ");
7914                     strcat(thinkOutput, p);
7915                 }
7916
7917                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7918                     strcat(programStats.movelist, " ");
7919                     strcat(programStats.movelist, p);
7920                 }
7921
7922                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7923                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7924                     DisplayMove(currentMove - 1);
7925                 }
7926                 return;
7927             }
7928         }
7929         else {
7930             buf1[0] = NULLCHAR;
7931
7932             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7933                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7934             {
7935                 ChessProgramStats cpstats;
7936
7937                 if (plyext != ' ' && plyext != '\t') {
7938                     time *= 100;
7939                 }
7940
7941                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7942                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7943                     curscore = -curscore;
7944                 }
7945
7946                 cpstats.depth = plylev;
7947                 cpstats.nodes = nodes;
7948                 cpstats.time = time;
7949                 cpstats.score = curscore;
7950                 cpstats.got_only_move = 0;
7951                 cpstats.movelist[0] = '\0';
7952
7953                 if (buf1[0] != NULLCHAR) {
7954                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7955                 }
7956
7957                 cpstats.ok_to_send = 0;
7958                 cpstats.line_is_book = 0;
7959                 cpstats.nr_moves = 0;
7960                 cpstats.moves_left = 0;
7961
7962                 SendProgramStatsToFrontend( cps, &cpstats );
7963             }
7964         }
7965     }
7966 }
7967
7968
7969 /* Parse a game score from the character string "game", and
7970    record it as the history of the current game.  The game
7971    score is NOT assumed to start from the standard position.
7972    The display is not updated in any way.
7973    */
7974 void
7975 ParseGameHistory(game)
7976      char *game;
7977 {
7978     ChessMove moveType;
7979     int fromX, fromY, toX, toY, boardIndex;
7980     char promoChar;
7981     char *p, *q;
7982     char buf[MSG_SIZ];
7983
7984     if (appData.debugMode)
7985       fprintf(debugFP, "Parsing game history: %s\n", game);
7986
7987     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7988     gameInfo.site = StrSave(appData.icsHost);
7989     gameInfo.date = PGNDate();
7990     gameInfo.round = StrSave("-");
7991
7992     /* Parse out names of players */
7993     while (*game == ' ') game++;
7994     p = buf;
7995     while (*game != ' ') *p++ = *game++;
7996     *p = NULLCHAR;
7997     gameInfo.white = StrSave(buf);
7998     while (*game == ' ') game++;
7999     p = buf;
8000     while (*game != ' ' && *game != '\n') *p++ = *game++;
8001     *p = NULLCHAR;
8002     gameInfo.black = StrSave(buf);
8003
8004     /* Parse moves */
8005     boardIndex = blackPlaysFirst ? 1 : 0;
8006     yynewstr(game);
8007     for (;;) {
8008         yyboardindex = boardIndex;
8009         moveType = (ChessMove) yylex();
8010         switch (moveType) {
8011           case IllegalMove:             /* maybe suicide chess, etc. */
8012   if (appData.debugMode) {
8013     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8014     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8015     setbuf(debugFP, NULL);
8016   }
8017           case WhitePromotionChancellor:
8018           case BlackPromotionChancellor:
8019           case WhitePromotionArchbishop:
8020           case BlackPromotionArchbishop:
8021           case WhitePromotionQueen:
8022           case BlackPromotionQueen:
8023           case WhitePromotionRook:
8024           case BlackPromotionRook:
8025           case WhitePromotionBishop:
8026           case BlackPromotionBishop:
8027           case WhitePromotionKnight:
8028           case BlackPromotionKnight:
8029           case WhitePromotionKing:
8030           case BlackPromotionKing:
8031           case NormalMove:
8032           case WhiteCapturesEnPassant:
8033           case BlackCapturesEnPassant:
8034           case WhiteKingSideCastle:
8035           case WhiteQueenSideCastle:
8036           case BlackKingSideCastle:
8037           case BlackQueenSideCastle:
8038           case WhiteKingSideCastleWild:
8039           case WhiteQueenSideCastleWild:
8040           case BlackKingSideCastleWild:
8041           case BlackQueenSideCastleWild:
8042           /* PUSH Fabien */
8043           case WhiteHSideCastleFR:
8044           case WhiteASideCastleFR:
8045           case BlackHSideCastleFR:
8046           case BlackASideCastleFR:
8047           /* POP Fabien */
8048             fromX = currentMoveString[0] - AAA;
8049             fromY = currentMoveString[1] - ONE;
8050             toX = currentMoveString[2] - AAA;
8051             toY = currentMoveString[3] - ONE;
8052             promoChar = currentMoveString[4];
8053             break;
8054           case WhiteDrop:
8055           case BlackDrop:
8056             fromX = moveType == WhiteDrop ?
8057               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8058             (int) CharToPiece(ToLower(currentMoveString[0]));
8059             fromY = DROP_RANK;
8060             toX = currentMoveString[2] - AAA;
8061             toY = currentMoveString[3] - ONE;
8062             promoChar = NULLCHAR;
8063             break;
8064           case AmbiguousMove:
8065             /* bug? */
8066             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8067   if (appData.debugMode) {
8068     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8069     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8070     setbuf(debugFP, NULL);
8071   }
8072             DisplayError(buf, 0);
8073             return;
8074           case ImpossibleMove:
8075             /* bug? */
8076             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8077   if (appData.debugMode) {
8078     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8079     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8080     setbuf(debugFP, NULL);
8081   }
8082             DisplayError(buf, 0);
8083             return;
8084           case (ChessMove) 0:   /* end of file */
8085             if (boardIndex < backwardMostMove) {
8086                 /* Oops, gap.  How did that happen? */
8087                 DisplayError(_("Gap in move list"), 0);
8088                 return;
8089             }
8090             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8091             if (boardIndex > forwardMostMove) {
8092                 forwardMostMove = boardIndex;
8093             }
8094             return;
8095           case ElapsedTime:
8096             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8097                 strcat(parseList[boardIndex-1], " ");
8098                 strcat(parseList[boardIndex-1], yy_text);
8099             }
8100             continue;
8101           case Comment:
8102           case PGNTag:
8103           case NAG:
8104           default:
8105             /* ignore */
8106             continue;
8107           case WhiteWins:
8108           case BlackWins:
8109           case GameIsDrawn:
8110           case GameUnfinished:
8111             if (gameMode == IcsExamining) {
8112                 if (boardIndex < backwardMostMove) {
8113                     /* Oops, gap.  How did that happen? */
8114                     return;
8115                 }
8116                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8117                 return;
8118             }
8119             gameInfo.result = moveType;
8120             p = strchr(yy_text, '{');
8121             if (p == NULL) p = strchr(yy_text, '(');
8122             if (p == NULL) {
8123                 p = yy_text;
8124                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8125             } else {
8126                 q = strchr(p, *p == '{' ? '}' : ')');
8127                 if (q != NULL) *q = NULLCHAR;
8128                 p++;
8129             }
8130             gameInfo.resultDetails = StrSave(p);
8131             continue;
8132         }
8133         if (boardIndex >= forwardMostMove &&
8134             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8135             backwardMostMove = blackPlaysFirst ? 1 : 0;
8136             return;
8137         }
8138         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8139                                  fromY, fromX, toY, toX, promoChar,
8140                                  parseList[boardIndex]);
8141         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8142         /* currentMoveString is set as a side-effect of yylex */
8143         strcpy(moveList[boardIndex], currentMoveString);
8144         strcat(moveList[boardIndex], "\n");
8145         boardIndex++;
8146         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8147         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8148           case MT_NONE:
8149           case MT_STALEMATE:
8150           default:
8151             break;
8152           case MT_CHECK:
8153             if(gameInfo.variant != VariantShogi)
8154                 strcat(parseList[boardIndex - 1], "+");
8155             break;
8156           case MT_CHECKMATE:
8157           case MT_STAINMATE:
8158             strcat(parseList[boardIndex - 1], "#");
8159             break;
8160         }
8161     }
8162 }
8163
8164
8165 /* Apply a move to the given board  */
8166 void
8167 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8168      int fromX, fromY, toX, toY;
8169      int promoChar;
8170      Board board;
8171 {
8172   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8173   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8174
8175     /* [HGM] compute & store e.p. status and castling rights for new position */
8176     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8177     { int i;
8178
8179       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8180       oldEP = (signed char)board[EP_STATUS];
8181       board[EP_STATUS] = EP_NONE;
8182
8183       if( board[toY][toX] != EmptySquare ) 
8184            board[EP_STATUS] = EP_CAPTURE;  
8185
8186       if( board[fromY][fromX] == WhitePawn ) {
8187            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8188                board[EP_STATUS] = EP_PAWN_MOVE;
8189            if( toY-fromY==2) {
8190                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8191                         gameInfo.variant != VariantBerolina || toX < fromX)
8192                       board[EP_STATUS] = toX | berolina;
8193                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8194                   gameInfo.variant != VariantBerolina || toX > fromX) 
8195                  board[EP_STATUS] = toX;
8196            }
8197       } else
8198       if( board[fromY][fromX] == BlackPawn ) {
8199            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8200                board[EP_STATUS] = EP_PAWN_MOVE; 
8201            if( toY-fromY== -2) {
8202                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8203                         gameInfo.variant != VariantBerolina || toX < fromX)
8204                       board[EP_STATUS] = toX | berolina;
8205                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8206                         gameInfo.variant != VariantBerolina || toX > fromX) 
8207                       board[EP_STATUS] = toX;
8208            }
8209        }
8210
8211        for(i=0; i<nrCastlingRights; i++) {
8212            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8213               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8214              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8215        }
8216
8217     }
8218
8219   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8220   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8221        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8222
8223   if (fromX == toX && fromY == toY) return;
8224
8225   if (fromY == DROP_RANK) {
8226         /* must be first */
8227         piece = board[toY][toX] = (ChessSquare) fromX;
8228   } else {
8229      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8230      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8231      if(gameInfo.variant == VariantKnightmate)
8232          king += (int) WhiteUnicorn - (int) WhiteKing;
8233
8234     /* Code added by Tord: */
8235     /* FRC castling assumed when king captures friendly rook. */
8236     if (board[fromY][fromX] == WhiteKing &&
8237              board[toY][toX] == WhiteRook) {
8238       board[fromY][fromX] = EmptySquare;
8239       board[toY][toX] = EmptySquare;
8240       if(toX > fromX) {
8241         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8242       } else {
8243         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8244       }
8245     } else if (board[fromY][fromX] == BlackKing &&
8246                board[toY][toX] == BlackRook) {
8247       board[fromY][fromX] = EmptySquare;
8248       board[toY][toX] = EmptySquare;
8249       if(toX > fromX) {
8250         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8251       } else {
8252         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8253       }
8254     /* End of code added by Tord */
8255
8256     } else if (board[fromY][fromX] == king
8257         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8258         && toY == fromY && toX > fromX+1) {
8259         board[fromY][fromX] = EmptySquare;
8260         board[toY][toX] = king;
8261         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8262         board[fromY][BOARD_RGHT-1] = EmptySquare;
8263     } else if (board[fromY][fromX] == king
8264         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8265                && toY == fromY && toX < fromX-1) {
8266         board[fromY][fromX] = EmptySquare;
8267         board[toY][toX] = king;
8268         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8269         board[fromY][BOARD_LEFT] = EmptySquare;
8270     } else if (board[fromY][fromX] == WhitePawn
8271                && toY >= BOARD_HEIGHT-promoRank
8272                && gameInfo.variant != VariantXiangqi
8273                ) {
8274         /* white pawn promotion */
8275         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8276         if (board[toY][toX] == EmptySquare) {
8277             board[toY][toX] = WhiteQueen;
8278         }
8279         if(gameInfo.variant==VariantBughouse ||
8280            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8281             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8282         board[fromY][fromX] = EmptySquare;
8283     } else if ((fromY == BOARD_HEIGHT-4)
8284                && (toX != fromX)
8285                && gameInfo.variant != VariantXiangqi
8286                && gameInfo.variant != VariantBerolina
8287                && (board[fromY][fromX] == WhitePawn)
8288                && (board[toY][toX] == EmptySquare)) {
8289         board[fromY][fromX] = EmptySquare;
8290         board[toY][toX] = WhitePawn;
8291         captured = board[toY - 1][toX];
8292         board[toY - 1][toX] = EmptySquare;
8293     } else if ((fromY == BOARD_HEIGHT-4)
8294                && (toX == fromX)
8295                && gameInfo.variant == VariantBerolina
8296                && (board[fromY][fromX] == WhitePawn)
8297                && (board[toY][toX] == EmptySquare)) {
8298         board[fromY][fromX] = EmptySquare;
8299         board[toY][toX] = WhitePawn;
8300         if(oldEP & EP_BEROLIN_A) {
8301                 captured = board[fromY][fromX-1];
8302                 board[fromY][fromX-1] = EmptySquare;
8303         }else{  captured = board[fromY][fromX+1];
8304                 board[fromY][fromX+1] = EmptySquare;
8305         }
8306     } else if (board[fromY][fromX] == king
8307         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8308                && toY == fromY && toX > fromX+1) {
8309         board[fromY][fromX] = EmptySquare;
8310         board[toY][toX] = king;
8311         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8312         board[fromY][BOARD_RGHT-1] = EmptySquare;
8313     } else if (board[fromY][fromX] == king
8314         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8315                && toY == fromY && toX < fromX-1) {
8316         board[fromY][fromX] = EmptySquare;
8317         board[toY][toX] = king;
8318         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8319         board[fromY][BOARD_LEFT] = EmptySquare;
8320     } else if (fromY == 7 && fromX == 3
8321                && board[fromY][fromX] == BlackKing
8322                && toY == 7 && toX == 5) {
8323         board[fromY][fromX] = EmptySquare;
8324         board[toY][toX] = BlackKing;
8325         board[fromY][7] = EmptySquare;
8326         board[toY][4] = BlackRook;
8327     } else if (fromY == 7 && fromX == 3
8328                && board[fromY][fromX] == BlackKing
8329                && toY == 7 && toX == 1) {
8330         board[fromY][fromX] = EmptySquare;
8331         board[toY][toX] = BlackKing;
8332         board[fromY][0] = EmptySquare;
8333         board[toY][2] = BlackRook;
8334     } else if (board[fromY][fromX] == BlackPawn
8335                && toY < promoRank
8336                && gameInfo.variant != VariantXiangqi
8337                ) {
8338         /* black pawn promotion */
8339         board[toY][toX] = CharToPiece(ToLower(promoChar));
8340         if (board[toY][toX] == EmptySquare) {
8341             board[toY][toX] = BlackQueen;
8342         }
8343         if(gameInfo.variant==VariantBughouse ||
8344            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8345             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8346         board[fromY][fromX] = EmptySquare;
8347     } else if ((fromY == 3)
8348                && (toX != fromX)
8349                && gameInfo.variant != VariantXiangqi
8350                && gameInfo.variant != VariantBerolina
8351                && (board[fromY][fromX] == BlackPawn)
8352                && (board[toY][toX] == EmptySquare)) {
8353         board[fromY][fromX] = EmptySquare;
8354         board[toY][toX] = BlackPawn;
8355         captured = board[toY + 1][toX];
8356         board[toY + 1][toX] = EmptySquare;
8357     } else if ((fromY == 3)
8358                && (toX == fromX)
8359                && gameInfo.variant == VariantBerolina
8360                && (board[fromY][fromX] == BlackPawn)
8361                && (board[toY][toX] == EmptySquare)) {
8362         board[fromY][fromX] = EmptySquare;
8363         board[toY][toX] = BlackPawn;
8364         if(oldEP & EP_BEROLIN_A) {
8365                 captured = board[fromY][fromX-1];
8366                 board[fromY][fromX-1] = EmptySquare;
8367         }else{  captured = board[fromY][fromX+1];
8368                 board[fromY][fromX+1] = EmptySquare;
8369         }
8370     } else {
8371         board[toY][toX] = board[fromY][fromX];
8372         board[fromY][fromX] = EmptySquare;
8373     }
8374
8375     /* [HGM] now we promote for Shogi, if needed */
8376     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8377         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8378   }
8379
8380     if (gameInfo.holdingsWidth != 0) {
8381
8382       /* !!A lot more code needs to be written to support holdings  */
8383       /* [HGM] OK, so I have written it. Holdings are stored in the */
8384       /* penultimate board files, so they are automaticlly stored   */
8385       /* in the game history.                                       */
8386       if (fromY == DROP_RANK) {
8387         /* Delete from holdings, by decreasing count */
8388         /* and erasing image if necessary            */
8389         p = (int) fromX;
8390         if(p < (int) BlackPawn) { /* white drop */
8391              p -= (int)WhitePawn;
8392                  p = PieceToNumber((ChessSquare)p);
8393              if(p >= gameInfo.holdingsSize) p = 0;
8394              if(--board[p][BOARD_WIDTH-2] <= 0)
8395                   board[p][BOARD_WIDTH-1] = EmptySquare;
8396              if((int)board[p][BOARD_WIDTH-2] < 0)
8397                         board[p][BOARD_WIDTH-2] = 0;
8398         } else {                  /* black drop */
8399              p -= (int)BlackPawn;
8400                  p = PieceToNumber((ChessSquare)p);
8401              if(p >= gameInfo.holdingsSize) p = 0;
8402              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8403                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8404              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8405                         board[BOARD_HEIGHT-1-p][1] = 0;
8406         }
8407       }
8408       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8409           && gameInfo.variant != VariantBughouse        ) {
8410         /* [HGM] holdings: Add to holdings, if holdings exist */
8411         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8412                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8413                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8414         }
8415         p = (int) captured;
8416         if (p >= (int) BlackPawn) {
8417           p -= (int)BlackPawn;
8418           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8419                   /* in Shogi restore piece to its original  first */
8420                   captured = (ChessSquare) (DEMOTED captured);
8421                   p = DEMOTED p;
8422           }
8423           p = PieceToNumber((ChessSquare)p);
8424           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8425           board[p][BOARD_WIDTH-2]++;
8426           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8427         } else {
8428           p -= (int)WhitePawn;
8429           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8430                   captured = (ChessSquare) (DEMOTED captured);
8431                   p = DEMOTED p;
8432           }
8433           p = PieceToNumber((ChessSquare)p);
8434           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8435           board[BOARD_HEIGHT-1-p][1]++;
8436           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8437         }
8438       }
8439     } else if (gameInfo.variant == VariantAtomic) {
8440       if (captured != EmptySquare) {
8441         int y, x;
8442         for (y = toY-1; y <= toY+1; y++) {
8443           for (x = toX-1; x <= toX+1; x++) {
8444             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8445                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8446               board[y][x] = EmptySquare;
8447             }
8448           }
8449         }
8450         board[toY][toX] = EmptySquare;
8451       }
8452     }
8453     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8454         /* [HGM] Shogi promotions */
8455         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8456     }
8457
8458     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8459                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8460         // [HGM] superchess: take promotion piece out of holdings
8461         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8462         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8463             if(!--board[k][BOARD_WIDTH-2])
8464                 board[k][BOARD_WIDTH-1] = EmptySquare;
8465         } else {
8466             if(!--board[BOARD_HEIGHT-1-k][1])
8467                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8468         }
8469     }
8470
8471 }
8472
8473 /* Updates forwardMostMove */
8474 void
8475 MakeMove(fromX, fromY, toX, toY, promoChar)
8476      int fromX, fromY, toX, toY;
8477      int promoChar;
8478 {
8479 //    forwardMostMove++; // [HGM] bare: moved downstream
8480
8481     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8482         int timeLeft; static int lastLoadFlag=0; int king, piece;
8483         piece = boards[forwardMostMove][fromY][fromX];
8484         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8485         if(gameInfo.variant == VariantKnightmate)
8486             king += (int) WhiteUnicorn - (int) WhiteKing;
8487         if(forwardMostMove == 0) {
8488             if(blackPlaysFirst)
8489                 fprintf(serverMoves, "%s;", second.tidy);
8490             fprintf(serverMoves, "%s;", first.tidy);
8491             if(!blackPlaysFirst)
8492                 fprintf(serverMoves, "%s;", second.tidy);
8493         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8494         lastLoadFlag = loadFlag;
8495         // print base move
8496         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8497         // print castling suffix
8498         if( toY == fromY && piece == king ) {
8499             if(toX-fromX > 1)
8500                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8501             if(fromX-toX >1)
8502                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8503         }
8504         // e.p. suffix
8505         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8506              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8507              boards[forwardMostMove][toY][toX] == EmptySquare
8508              && fromX != toX )
8509                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8510         // promotion suffix
8511         if(promoChar != NULLCHAR)
8512                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8513         if(!loadFlag) {
8514             fprintf(serverMoves, "/%d/%d",
8515                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8516             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8517             else                      timeLeft = blackTimeRemaining/1000;
8518             fprintf(serverMoves, "/%d", timeLeft);
8519         }
8520         fflush(serverMoves);
8521     }
8522
8523     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8524       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8525                         0, 1);
8526       return;
8527     }
8528     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8529     if (commentList[forwardMostMove+1] != NULL) {
8530         free(commentList[forwardMostMove+1]);
8531         commentList[forwardMostMove+1] = NULL;
8532     }
8533     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8534     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8535     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8536     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8537     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8538     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8539     gameInfo.result = GameUnfinished;
8540     if (gameInfo.resultDetails != NULL) {
8541         free(gameInfo.resultDetails);
8542         gameInfo.resultDetails = NULL;
8543     }
8544     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8545                               moveList[forwardMostMove - 1]);
8546     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8547                              PosFlags(forwardMostMove - 1),
8548                              fromY, fromX, toY, toX, promoChar,
8549                              parseList[forwardMostMove - 1]);
8550     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8551       case MT_NONE:
8552       case MT_STALEMATE:
8553       default:
8554         break;
8555       case MT_CHECK:
8556         if(gameInfo.variant != VariantShogi)
8557             strcat(parseList[forwardMostMove - 1], "+");
8558         break;
8559       case MT_CHECKMATE:
8560       case MT_STAINMATE:
8561         strcat(parseList[forwardMostMove - 1], "#");
8562         break;
8563     }
8564     if (appData.debugMode) {
8565         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8566     }
8567
8568 }
8569
8570 /* Updates currentMove if not pausing */
8571 void
8572 ShowMove(fromX, fromY, toX, toY)
8573 {
8574     int instant = (gameMode == PlayFromGameFile) ?
8575         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8576
8577     if(appData.noGUI) return;
8578
8579     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8580       {
8581         if (!instant)
8582           {
8583             if (forwardMostMove == currentMove + 1)
8584               {
8585 //TODO
8586 //              AnimateMove(boards[forwardMostMove - 1],
8587 //                          fromX, fromY, toX, toY);
8588               }
8589             if (appData.highlightLastMove)
8590               {
8591                 SetHighlights(fromX, fromY, toX, toY);
8592               }
8593           }
8594         currentMove = forwardMostMove;
8595     }
8596
8597     if (instant) return;
8598
8599     DisplayMove(currentMove - 1);
8600     DrawPosition(FALSE, boards[currentMove]);
8601     DisplayBothClocks();
8602     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8603
8604     return;
8605 }
8606
8607 void SendEgtPath(ChessProgramState *cps)
8608 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8609         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8610
8611         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8612
8613         while(*p) {
8614             char c, *q = name+1, *r, *s;
8615
8616             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8617             while(*p && *p != ',') *q++ = *p++;
8618             *q++ = ':'; *q = 0;
8619             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8620                 strcmp(name, ",nalimov:") == 0 ) {
8621                 // take nalimov path from the menu-changeable option first, if it is defined
8622                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8623                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8624             } else
8625             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8626                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8627                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8628                 s = r = StrStr(s, ":") + 1; // beginning of path info
8629                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8630                 c = *r; *r = 0;             // temporarily null-terminate path info
8631                     *--q = 0;               // strip of trailig ':' from name
8632                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8633                 *r = c;
8634                 SendToProgram(buf,cps);     // send egtbpath command for this format
8635             }
8636             if(*p == ',') p++; // read away comma to position for next format name
8637         }
8638 }
8639
8640 void
8641 InitChessProgram(cps, setup)
8642      ChessProgramState *cps;
8643      int setup; /* [HGM] needed to setup FRC opening position */
8644 {
8645     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8646     if (appData.noChessProgram) return;
8647     hintRequested = FALSE;
8648     bookRequested = FALSE;
8649
8650     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8651     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8652     if(cps->memSize) { /* [HGM] memory */
8653         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8654         SendToProgram(buf, cps);
8655     }
8656     SendEgtPath(cps); /* [HGM] EGT */
8657     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8658         sprintf(buf, "cores %d\n", appData.smpCores);
8659         SendToProgram(buf, cps);
8660     }
8661
8662     SendToProgram(cps->initString, cps);
8663     if (gameInfo.variant != VariantNormal &&
8664         gameInfo.variant != VariantLoadable
8665         /* [HGM] also send variant if board size non-standard */
8666         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8667                                             ) {
8668       char *v = VariantName(gameInfo.variant);
8669       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8670         /* [HGM] in protocol 1 we have to assume all variants valid */
8671         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8672         DisplayFatalError(buf, 0, 1);
8673         return;
8674       }
8675
8676       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8677       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8678       if( gameInfo.variant == VariantXiangqi )
8679            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8680       if( gameInfo.variant == VariantShogi )
8681            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8682       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8683            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8684       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8685                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8686            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8687       if( gameInfo.variant == VariantCourier )
8688            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8689       if( gameInfo.variant == VariantSuper )
8690            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8691       if( gameInfo.variant == VariantGreat )
8692            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8693
8694       if(overruled) {
8695            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8696                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8697            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8698            if(StrStr(cps->variants, b) == NULL) {
8699                // specific sized variant not known, check if general sizing allowed
8700                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8701                    if(StrStr(cps->variants, "boardsize") == NULL) {
8702                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8703                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8704                        DisplayFatalError(buf, 0, 1);
8705                        return;
8706                    }
8707                    /* [HGM] here we really should compare with the maximum supported board size */
8708                }
8709            }
8710       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8711       sprintf(buf, "variant %s\n", b);
8712       SendToProgram(buf, cps);
8713     }
8714     currentlyInitializedVariant = gameInfo.variant;
8715
8716     /* [HGM] send opening position in FRC to first engine */
8717     if(setup) {
8718           SendToProgram("force\n", cps);
8719           SendBoard(cps, 0);
8720           /* engine is now in force mode! Set flag to wake it up after first move. */
8721           setboardSpoiledMachineBlack = 1;
8722     }
8723
8724     if (cps->sendICS) {
8725       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8726       SendToProgram(buf, cps);
8727     }
8728     cps->maybeThinking = FALSE;
8729     cps->offeredDraw = 0;
8730     if (!appData.icsActive) {
8731         SendTimeControl(cps, movesPerSession, timeControl,
8732                         timeIncrement, appData.searchDepth,
8733                         searchTime);
8734     }
8735     if (appData.showThinking
8736         // [HGM] thinking: four options require thinking output to be sent
8737         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8738                                 ) {
8739         SendToProgram("post\n", cps);
8740     }
8741     SendToProgram("hard\n", cps);
8742     if (!appData.ponderNextMove) {
8743         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8744            it without being sure what state we are in first.  "hard"
8745            is not a toggle, so that one is OK.
8746          */
8747         SendToProgram("easy\n", cps);
8748     }
8749     if (cps->usePing) {
8750       sprintf(buf, "ping %d\n", ++cps->lastPing);
8751       SendToProgram(buf, cps);
8752     }
8753     cps->initDone = TRUE;
8754 }
8755
8756
8757 void
8758 StartChessProgram(cps)
8759      ChessProgramState *cps;
8760 {
8761     char buf[MSG_SIZ];
8762     int err;
8763
8764     if (appData.noChessProgram) return;
8765     cps->initDone = FALSE;
8766
8767     if (strcmp(cps->host, "localhost") == 0) {
8768         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8769     } else if (*appData.remoteShell == NULLCHAR) {
8770         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8771     } else {
8772         if (*appData.remoteUser == NULLCHAR) {
8773           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8774                     cps->program);
8775         } else {
8776           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8777                     cps->host, appData.remoteUser, cps->program);
8778         }
8779         err = StartChildProcess(buf, "", &cps->pr);
8780     }
8781
8782     if (err != 0) {
8783         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8784         DisplayFatalError(buf, err, 1);
8785         cps->pr = NoProc;
8786         cps->isr = NULL;
8787         return;
8788     }
8789
8790     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8791     if (cps->protocolVersion > 1) {
8792       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8793       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8794       cps->comboCnt = 0;  //                and values of combo boxes
8795       SendToProgram(buf, cps);
8796     } else {
8797       SendToProgram("xboard\n", cps);
8798     }
8799 }
8800
8801
8802 void
8803 TwoMachinesEventIfReady P((void))
8804 {
8805   if (first.lastPing != first.lastPong) {
8806     DisplayMessage("", _("Waiting for first chess program"));
8807     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8808     return;
8809   }
8810   if (second.lastPing != second.lastPong) {
8811     DisplayMessage("", _("Waiting for second chess program"));
8812     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8813     return;
8814   }
8815   ThawUI();
8816   TwoMachinesEvent();
8817 }
8818
8819 void
8820 NextMatchGame P((void))
8821 {
8822     int index; /* [HGM] autoinc: step load index during match */
8823     Reset(FALSE, TRUE);
8824     if (*appData.loadGameFile != NULLCHAR) {
8825         index = appData.loadGameIndex;
8826         if(index < 0) { // [HGM] autoinc
8827             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8828             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8829         }
8830         LoadGameFromFile(appData.loadGameFile,
8831                          index,
8832                          appData.loadGameFile, FALSE);
8833     } else if (*appData.loadPositionFile != NULLCHAR) {
8834         index = appData.loadPositionIndex;
8835         if(index < 0) { // [HGM] autoinc
8836             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8837             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8838         }
8839         LoadPositionFromFile(appData.loadPositionFile,
8840                              index,
8841                              appData.loadPositionFile);
8842     }
8843     TwoMachinesEventIfReady();
8844 }
8845
8846 void UserAdjudicationEvent( int result )
8847 {
8848     ChessMove gameResult = GameIsDrawn;
8849
8850     if( result > 0 ) {
8851         gameResult = WhiteWins;
8852     }
8853     else if( result < 0 ) {
8854         gameResult = BlackWins;
8855     }
8856
8857     if( gameMode == TwoMachinesPlay ) {
8858         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8859     }
8860 }
8861
8862
8863 // [HGM] save: calculate checksum of game to make games easily identifiable
8864 int StringCheckSum(char *s)
8865 {
8866         int i = 0;
8867         if(s==NULL) return 0;
8868         while(*s) i = i*259 + *s++;
8869         return i;
8870 }
8871
8872 int GameCheckSum()
8873 {
8874         int i, sum=0;
8875         for(i=backwardMostMove; i<forwardMostMove; i++) {
8876                 sum += pvInfoList[i].depth;
8877                 sum += StringCheckSum(parseList[i]);
8878                 sum += StringCheckSum(commentList[i]);
8879                 sum *= 261;
8880         }
8881         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8882         return sum + StringCheckSum(commentList[i]);
8883 } // end of save patch
8884
8885 void
8886 GameEnds(result, resultDetails, whosays)
8887      ChessMove result;
8888      char *resultDetails;
8889      int whosays;
8890 {
8891     GameMode nextGameMode;
8892     int isIcsGame;
8893     char buf[MSG_SIZ];
8894
8895     if(endingGame) return; /* [HGM] crash: forbid recursion */
8896     endingGame = 1;
8897
8898     if (appData.debugMode) {
8899       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8900               result, resultDetails ? resultDetails : "(null)", whosays);
8901     }
8902
8903     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8904         /* If we are playing on ICS, the server decides when the
8905            game is over, but the engine can offer to draw, claim
8906            a draw, or resign.
8907          */
8908 #if ZIPPY
8909         if (appData.zippyPlay && first.initDone) {
8910             if (result == GameIsDrawn) {
8911                 /* In case draw still needs to be claimed */
8912                 SendToICS(ics_prefix);
8913                 SendToICS("draw\n");
8914             } else if (StrCaseStr(resultDetails, "resign")) {
8915                 SendToICS(ics_prefix);
8916                 SendToICS("resign\n");
8917             }
8918         }
8919 #endif
8920         endingGame = 0; /* [HGM] crash */
8921         return;
8922     }
8923
8924     /* If we're loading the game from a file, stop */
8925     if (whosays == GE_FILE) {
8926       (void) StopLoadGameTimer();
8927       gameFileFP = NULL;
8928     }
8929
8930     /* Cancel draw offers */
8931     first.offeredDraw = second.offeredDraw = 0;
8932
8933     /* If this is an ICS game, only ICS can really say it's done;
8934        if not, anyone can. */
8935     isIcsGame = (gameMode == IcsPlayingWhite ||
8936                  gameMode == IcsPlayingBlack ||
8937                  gameMode == IcsObserving    ||
8938                  gameMode == IcsExamining);
8939
8940     if (!isIcsGame || whosays == GE_ICS) {
8941         /* OK -- not an ICS game, or ICS said it was done */
8942         StopClocks();
8943         if (!isIcsGame && !appData.noChessProgram)
8944           SetUserThinkingEnables();
8945
8946         /* [HGM] if a machine claims the game end we verify this claim */
8947         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8948             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8949                 char claimer;
8950                 ChessMove trueResult = (ChessMove) -1;
8951
8952                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8953                                             first.twoMachinesColor[0] :
8954                                             second.twoMachinesColor[0] ;
8955
8956                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8957                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8958                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8959                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8960                 } else
8961                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8962                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8963                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8964                 } else
8965                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8966                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8967                 }
8968
8969                 // now verify win claims, but not in drop games, as we don't understand those yet
8970                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8971                                                  || gameInfo.variant == VariantGreat) &&
8972                     (result == WhiteWins && claimer == 'w' ||
8973                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8974                       if (appData.debugMode) {
8975                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8976                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8977                       }
8978                       if(result != trueResult) {
8979                               sprintf(buf, "False win claim: '%s'", resultDetails);
8980                               result = claimer == 'w' ? BlackWins : WhiteWins;
8981                               resultDetails = buf;
8982                       }
8983                 } else
8984                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8985                     && (forwardMostMove <= backwardMostMove ||
8986                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8987                         (claimer=='b')==(forwardMostMove&1))
8988                                                                                   ) {
8989                       /* [HGM] verify: draws that were not flagged are false claims */
8990                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8991                       result = claimer == 'w' ? BlackWins : WhiteWins;
8992                       resultDetails = buf;
8993                 }
8994                 /* (Claiming a loss is accepted no questions asked!) */
8995             }
8996
8997             /* [HGM] bare: don't allow bare King to win */
8998             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8999                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9000                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9001                && result != GameIsDrawn)
9002             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9003                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9004                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9005                         if(p >= 0 && p <= (int)WhiteKing) k++;
9006                 }
9007                 if (appData.debugMode) {
9008                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9009                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9010                 }
9011                 if(k <= 1) {
9012                         result = GameIsDrawn;
9013                         sprintf(buf, "%s but bare king", resultDetails);
9014                         resultDetails = buf;
9015                 }
9016             }
9017         }
9018
9019         if(serverMoves != NULL && !loadFlag) { char c = '=';
9020             if(result==WhiteWins) c = '+';
9021             if(result==BlackWins) c = '-';
9022             if(resultDetails != NULL)
9023                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9024         }
9025         if (resultDetails != NULL) {
9026             gameInfo.result = result;
9027             gameInfo.resultDetails = StrSave(resultDetails);
9028
9029             /* display last move only if game was not loaded from file */
9030             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9031                 DisplayMove(currentMove - 1);
9032
9033             if (forwardMostMove != 0) {
9034                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9035                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9036                                                                 ) {
9037                     if (*appData.saveGameFile != NULLCHAR) {
9038                         SaveGameToFile(appData.saveGameFile, TRUE);
9039                     } else if (appData.autoSaveGames) {
9040                         AutoSaveGame();
9041                     }
9042                     if (*appData.savePositionFile != NULLCHAR) {
9043                         SavePositionToFile(appData.savePositionFile);
9044                     }
9045                 }
9046             }
9047
9048             /* Tell program how game ended in case it is learning */
9049             /* [HGM] Moved this to after saving the PGN, just in case */
9050             /* engine died and we got here through time loss. In that */
9051             /* case we will get a fatal error writing the pipe, which */
9052             /* would otherwise lose us the PGN.                       */
9053             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9054             /* output during GameEnds should never be fatal anymore   */
9055             if (gameMode == MachinePlaysWhite ||
9056                 gameMode == MachinePlaysBlack ||
9057                 gameMode == TwoMachinesPlay ||
9058                 gameMode == IcsPlayingWhite ||
9059                 gameMode == IcsPlayingBlack ||
9060                 gameMode == BeginningOfGame) {
9061                 char buf[MSG_SIZ];
9062                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9063                         resultDetails);
9064                 if (first.pr != NoProc) {
9065                     SendToProgram(buf, &first);
9066                 }
9067                 if (second.pr != NoProc &&
9068                     gameMode == TwoMachinesPlay) {
9069                     SendToProgram(buf, &second);
9070                 }
9071             }
9072         }
9073
9074         if (appData.icsActive) {
9075             if (appData.quietPlay &&
9076                 (gameMode == IcsPlayingWhite ||
9077                  gameMode == IcsPlayingBlack)) {
9078                 SendToICS(ics_prefix);
9079                 SendToICS("set shout 1\n");
9080             }
9081             nextGameMode = IcsIdle;
9082             ics_user_moved = FALSE;
9083             /* clean up premove.  It's ugly when the game has ended and the
9084              * premove highlights are still on the board.
9085              */
9086             if (gotPremove) {
9087               gotPremove = FALSE;
9088               ClearPremoveHighlights();
9089               DrawPosition(FALSE, boards[currentMove]);
9090             }
9091             if (whosays == GE_ICS) {
9092                 switch (result) {
9093                 case WhiteWins:
9094                     if (gameMode == IcsPlayingWhite)
9095                         PlayIcsWinSound();
9096                     else if(gameMode == IcsPlayingBlack)
9097                         PlayIcsLossSound();
9098                     break;
9099                 case BlackWins:
9100                     if (gameMode == IcsPlayingBlack)
9101                         PlayIcsWinSound();
9102                     else if(gameMode == IcsPlayingWhite)
9103                         PlayIcsLossSound();
9104                     break;
9105                 case GameIsDrawn:
9106                     PlayIcsDrawSound();
9107                     break;
9108                 default:
9109                     PlayIcsUnfinishedSound();
9110                 }
9111             }
9112         } else if (gameMode == EditGame ||
9113                    gameMode == PlayFromGameFile ||
9114                    gameMode == AnalyzeMode ||
9115                    gameMode == AnalyzeFile) {
9116             nextGameMode = gameMode;
9117         } else {
9118             nextGameMode = EndOfGame;
9119         }
9120         pausing = FALSE;
9121         ModeHighlight();
9122     } else {
9123         nextGameMode = gameMode;
9124     }
9125
9126     if (appData.noChessProgram) {
9127         gameMode = nextGameMode;
9128         ModeHighlight();
9129         endingGame = 0; /* [HGM] crash */
9130         return;
9131     }
9132
9133     if (first.reuse) {
9134         /* Put first chess program into idle state */
9135         if (first.pr != NoProc &&
9136             (gameMode == MachinePlaysWhite ||
9137              gameMode == MachinePlaysBlack ||
9138              gameMode == TwoMachinesPlay ||
9139              gameMode == IcsPlayingWhite ||
9140              gameMode == IcsPlayingBlack ||
9141              gameMode == BeginningOfGame)) {
9142             SendToProgram("force\n", &first);
9143             if (first.usePing) {
9144               char buf[MSG_SIZ];
9145               sprintf(buf, "ping %d\n", ++first.lastPing);
9146               SendToProgram(buf, &first);
9147             }
9148         }
9149     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9150         /* Kill off first chess program */
9151         if (first.isr != NULL)
9152           RemoveInputSource(first.isr);
9153         first.isr = NULL;
9154
9155         if (first.pr != NoProc) {
9156             ExitAnalyzeMode();
9157             DoSleep( appData.delayBeforeQuit );
9158             SendToProgram("quit\n", &first);
9159             DoSleep( appData.delayAfterQuit );
9160             DestroyChildProcess(first.pr, first.useSigterm);
9161         }
9162         first.pr = NoProc;
9163     }
9164     if (second.reuse) {
9165         /* Put second chess program into idle state */
9166         if (second.pr != NoProc &&
9167             gameMode == TwoMachinesPlay) {
9168             SendToProgram("force\n", &second);
9169             if (second.usePing) {
9170               char buf[MSG_SIZ];
9171               sprintf(buf, "ping %d\n", ++second.lastPing);
9172               SendToProgram(buf, &second);
9173             }
9174         }
9175     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9176         /* Kill off second chess program */
9177         if (second.isr != NULL)
9178           RemoveInputSource(second.isr);
9179         second.isr = NULL;
9180
9181         if (second.pr != NoProc) {
9182             DoSleep( appData.delayBeforeQuit );
9183             SendToProgram("quit\n", &second);
9184             DoSleep( appData.delayAfterQuit );
9185             DestroyChildProcess(second.pr, second.useSigterm);
9186         }
9187         second.pr = NoProc;
9188     }
9189
9190     if (matchMode && gameMode == TwoMachinesPlay) {
9191         switch (result) {
9192         case WhiteWins:
9193           if (first.twoMachinesColor[0] == 'w') {
9194             first.matchWins++;
9195           } else {
9196             second.matchWins++;
9197           }
9198           break;
9199         case BlackWins:
9200           if (first.twoMachinesColor[0] == 'b') {
9201             first.matchWins++;
9202           } else {
9203             second.matchWins++;
9204           }
9205           break;
9206         default:
9207           break;
9208         }
9209         if (matchGame < appData.matchGames) {
9210             char *tmp;
9211             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9212                 tmp = first.twoMachinesColor;
9213                 first.twoMachinesColor = second.twoMachinesColor;
9214                 second.twoMachinesColor = tmp;
9215             }
9216             gameMode = nextGameMode;
9217             matchGame++;
9218             if(appData.matchPause>10000 || appData.matchPause<10)
9219                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9220             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9221             endingGame = 0; /* [HGM] crash */
9222             return;
9223         } else {
9224             char buf[MSG_SIZ];
9225             gameMode = nextGameMode;
9226             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9227                     first.tidy, second.tidy,
9228                     first.matchWins, second.matchWins,
9229                     appData.matchGames - (first.matchWins + second.matchWins));
9230             DisplayFatalError(buf, 0, 0);
9231         }
9232     }
9233     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9234         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9235       ExitAnalyzeMode();
9236     gameMode = nextGameMode;
9237     ModeHighlight();
9238     endingGame = 0;  /* [HGM] crash */
9239 }
9240
9241 /* Assumes program was just initialized (initString sent).
9242    Leaves program in force mode. */
9243 void
9244 FeedMovesToProgram(cps, upto)
9245      ChessProgramState *cps;
9246      int upto;
9247 {
9248     int i;
9249
9250     if (appData.debugMode)
9251       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9252               startedFromSetupPosition ? "position and " : "",
9253               backwardMostMove, upto, cps->which);
9254     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9255         // [HGM] variantswitch: make engine aware of new variant
9256         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9257                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9258         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9259         SendToProgram(buf, cps);
9260         currentlyInitializedVariant = gameInfo.variant;
9261     }
9262     SendToProgram("force\n", cps);
9263     if (startedFromSetupPosition) {
9264         SendBoard(cps, backwardMostMove);
9265     if (appData.debugMode) {
9266         fprintf(debugFP, "feedMoves\n");
9267     }
9268     }
9269     for (i = backwardMostMove; i < upto; i++) {
9270         SendMoveToProgram(i, cps);
9271     }
9272 }
9273
9274
9275 void
9276 ResurrectChessProgram()
9277 {
9278      /* The chess program may have exited.
9279         If so, restart it and feed it all the moves made so far. */
9280
9281     if (appData.noChessProgram || first.pr != NoProc) return;
9282
9283     StartChessProgram(&first);
9284     InitChessProgram(&first, FALSE);
9285     FeedMovesToProgram(&first, currentMove);
9286
9287     if (!first.sendTime) {
9288         /* can't tell gnuchess what its clock should read,
9289            so we bow to its notion. */
9290         ResetClocks();
9291         timeRemaining[0][currentMove] = whiteTimeRemaining;
9292         timeRemaining[1][currentMove] = blackTimeRemaining;
9293     }
9294
9295     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9296                 appData.icsEngineAnalyze) && first.analysisSupport) {
9297       SendToProgram("analyze\n", &first);
9298       first.analyzing = TRUE;
9299     }
9300 }
9301
9302 /*
9303  * Button procedures
9304  */
9305 void
9306 Reset(redraw, init)
9307      int redraw, init;
9308 {
9309     int i;
9310
9311     if (appData.debugMode) {
9312         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9313                 redraw, init, gameMode);
9314     }
9315     CleanupTail(); // [HGM] vari: delete any stored variations
9316     pausing = pauseExamInvalid = FALSE;
9317     startedFromSetupPosition = blackPlaysFirst = FALSE;
9318     firstMove = TRUE;
9319     whiteFlag = blackFlag = FALSE;
9320     userOfferedDraw = FALSE;
9321     hintRequested = bookRequested = FALSE;
9322     first.maybeThinking = FALSE;
9323     second.maybeThinking = FALSE;
9324     first.bookSuspend = FALSE; // [HGM] book
9325     second.bookSuspend = FALSE;
9326     thinkOutput[0] = NULLCHAR;
9327     lastHint[0] = NULLCHAR;
9328     ClearGameInfo(&gameInfo);
9329     gameInfo.variant = StringToVariant(appData.variant);
9330     ics_user_moved = ics_clock_paused = FALSE;
9331     ics_getting_history = H_FALSE;
9332     ics_gamenum = -1;
9333     white_holding[0] = black_holding[0] = NULLCHAR;
9334     ClearProgramStats();
9335     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9336
9337     ResetFrontEnd();
9338     ClearHighlights();
9339     flipView = appData.flipView;
9340     ClearPremoveHighlights();
9341     gotPremove = FALSE;
9342     alarmSounded = FALSE;
9343
9344     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9345     if(appData.serverMovesName != NULL) {
9346         /* [HGM] prepare to make moves file for broadcasting */
9347         clock_t t = clock();
9348         if(serverMoves != NULL) fclose(serverMoves);
9349         serverMoves = fopen(appData.serverMovesName, "r");
9350         if(serverMoves != NULL) {
9351             fclose(serverMoves);
9352             /* delay 15 sec before overwriting, so all clients can see end */
9353             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9354         }
9355         serverMoves = fopen(appData.serverMovesName, "w");
9356     }
9357
9358     ExitAnalyzeMode();
9359     gameMode = BeginningOfGame;
9360     ModeHighlight();
9361
9362     if(appData.icsActive) gameInfo.variant = VariantNormal;
9363     currentMove = forwardMostMove = backwardMostMove = 0;
9364     InitPosition(redraw);
9365     for (i = 0; i < MAX_MOVES; i++) {
9366         if (commentList[i] != NULL) {
9367             free(commentList[i]);
9368             commentList[i] = NULL;
9369         }
9370     }
9371
9372     ResetClocks();
9373     timeRemaining[0][0] = whiteTimeRemaining;
9374     timeRemaining[1][0] = blackTimeRemaining;
9375     if (first.pr == NULL) {
9376         StartChessProgram(&first);
9377     }
9378     if (init) {
9379             InitChessProgram(&first, startedFromSetupPosition);
9380     }
9381
9382     DisplayTitle("");
9383     DisplayMessage("", "");
9384     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9385     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9386     return;
9387 }
9388
9389 void
9390 AutoPlayGameLoop()
9391 {
9392     for (;;) {
9393         if (!AutoPlayOneMove())
9394           return;
9395         if (matchMode || appData.timeDelay == 0)
9396           continue;
9397         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9398           return;
9399         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9400         break;
9401     }
9402 }
9403
9404
9405 int
9406 AutoPlayOneMove()
9407 {
9408     int fromX, fromY, toX, toY;
9409
9410     if (appData.debugMode) {
9411       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9412     }
9413
9414     if (gameMode != PlayFromGameFile)
9415       return FALSE;
9416
9417     if (currentMove >= forwardMostMove) {
9418       gameMode = EditGame;
9419       ModeHighlight();
9420
9421       /* [AS] Clear current move marker at the end of a game */
9422       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9423
9424       return FALSE;
9425     }
9426
9427     toX = moveList[currentMove][2] - AAA;
9428     toY = moveList[currentMove][3] - ONE;
9429
9430     if (moveList[currentMove][1] == '@') {
9431         if (appData.highlightLastMove) {
9432             SetHighlights(-1, -1, toX, toY);
9433         }
9434     } else {
9435         fromX = moveList[currentMove][0] - AAA;
9436         fromY = moveList[currentMove][1] - ONE;
9437
9438         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9439
9440         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9441
9442         if (appData.highlightLastMove) {
9443             SetHighlights(fromX, fromY, toX, toY);
9444         }
9445     }
9446     DisplayMove(currentMove);
9447     SendMoveToProgram(currentMove++, &first);
9448     DisplayBothClocks();
9449     DrawPosition(FALSE, boards[currentMove]);
9450     // [HGM] PV info: always display, routine tests if empty
9451     DisplayComment(currentMove - 1, commentList[currentMove]);
9452     return TRUE;
9453 }
9454
9455
9456 int
9457 LoadGameOneMove(readAhead)
9458      ChessMove readAhead;
9459 {
9460     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9461     char promoChar = NULLCHAR;
9462     ChessMove moveType;
9463     char move[MSG_SIZ];
9464     char *p, *q;
9465
9466     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9467         gameMode != AnalyzeMode && gameMode != Training) {
9468         gameFileFP = NULL;
9469         return FALSE;
9470     }
9471
9472     yyboardindex = forwardMostMove;
9473     if (readAhead != (ChessMove)0) {
9474       moveType = readAhead;
9475     } else {
9476       if (gameFileFP == NULL)
9477           return FALSE;
9478       moveType = (ChessMove) yylex();
9479     }
9480
9481     done = FALSE;
9482     switch (moveType) {
9483       case Comment:
9484         if (appData.debugMode)
9485           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9486         p = yy_text;
9487
9488         /* append the comment but don't display it */
9489         AppendComment(currentMove, p, FALSE);
9490         return TRUE;
9491
9492       case WhiteCapturesEnPassant:
9493       case BlackCapturesEnPassant:
9494       case WhitePromotionChancellor:
9495       case BlackPromotionChancellor:
9496       case WhitePromotionArchbishop:
9497       case BlackPromotionArchbishop:
9498       case WhitePromotionCentaur:
9499       case BlackPromotionCentaur:
9500       case WhitePromotionQueen:
9501       case BlackPromotionQueen:
9502       case WhitePromotionRook:
9503       case BlackPromotionRook:
9504       case WhitePromotionBishop:
9505       case BlackPromotionBishop:
9506       case WhitePromotionKnight:
9507       case BlackPromotionKnight:
9508       case WhitePromotionKing:
9509       case BlackPromotionKing:
9510       case NormalMove:
9511       case WhiteKingSideCastle:
9512       case WhiteQueenSideCastle:
9513       case BlackKingSideCastle:
9514       case BlackQueenSideCastle:
9515       case WhiteKingSideCastleWild:
9516       case WhiteQueenSideCastleWild:
9517       case BlackKingSideCastleWild:
9518       case BlackQueenSideCastleWild:
9519       /* PUSH Fabien */
9520       case WhiteHSideCastleFR:
9521       case WhiteASideCastleFR:
9522       case BlackHSideCastleFR:
9523       case BlackASideCastleFR:
9524       /* POP Fabien */
9525         if (appData.debugMode)
9526           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9527         fromX = currentMoveString[0] - AAA;
9528         fromY = currentMoveString[1] - ONE;
9529         toX = currentMoveString[2] - AAA;
9530         toY = currentMoveString[3] - ONE;
9531         promoChar = currentMoveString[4];
9532         break;
9533
9534       case WhiteDrop:
9535       case BlackDrop:
9536         if (appData.debugMode)
9537           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9538         fromX = moveType == WhiteDrop ?
9539           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9540         (int) CharToPiece(ToLower(currentMoveString[0]));
9541         fromY = DROP_RANK;
9542         toX = currentMoveString[2] - AAA;
9543         toY = currentMoveString[3] - ONE;
9544         break;
9545
9546       case WhiteWins:
9547       case BlackWins:
9548       case GameIsDrawn:
9549       case GameUnfinished:
9550         if (appData.debugMode)
9551           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9552         p = strchr(yy_text, '{');
9553         if (p == NULL) p = strchr(yy_text, '(');
9554         if (p == NULL) {
9555             p = yy_text;
9556             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9557         } else {
9558             q = strchr(p, *p == '{' ? '}' : ')');
9559             if (q != NULL) *q = NULLCHAR;
9560             p++;
9561         }
9562         GameEnds(moveType, p, GE_FILE);
9563         done = TRUE;
9564         if (cmailMsgLoaded) {
9565             ClearHighlights();
9566             flipView = WhiteOnMove(currentMove);
9567             if (moveType == GameUnfinished) flipView = !flipView;
9568             if (appData.debugMode)
9569               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9570         }
9571         break;
9572
9573       case (ChessMove) 0:       /* end of file */
9574         if (appData.debugMode)
9575           fprintf(debugFP, "Parser hit end of file\n");
9576         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9577           case MT_NONE:
9578           case MT_CHECK:
9579             break;
9580           case MT_CHECKMATE:
9581           case MT_STAINMATE:
9582             if (WhiteOnMove(currentMove)) {
9583                 GameEnds(BlackWins, "Black mates", GE_FILE);
9584             } else {
9585                 GameEnds(WhiteWins, "White mates", GE_FILE);
9586             }
9587             break;
9588           case MT_STALEMATE:
9589             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9590             break;
9591         }
9592         done = TRUE;
9593         break;
9594
9595       case MoveNumberOne:
9596         if (lastLoadGameStart == GNUChessGame) {
9597             /* GNUChessGames have numbers, but they aren't move numbers */
9598             if (appData.debugMode)
9599               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9600                       yy_text, (int) moveType);
9601             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9602         }
9603         /* else fall thru */
9604
9605       case XBoardGame:
9606       case GNUChessGame:
9607       case PGNTag:
9608         /* Reached start of next game in file */
9609         if (appData.debugMode)
9610           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9611         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9612           case MT_NONE:
9613           case MT_CHECK:
9614             break;
9615           case MT_CHECKMATE:
9616           case MT_STAINMATE:
9617             if (WhiteOnMove(currentMove)) {
9618                 GameEnds(BlackWins, "Black mates", GE_FILE);
9619             } else {
9620                 GameEnds(WhiteWins, "White mates", GE_FILE);
9621             }
9622             break;
9623           case MT_STALEMATE:
9624             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9625             break;
9626         }
9627         done = TRUE;
9628         break;
9629
9630       case PositionDiagram:     /* should not happen; ignore */
9631       case ElapsedTime:         /* ignore */
9632       case NAG:                 /* ignore */
9633         if (appData.debugMode)
9634           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9635                   yy_text, (int) moveType);
9636         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9637
9638       case IllegalMove:
9639         if (appData.testLegality) {
9640             if (appData.debugMode)
9641               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9642             sprintf(move, _("Illegal move: %d.%s%s"),
9643                     (forwardMostMove / 2) + 1,
9644                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9645             DisplayError(move, 0);
9646             done = TRUE;
9647         } else {
9648             if (appData.debugMode)
9649               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9650                       yy_text, currentMoveString);
9651             fromX = currentMoveString[0] - AAA;
9652             fromY = currentMoveString[1] - ONE;
9653             toX = currentMoveString[2] - AAA;
9654             toY = currentMoveString[3] - ONE;
9655             promoChar = currentMoveString[4];
9656         }
9657         break;
9658
9659       case AmbiguousMove:
9660         if (appData.debugMode)
9661           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9662         sprintf(move, _("Ambiguous move: %d.%s%s"),
9663                 (forwardMostMove / 2) + 1,
9664                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9665         DisplayError(move, 0);
9666         done = TRUE;
9667         break;
9668
9669       default:
9670       case ImpossibleMove:
9671         if (appData.debugMode)
9672           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9673         sprintf(move, _("Illegal move: %d.%s%s"),
9674                 (forwardMostMove / 2) + 1,
9675                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9676         DisplayError(move, 0);
9677         done = TRUE;
9678         break;
9679     }
9680
9681     if (done) {
9682         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9683             DrawPosition(FALSE, boards[currentMove]);
9684             DisplayBothClocks();
9685             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9686               DisplayComment(currentMove - 1, commentList[currentMove]);
9687         }
9688         (void) StopLoadGameTimer();
9689         gameFileFP = NULL;
9690         cmailOldMove = forwardMostMove;
9691         return FALSE;
9692     } else {
9693         /* currentMoveString is set as a side-effect of yylex */
9694         strcat(currentMoveString, "\n");
9695         strcpy(moveList[forwardMostMove], currentMoveString);
9696
9697         thinkOutput[0] = NULLCHAR;
9698         MakeMove(fromX, fromY, toX, toY, promoChar);
9699         currentMove = forwardMostMove;
9700         return TRUE;
9701     }
9702 }
9703
9704 /* Load the nth game from the given file */
9705 int
9706 LoadGameFromFile(filename, n, title, useList)
9707      char *filename;
9708      int n;
9709      char *title;
9710      /*Boolean*/ int useList;
9711 {
9712     FILE *f;
9713     char buf[MSG_SIZ];
9714
9715     if (strcmp(filename, "-") == 0) {
9716         f = stdin;
9717         title = "stdin";
9718     } else {
9719         f = fopen(filename, "rb");
9720         if (f == NULL) {
9721           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9722             DisplayError(buf, errno);
9723             return FALSE;
9724         }
9725     }
9726     if (fseek(f, 0, 0) == -1) {
9727         /* f is not seekable; probably a pipe */
9728         useList = FALSE;
9729     }
9730     if (useList && n == 0) {
9731         int error = GameListBuild(f);
9732         if (error) {
9733             DisplayError(_("Cannot build game list"), error);
9734         } else if (!ListEmpty(&gameList) &&
9735                    ((ListGame *) gameList.tailPred)->number > 1) {
9736           // TODO convert to GTK
9737           //        GameListPopUp(f, title);
9738             return TRUE;
9739         }
9740         GameListDestroy();
9741         n = 1;
9742     }
9743     if (n == 0) n = 1;
9744     return LoadGame(f, n, title, FALSE);
9745 }
9746
9747
9748 void
9749 MakeRegisteredMove()
9750 {
9751     int fromX, fromY, toX, toY;
9752     char promoChar;
9753     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9754         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9755           case CMAIL_MOVE:
9756           case CMAIL_DRAW:
9757             if (appData.debugMode)
9758               fprintf(debugFP, "Restoring %s for game %d\n",
9759                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9760
9761             thinkOutput[0] = NULLCHAR;
9762             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9763             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9764             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9765             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9766             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9767             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9768             MakeMove(fromX, fromY, toX, toY, promoChar);
9769             ShowMove(fromX, fromY, toX, toY);
9770             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9771               case MT_NONE:
9772               case MT_CHECK:
9773                 break;
9774
9775               case MT_CHECKMATE:
9776               case MT_STAINMATE:
9777                 if (WhiteOnMove(currentMove)) {
9778                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9779                 } else {
9780                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9781                 }
9782                 break;
9783
9784               case MT_STALEMATE:
9785                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9786                 break;
9787             }
9788
9789             break;
9790
9791           case CMAIL_RESIGN:
9792             if (WhiteOnMove(currentMove)) {
9793                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9794             } else {
9795                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9796             }
9797             break;
9798
9799           case CMAIL_ACCEPT:
9800             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9801             break;
9802
9803           default:
9804             break;
9805         }
9806     }
9807
9808     return;
9809 }
9810
9811 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9812 int
9813 CmailLoadGame(f, gameNumber, title, useList)
9814      FILE *f;
9815      int gameNumber;
9816      char *title;
9817      int useList;
9818 {
9819     int retVal;
9820
9821     if (gameNumber > nCmailGames) {
9822         DisplayError(_("No more games in this message"), 0);
9823         return FALSE;
9824     }
9825     if (f == lastLoadGameFP) {
9826         int offset = gameNumber - lastLoadGameNumber;
9827         if (offset == 0) {
9828             cmailMsg[0] = NULLCHAR;
9829             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9830                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9831                 nCmailMovesRegistered--;
9832             }
9833             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9834             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9835                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9836             }
9837         } else {
9838             if (! RegisterMove()) return FALSE;
9839         }
9840     }
9841
9842     retVal = LoadGame(f, gameNumber, title, useList);
9843
9844     /* Make move registered during previous look at this game, if any */
9845     MakeRegisteredMove();
9846
9847     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9848         commentList[currentMove]
9849           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9850         DisplayComment(currentMove - 1, commentList[currentMove]);
9851     }
9852
9853     return retVal;
9854 }
9855
9856 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9857 int
9858 ReloadGame(offset)
9859      int offset;
9860 {
9861     int gameNumber = lastLoadGameNumber + offset;
9862     if (lastLoadGameFP == NULL) {
9863         DisplayError(_("No game has been loaded yet"), 0);
9864         return FALSE;
9865     }
9866     if (gameNumber <= 0) {
9867         DisplayError(_("Can't back up any further"), 0);
9868         return FALSE;
9869     }
9870     if (cmailMsgLoaded) {
9871         return CmailLoadGame(lastLoadGameFP, gameNumber,
9872                              lastLoadGameTitle, lastLoadGameUseList);
9873     } else {
9874         return LoadGame(lastLoadGameFP, gameNumber,
9875                         lastLoadGameTitle, lastLoadGameUseList);
9876     }
9877 }
9878
9879
9880
9881 /* Load the nth game from open file f */
9882 int
9883 LoadGame(f, gameNumber, title, useList)
9884      FILE *f;
9885      int gameNumber;
9886      char *title;
9887      int useList;
9888 {
9889     ChessMove cm;
9890     char buf[MSG_SIZ];
9891     int gn = gameNumber;
9892     ListGame *lg = NULL;
9893     int numPGNTags = 0;
9894     int err;
9895     GameMode oldGameMode;
9896     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9897
9898     if (appData.debugMode)
9899         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9900
9901     if (gameMode == Training )
9902         SetTrainingModeOff();
9903
9904     oldGameMode = gameMode;
9905     if (gameMode != BeginningOfGame) 
9906       {
9907         Reset(FALSE, TRUE);
9908       };
9909
9910     gameFileFP = f;
9911     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9912       {
9913         fclose(lastLoadGameFP);
9914       };
9915
9916     if (useList) 
9917       {
9918         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9919         
9920         if (lg) 
9921           {
9922             fseek(f, lg->offset, 0);
9923             GameListHighlight(gameNumber);
9924             gn = 1;
9925           }
9926         else 
9927           {
9928             DisplayError(_("Game number out of range"), 0);
9929             return FALSE;
9930           };
9931       } 
9932     else 
9933       {
9934         GameListDestroy();
9935         if (fseek(f, 0, 0) == -1) 
9936           {
9937             if (f == lastLoadGameFP ?
9938                 gameNumber == lastLoadGameNumber + 1 :
9939                 gameNumber == 1) 
9940               {
9941                 gn = 1;
9942               } 
9943             else 
9944               {
9945                 DisplayError(_("Can't seek on game file"), 0);
9946                 return FALSE;
9947               };
9948           };
9949       };
9950
9951     lastLoadGameFP      = f;
9952     lastLoadGameNumber  = gameNumber;
9953     strcpy(lastLoadGameTitle, title);
9954     lastLoadGameUseList = useList;
9955
9956     yynewfile(f);
9957
9958     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9959       {
9960         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9961                  lg->gameInfo.black);
9962         DisplayTitle(buf);
9963       } 
9964     else if (*title != NULLCHAR) 
9965       {
9966         if (gameNumber > 1) 
9967           {
9968             sprintf(buf, "%s %d", title, gameNumber);
9969             DisplayTitle(buf);
9970           } 
9971         else 
9972           {
9973             DisplayTitle(title);
9974           };
9975       };
9976
9977     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9978       {
9979         gameMode = PlayFromGameFile;
9980         ModeHighlight();
9981       };
9982
9983     currentMove = forwardMostMove = backwardMostMove = 0;
9984     CopyBoard(boards[0], initialPosition);
9985     StopClocks();
9986
9987     /*
9988      * Skip the first gn-1 games in the file.
9989      * Also skip over anything that precedes an identifiable
9990      * start of game marker, to avoid being confused by
9991      * garbage at the start of the file.  Currently
9992      * recognized start of game markers are the move number "1",
9993      * the pattern "gnuchess .* game", the pattern
9994      * "^[#;%] [^ ]* game file", and a PGN tag block.
9995      * A game that starts with one of the latter two patterns
9996      * will also have a move number 1, possibly
9997      * following a position diagram.
9998      * 5-4-02: Let's try being more lenient and allowing a game to
9999      * start with an unnumbered move.  Does that break anything?
10000      */
10001     cm = lastLoadGameStart = (ChessMove) 0;
10002     while (gn > 0) {
10003         yyboardindex = forwardMostMove;
10004         cm = (ChessMove) yylex();
10005         switch (cm) {
10006           case (ChessMove) 0:
10007             if (cmailMsgLoaded) {
10008                 nCmailGames = CMAIL_MAX_GAMES - gn;
10009             } else {
10010                 Reset(TRUE, TRUE);
10011                 DisplayError(_("Game not found in file"), 0);
10012             }
10013             return FALSE;
10014
10015           case GNUChessGame:
10016           case XBoardGame:
10017             gn--;
10018             lastLoadGameStart = cm;
10019             break;
10020
10021           case MoveNumberOne:
10022             switch (lastLoadGameStart) {
10023               case GNUChessGame:
10024               case XBoardGame:
10025               case PGNTag:
10026                 break;
10027               case MoveNumberOne:
10028               case (ChessMove) 0:
10029                 gn--;           /* count this game */
10030                 lastLoadGameStart = cm;
10031                 break;
10032               default:
10033                 /* impossible */
10034                 break;
10035             }
10036             break;
10037
10038           case PGNTag:
10039             switch (lastLoadGameStart) {
10040               case GNUChessGame:
10041               case PGNTag:
10042               case MoveNumberOne:
10043               case (ChessMove) 0:
10044                 gn--;           /* count this game */
10045                 lastLoadGameStart = cm;
10046                 break;
10047               case XBoardGame:
10048                 lastLoadGameStart = cm; /* game counted already */
10049                 break;
10050               default:
10051                 /* impossible */
10052                 break;
10053             }
10054             if (gn > 0) {
10055                 do {
10056                     yyboardindex = forwardMostMove;
10057                     cm = (ChessMove) yylex();
10058                 } while (cm == PGNTag || cm == Comment);
10059             }
10060             break;
10061
10062           case WhiteWins:
10063           case BlackWins:
10064           case GameIsDrawn:
10065             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10066                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10067                     != CMAIL_OLD_RESULT) {
10068                     nCmailResults ++ ;
10069                     cmailResult[  CMAIL_MAX_GAMES
10070                                 - gn - 1] = CMAIL_OLD_RESULT;
10071                 }
10072             }
10073             break;
10074
10075           case NormalMove:
10076             /* Only a NormalMove can be at the start of a game
10077              * without a position diagram. */
10078             if (lastLoadGameStart == (ChessMove) 0) {
10079               gn--;
10080               lastLoadGameStart = MoveNumberOne;
10081             }
10082             break;
10083
10084           default:
10085             break;
10086         }
10087     }
10088
10089     if (appData.debugMode)
10090       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10091
10092     if (cm == XBoardGame) {
10093         /* Skip any header junk before position diagram and/or move 1 */
10094         for (;;) {
10095             yyboardindex = forwardMostMove;
10096             cm = (ChessMove) yylex();
10097
10098             if (cm == (ChessMove) 0 ||
10099                 cm == GNUChessGame || cm == XBoardGame) {
10100                 /* Empty game; pretend end-of-file and handle later */
10101                 cm = (ChessMove) 0;
10102                 break;
10103             }
10104
10105             if (cm == MoveNumberOne || cm == PositionDiagram ||
10106                 cm == PGNTag || cm == Comment)
10107               break;
10108         }
10109     } else if (cm == GNUChessGame) {
10110         if (gameInfo.event != NULL) {
10111             free(gameInfo.event);
10112         }
10113         gameInfo.event = StrSave(yy_text);
10114     }
10115
10116     startedFromSetupPosition = FALSE;
10117     while (cm == PGNTag) {
10118         if (appData.debugMode)
10119           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10120         err = ParsePGNTag(yy_text, &gameInfo);
10121         if (!err) numPGNTags++;
10122
10123         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10124         if(gameInfo.variant != oldVariant) {
10125             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10126             InitPosition(TRUE);
10127             oldVariant = gameInfo.variant;
10128             if (appData.debugMode)
10129               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10130         }
10131
10132
10133         if (gameInfo.fen != NULL) {
10134           Board initial_position;
10135           startedFromSetupPosition = TRUE;
10136           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10137             Reset(TRUE, TRUE);
10138             DisplayError(_("Bad FEN position in file"), 0);
10139             return FALSE;
10140           }
10141           CopyBoard(boards[0], initial_position);
10142           if (blackPlaysFirst) {
10143             currentMove = forwardMostMove = backwardMostMove = 1;
10144             CopyBoard(boards[1], initial_position);
10145             strcpy(moveList[0], "");
10146             strcpy(parseList[0], "");
10147             timeRemaining[0][1] = whiteTimeRemaining;
10148             timeRemaining[1][1] = blackTimeRemaining;
10149             if (commentList[0] != NULL) {
10150               commentList[1] = commentList[0];
10151               commentList[0] = NULL;
10152             }
10153           } else {
10154             currentMove = forwardMostMove = backwardMostMove = 0;
10155           }
10156           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10157           {   int i;
10158               initialRulePlies = FENrulePlies;
10159               for( i=0; i< nrCastlingRights; i++ )
10160                   initialRights[i] = initial_position[CASTLING][i];
10161           }
10162           yyboardindex = forwardMostMove;
10163           free(gameInfo.fen);
10164           gameInfo.fen = NULL;
10165         }
10166
10167         yyboardindex = forwardMostMove;
10168         cm = (ChessMove) yylex();
10169
10170         /* Handle comments interspersed among the tags */
10171         while (cm == Comment) {
10172             char *p;
10173             if (appData.debugMode)
10174               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10175             p = yy_text;
10176             AppendComment(currentMove, p, FALSE);
10177             yyboardindex = forwardMostMove;
10178             cm = (ChessMove) yylex();
10179         }
10180     }
10181
10182     /* don't rely on existence of Event tag since if game was
10183      * pasted from clipboard the Event tag may not exist
10184      */
10185     if (numPGNTags > 0){
10186         char *tags;
10187         if (gameInfo.variant == VariantNormal) {
10188           gameInfo.variant = StringToVariant(gameInfo.event);
10189         }
10190         if (!matchMode) {
10191           if( appData.autoDisplayTags ) {
10192             tags = PGNTags(&gameInfo);
10193             TagsPopUp(tags, CmailMsg());
10194             free(tags);
10195           }
10196         }
10197     } else {
10198         /* Make something up, but don't display it now */
10199         SetGameInfo();
10200         TagsPopDown();
10201     }
10202
10203     if (cm == PositionDiagram) {
10204         int i, j;
10205         char *p;
10206         Board initial_position;
10207
10208         if (appData.debugMode)
10209           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10210
10211         if (!startedFromSetupPosition) {
10212             p = yy_text;
10213             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10214               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10215                 switch (*p) {
10216                   case '[':
10217                   case '-':
10218                   case ' ':
10219                   case '\t':
10220                   case '\n':
10221                   case '\r':
10222                     break;
10223                   default:
10224                     initial_position[i][j++] = CharToPiece(*p);
10225                     break;
10226                 }
10227             while (*p == ' ' || *p == '\t' ||
10228                    *p == '\n' || *p == '\r') p++;
10229
10230             if (strncmp(p, "black", strlen("black"))==0)
10231               blackPlaysFirst = TRUE;
10232             else
10233               blackPlaysFirst = FALSE;
10234             startedFromSetupPosition = TRUE;
10235
10236             CopyBoard(boards[0], initial_position);
10237             if (blackPlaysFirst) {
10238                 currentMove = forwardMostMove = backwardMostMove = 1;
10239                 CopyBoard(boards[1], initial_position);
10240                 strcpy(moveList[0], "");
10241                 strcpy(parseList[0], "");
10242                 timeRemaining[0][1] = whiteTimeRemaining;
10243                 timeRemaining[1][1] = blackTimeRemaining;
10244                 if (commentList[0] != NULL) {
10245                     commentList[1] = commentList[0];
10246                     commentList[0] = NULL;
10247                 }
10248             } else {
10249                 currentMove = forwardMostMove = backwardMostMove = 0;
10250             }
10251         }
10252         yyboardindex = forwardMostMove;
10253         cm = (ChessMove) yylex();
10254     }
10255
10256     if (first.pr == NoProc) {
10257         StartChessProgram(&first);
10258     }
10259     InitChessProgram(&first, FALSE);
10260     SendToProgram("force\n", &first);
10261     if (startedFromSetupPosition) {
10262         SendBoard(&first, forwardMostMove);
10263     if (appData.debugMode) {
10264         fprintf(debugFP, "Load Game\n");
10265     }
10266         DisplayBothClocks();
10267     }
10268
10269     /* [HGM] server: flag to write setup moves in broadcast file as one */
10270     loadFlag = appData.suppressLoadMoves;
10271
10272     while (cm == Comment) {
10273         char *p;
10274         if (appData.debugMode)
10275           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10276         p = yy_text;
10277         AppendComment(currentMove, p, FALSE);
10278         yyboardindex = forwardMostMove;
10279         cm = (ChessMove) yylex();
10280     }
10281
10282     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10283         cm == WhiteWins || cm == BlackWins ||
10284         cm == GameIsDrawn || cm == GameUnfinished) {
10285         DisplayMessage("", _("No moves in game"));
10286         if (cmailMsgLoaded) {
10287             if (appData.debugMode)
10288               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10289             ClearHighlights();
10290             flipView = FALSE;
10291         }
10292         DrawPosition(FALSE, boards[currentMove]);
10293         DisplayBothClocks();
10294         gameMode = EditGame;
10295         ModeHighlight();
10296         gameFileFP = NULL;
10297         cmailOldMove = 0;
10298         return TRUE;
10299     }
10300
10301     // [HGM] PV info: routine tests if comment empty
10302     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10303         DisplayComment(currentMove - 1, commentList[currentMove]);
10304     }
10305     if (!matchMode && appData.timeDelay != 0)
10306       DrawPosition(FALSE, boards[currentMove]);
10307
10308     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10309       programStats.ok_to_send = 1;
10310     }
10311
10312     /* if the first token after the PGN tags is a move
10313      * and not move number 1, retrieve it from the parser
10314      */
10315     if (cm != MoveNumberOne)
10316         LoadGameOneMove(cm);
10317
10318     /* load the remaining moves from the file */
10319     while (LoadGameOneMove((ChessMove)0)) {
10320       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10321       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10322     }
10323
10324     /* rewind to the start of the game */
10325     currentMove = backwardMostMove;
10326
10327     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10328
10329     if (oldGameMode == AnalyzeFile ||
10330         oldGameMode == AnalyzeMode) {
10331       AnalyzeFileEvent();
10332     }
10333
10334     if (matchMode || appData.timeDelay == 0) {
10335       ToEndEvent();
10336       gameMode = EditGame;
10337       ModeHighlight();
10338     } else if (appData.timeDelay > 0) {
10339       AutoPlayGameLoop();
10340     }
10341
10342     if (appData.debugMode)
10343         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10344
10345     loadFlag = 0; /* [HGM] true game starts */
10346     return TRUE;
10347 }
10348
10349 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10350 int
10351 ReloadPosition(offset)
10352      int offset;
10353 {
10354     int positionNumber = lastLoadPositionNumber + offset;
10355     if (lastLoadPositionFP == NULL) {
10356         DisplayError(_("No position has been loaded yet"), 0);
10357         return FALSE;
10358     }
10359     if (positionNumber <= 0) {
10360         DisplayError(_("Can't back up any further"), 0);
10361         return FALSE;
10362     }
10363     return LoadPosition(lastLoadPositionFP, positionNumber,
10364                         lastLoadPositionTitle);
10365 }
10366
10367 /* Load the nth position from the given file */
10368 int
10369 LoadPositionFromFile(filename, n, title)
10370      char *filename;
10371      int n;
10372      char *title;
10373 {
10374     FILE *f;
10375     char buf[MSG_SIZ];
10376
10377     if (strcmp(filename, "-") == 0) {
10378         return LoadPosition(stdin, n, "stdin");
10379     } else {
10380         f = fopen(filename, "rb");
10381         if (f == NULL) {
10382             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10383             DisplayError(buf, errno);
10384             return FALSE;
10385         } else {
10386             return LoadPosition(f, n, title);
10387         }
10388     }
10389 }
10390
10391 /* Load the nth position from the given open file, and close it */
10392 int
10393 LoadPosition(f, positionNumber, title)
10394      FILE *f;
10395      int positionNumber;
10396      char *title;
10397 {
10398     char *p, line[MSG_SIZ];
10399     Board initial_position;
10400     int i, j, fenMode, pn;
10401
10402     if (gameMode == Training )
10403         SetTrainingModeOff();
10404
10405     if (gameMode != BeginningOfGame) {
10406         Reset(FALSE, TRUE);
10407     }
10408     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10409         fclose(lastLoadPositionFP);
10410     }
10411     if (positionNumber == 0) positionNumber = 1;
10412     lastLoadPositionFP = f;
10413     lastLoadPositionNumber = positionNumber;
10414     strcpy(lastLoadPositionTitle, title);
10415     if (first.pr == NoProc) {
10416       StartChessProgram(&first);
10417       InitChessProgram(&first, FALSE);
10418     }
10419     pn = positionNumber;
10420     if (positionNumber < 0) {
10421         /* Negative position number means to seek to that byte offset */
10422         if (fseek(f, -positionNumber, 0) == -1) {
10423             DisplayError(_("Can't seek on position file"), 0);
10424             return FALSE;
10425         };
10426         pn = 1;
10427     } else {
10428         if (fseek(f, 0, 0) == -1) {
10429             if (f == lastLoadPositionFP ?
10430                 positionNumber == lastLoadPositionNumber + 1 :
10431                 positionNumber == 1) {
10432                 pn = 1;
10433             } else {
10434                 DisplayError(_("Can't seek on position file"), 0);
10435                 return FALSE;
10436             }
10437         }
10438     }
10439     /* See if this file is FEN or old-style xboard */
10440     if (fgets(line, MSG_SIZ, f) == NULL) {
10441         DisplayError(_("Position not found in file"), 0);
10442         return FALSE;
10443     }
10444     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10445     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10446
10447     if (pn >= 2) {
10448         if (fenMode || line[0] == '#') pn--;
10449         while (pn > 0) {
10450             /* skip positions before number pn */
10451             if (fgets(line, MSG_SIZ, f) == NULL) {
10452                 Reset(TRUE, TRUE);
10453                 DisplayError(_("Position not found in file"), 0);
10454                 return FALSE;
10455             }
10456             if (fenMode || line[0] == '#') pn--;
10457         }
10458     }
10459
10460     if (fenMode) {
10461         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10462             DisplayError(_("Bad FEN position in file"), 0);
10463             return FALSE;
10464         }
10465     } else {
10466         (void) fgets(line, MSG_SIZ, f);
10467         (void) fgets(line, MSG_SIZ, f);
10468
10469         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10470             (void) fgets(line, MSG_SIZ, f);
10471             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10472                 if (*p == ' ')
10473                   continue;
10474                 initial_position[i][j++] = CharToPiece(*p);
10475             }
10476         }
10477
10478         blackPlaysFirst = FALSE;
10479         if (!feof(f)) {
10480             (void) fgets(line, MSG_SIZ, f);
10481             if (strncmp(line, "black", strlen("black"))==0)
10482               blackPlaysFirst = TRUE;
10483         }
10484     }
10485     startedFromSetupPosition = TRUE;
10486
10487     SendToProgram("force\n", &first);
10488     CopyBoard(boards[0], initial_position);
10489     if (blackPlaysFirst) {
10490         currentMove = forwardMostMove = backwardMostMove = 1;
10491         strcpy(moveList[0], "");
10492         strcpy(parseList[0], "");
10493         CopyBoard(boards[1], initial_position);
10494         DisplayMessage("", _("Black to play"));
10495     } else {
10496         currentMove = forwardMostMove = backwardMostMove = 0;
10497         DisplayMessage("", _("White to play"));
10498     }
10499     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10500     SendBoard(&first, forwardMostMove);
10501     if (appData.debugMode) {
10502 int i, j;
10503   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10504   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10505         fprintf(debugFP, "Load Position\n");
10506     }
10507
10508     if (positionNumber > 1) {
10509         sprintf(line, "%s %d", title, positionNumber);
10510         DisplayTitle(line);
10511     } else {
10512         DisplayTitle(title);
10513     }
10514     gameMode = EditGame;
10515     ModeHighlight();
10516     ResetClocks();
10517     timeRemaining[0][1] = whiteTimeRemaining;
10518     timeRemaining[1][1] = blackTimeRemaining;
10519     DrawPosition(FALSE, boards[currentMove]);
10520
10521     return TRUE;
10522 }
10523
10524
10525 void
10526 CopyPlayerNameIntoFileName(dest, src)
10527      char **dest, *src;
10528 {
10529     while (*src != NULLCHAR && *src != ',') {
10530         if (*src == ' ') {
10531             *(*dest)++ = '_';
10532             src++;
10533         } else {
10534             *(*dest)++ = *src++;
10535         }
10536     }
10537 }
10538
10539 char *DefaultFileName(ext)
10540      char *ext;
10541 {
10542     static char def[MSG_SIZ];
10543     char *p;
10544
10545     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10546         p = def;
10547         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10548         *p++ = '-';
10549         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10550         *p++ = '.';
10551         strcpy(p, ext);
10552     } else {
10553         def[0] = NULLCHAR;
10554     }
10555     return def;
10556 }
10557
10558 /* Save the current game to the given file */
10559 int
10560 SaveGameToFile(filename, append)
10561      char *filename;
10562      int append;
10563 {
10564     FILE *f;
10565     char buf[MSG_SIZ];
10566
10567     if (strcmp(filename, "-") == 0) {
10568         return SaveGame(stdout, 0, NULL);
10569     } else {
10570         f = fopen(filename, append ? "a" : "w");
10571         if (f == NULL) {
10572             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10573             DisplayError(buf, errno);
10574             return FALSE;
10575         } else {
10576             return SaveGame(f, 0, NULL);
10577         }
10578     }
10579 }
10580
10581 char *
10582 SavePart(str)
10583      char *str;
10584 {
10585     static char buf[MSG_SIZ];
10586     char *p;
10587
10588     p = strchr(str, ' ');
10589     if (p == NULL) return str;
10590     strncpy(buf, str, p - str);
10591     buf[p - str] = NULLCHAR;
10592     return buf;
10593 }
10594
10595 #define PGN_MAX_LINE 75
10596
10597 #define PGN_SIDE_WHITE  0
10598 #define PGN_SIDE_BLACK  1
10599
10600 /* [AS] */
10601 static int FindFirstMoveOutOfBook( int side )
10602 {
10603     int result = -1;
10604
10605     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10606         int index = backwardMostMove;
10607         int has_book_hit = 0;
10608
10609         if( (index % 2) != side ) {
10610             index++;
10611         }
10612
10613         while( index < forwardMostMove ) {
10614             /* Check to see if engine is in book */
10615             int depth = pvInfoList[index].depth;
10616             int score = pvInfoList[index].score;
10617             int in_book = 0;
10618
10619             if( depth <= 2 ) {
10620                 in_book = 1;
10621             }
10622             else if( score == 0 && depth == 63 ) {
10623                 in_book = 1; /* Zappa */
10624             }
10625             else if( score == 2 && depth == 99 ) {
10626                 in_book = 1; /* Abrok */
10627             }
10628
10629             has_book_hit += in_book;
10630
10631             if( ! in_book ) {
10632                 result = index;
10633
10634                 break;
10635             }
10636
10637             index += 2;
10638         }
10639     }
10640
10641     return result;
10642 }
10643
10644 /* [AS] */
10645 void GetOutOfBookInfo( char * buf )
10646 {
10647     int oob[2];
10648     int i;
10649     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10650
10651     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10652     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10653
10654     *buf = '\0';
10655
10656     if( oob[0] >= 0 || oob[1] >= 0 ) {
10657         for( i=0; i<2; i++ ) {
10658             int idx = oob[i];
10659
10660             if( idx >= 0 ) {
10661                 if( i > 0 && oob[0] >= 0 ) {
10662                     strcat( buf, "   " );
10663                 }
10664
10665                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10666                 sprintf( buf+strlen(buf), "%s%.2f",
10667                     pvInfoList[idx].score >= 0 ? "+" : "",
10668                     pvInfoList[idx].score / 100.0 );
10669             }
10670         }
10671     }
10672 }
10673
10674 /* Save game in PGN style and close the file */
10675 int
10676 SaveGamePGN(f)
10677      FILE *f;
10678 {
10679     int i, offset, linelen, newblock;
10680     time_t tm;
10681 //    char *movetext;
10682     char numtext[32];
10683     int movelen, numlen, blank;
10684     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10685
10686     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10687
10688     tm = time((time_t *) NULL);
10689
10690     PrintPGNTags(f, &gameInfo);
10691
10692     if (backwardMostMove > 0 || startedFromSetupPosition) {
10693         char *fen = PositionToFEN(backwardMostMove, NULL);
10694         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10695         fprintf(f, "\n{--------------\n");
10696         PrintPosition(f, backwardMostMove);
10697         fprintf(f, "--------------}\n");
10698         free(fen);
10699     }
10700     else {
10701         /* [AS] Out of book annotation */
10702         if( appData.saveOutOfBookInfo ) {
10703             char buf[64];
10704
10705             GetOutOfBookInfo( buf );
10706
10707             if( buf[0] != '\0' ) {
10708                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10709             }
10710         }
10711
10712         fprintf(f, "\n");
10713     }
10714
10715     i = backwardMostMove;
10716     linelen = 0;
10717     newblock = TRUE;
10718
10719     while (i < forwardMostMove) {
10720         /* Print comments preceding this move */
10721         if (commentList[i] != NULL) {
10722             if (linelen > 0) fprintf(f, "\n");
10723             fprintf(f, "%s", commentList[i]);
10724             linelen = 0;
10725             newblock = TRUE;
10726         }
10727
10728         /* Format move number */
10729         if ((i % 2) == 0) {
10730             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10731         } else {
10732             if (newblock) {
10733                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10734             } else {
10735                 numtext[0] = NULLCHAR;
10736             }
10737         }
10738         numlen = strlen(numtext);
10739         newblock = FALSE;
10740
10741         /* Print move number */
10742         blank = linelen > 0 && numlen > 0;
10743         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10744             fprintf(f, "\n");
10745             linelen = 0;
10746             blank = 0;
10747         }
10748         if (blank) {
10749             fprintf(f, " ");
10750             linelen++;
10751         }
10752         fprintf(f, "%s", numtext);
10753         linelen += numlen;
10754
10755         /* Get move */
10756         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10757         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10758
10759         /* Print move */
10760         blank = linelen > 0 && movelen > 0;
10761         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10762             fprintf(f, "\n");
10763             linelen = 0;
10764             blank = 0;
10765         }
10766         if (blank) {
10767             fprintf(f, " ");
10768             linelen++;
10769         }
10770         fprintf(f, "%s", move_buffer);
10771         linelen += movelen;
10772
10773         /* [AS] Add PV info if present */
10774         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10775             /* [HGM] add time */
10776             char buf[MSG_SIZ]; int seconds;
10777
10778             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10779
10780             if( seconds <= 0) buf[0] = 0; else
10781             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10782                 seconds = (seconds + 4)/10; // round to full seconds
10783                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10784                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10785             }
10786
10787             sprintf( move_buffer, "{%s%.2f/%d%s}",
10788                 pvInfoList[i].score >= 0 ? "+" : "",
10789                 pvInfoList[i].score / 100.0,
10790                 pvInfoList[i].depth,
10791                 buf );
10792
10793             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10794
10795             /* Print score/depth */
10796             blank = linelen > 0 && movelen > 0;
10797             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10798                 fprintf(f, "\n");
10799                 linelen = 0;
10800                 blank = 0;
10801             }
10802             if (blank) {
10803                 fprintf(f, " ");
10804                 linelen++;
10805             }
10806             fprintf(f, "%s", move_buffer);
10807             linelen += movelen;
10808         }
10809
10810         i++;
10811     }
10812
10813     /* Start a new line */
10814     if (linelen > 0) fprintf(f, "\n");
10815
10816     /* Print comments after last move */
10817     if (commentList[i] != NULL) {
10818         fprintf(f, "%s\n", commentList[i]);
10819     }
10820
10821     /* Print result */
10822     if (gameInfo.resultDetails != NULL &&
10823         gameInfo.resultDetails[0] != NULLCHAR) {
10824         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10825                 PGNResult(gameInfo.result));
10826     } else {
10827         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10828     }
10829
10830     fclose(f);
10831     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10832     return TRUE;
10833 }
10834
10835 /* Save game in old style and close the file */
10836 int
10837 SaveGameOldStyle(f)
10838      FILE *f;
10839 {
10840     int i, offset;
10841     time_t tm;
10842
10843     tm = time((time_t *) NULL);
10844
10845     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10846     PrintOpponents(f);
10847
10848     if (backwardMostMove > 0 || startedFromSetupPosition) {
10849         fprintf(f, "\n[--------------\n");
10850         PrintPosition(f, backwardMostMove);
10851         fprintf(f, "--------------]\n");
10852     } else {
10853         fprintf(f, "\n");
10854     }
10855
10856     i = backwardMostMove;
10857     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10858
10859     while (i < forwardMostMove) {
10860         if (commentList[i] != NULL) {
10861             fprintf(f, "[%s]\n", commentList[i]);
10862         }
10863
10864         if ((i % 2) == 1) {
10865             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10866             i++;
10867         } else {
10868             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10869             i++;
10870             if (commentList[i] != NULL) {
10871                 fprintf(f, "\n");
10872                 continue;
10873             }
10874             if (i >= forwardMostMove) {
10875                 fprintf(f, "\n");
10876                 break;
10877             }
10878             fprintf(f, "%s\n", parseList[i]);
10879             i++;
10880         }
10881     }
10882
10883     if (commentList[i] != NULL) {
10884         fprintf(f, "[%s]\n", commentList[i]);
10885     }
10886
10887     /* This isn't really the old style, but it's close enough */
10888     if (gameInfo.resultDetails != NULL &&
10889         gameInfo.resultDetails[0] != NULLCHAR) {
10890         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10891                 gameInfo.resultDetails);
10892     } else {
10893         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10894     }
10895
10896     fclose(f);
10897     return TRUE;
10898 }
10899
10900 /* Save the current game to open file f and close the file */
10901 int
10902 SaveGame(f, dummy, dummy2)
10903      FILE *f;
10904      int dummy;
10905      char *dummy2;
10906 {
10907     if (gameMode == EditPosition) EditPositionDone(TRUE);
10908     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10909     if (appData.oldSaveStyle)
10910       return SaveGameOldStyle(f);
10911     else
10912       return SaveGamePGN(f);
10913 }
10914
10915 /* Save the current position to the given file */
10916 int
10917 SavePositionToFile(filename)
10918      char *filename;
10919 {
10920     FILE *f;
10921     char buf[MSG_SIZ];
10922
10923     if (strcmp(filename, "-") == 0) {
10924         return SavePosition(stdout, 0, NULL);
10925     } else {
10926         f = fopen(filename, "a");
10927         if (f == NULL) {
10928             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10929             DisplayError(buf, errno);
10930             return FALSE;
10931         } else {
10932             SavePosition(f, 0, NULL);
10933             return TRUE;
10934         }
10935     }
10936 }
10937
10938 /* Save the current position to the given open file and close the file */
10939 int
10940 SavePosition(f, dummy, dummy2)
10941      FILE *f;
10942      int dummy;
10943      char *dummy2;
10944 {
10945     time_t tm;
10946     char *fen;
10947     if (gameMode == EditPosition) EditPositionDone(TRUE);
10948     if (appData.oldSaveStyle) {
10949         tm = time((time_t *) NULL);
10950
10951         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10952         PrintOpponents(f);
10953         fprintf(f, "[--------------\n");
10954         PrintPosition(f, currentMove);
10955         fprintf(f, "--------------]\n");
10956     } else {
10957         fen = PositionToFEN(currentMove, NULL);
10958         fprintf(f, "%s\n", fen);
10959         free(fen);
10960     }
10961     fclose(f);
10962     return TRUE;
10963 }
10964
10965 void
10966 ReloadCmailMsgEvent(unregister)
10967      int unregister;
10968 {
10969 #if !WIN32
10970     static char *inFilename = NULL;
10971     static char *outFilename;
10972     int i;
10973     struct stat inbuf, outbuf;
10974     int status;
10975
10976     /* Any registered moves are unregistered if unregister is set, */
10977     /* i.e. invoked by the signal handler */
10978     if (unregister) {
10979         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10980             cmailMoveRegistered[i] = FALSE;
10981             if (cmailCommentList[i] != NULL) {
10982                 free(cmailCommentList[i]);
10983                 cmailCommentList[i] = NULL;
10984             }
10985         }
10986         nCmailMovesRegistered = 0;
10987     }
10988
10989     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10990         cmailResult[i] = CMAIL_NOT_RESULT;
10991     }
10992     nCmailResults = 0;
10993
10994     if (inFilename == NULL) {
10995         /* Because the filenames are static they only get malloced once  */
10996         /* and they never get freed                                      */
10997         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10998         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10999
11000         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11001         sprintf(outFilename, "%s.out", appData.cmailGameName);
11002     }
11003
11004     status = stat(outFilename, &outbuf);
11005     if (status < 0) {
11006         cmailMailedMove = FALSE;
11007     } else {
11008         status = stat(inFilename, &inbuf);
11009         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11010     }
11011
11012     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11013        counts the games, notes how each one terminated, etc.
11014
11015        It would be nice to remove this kludge and instead gather all
11016        the information while building the game list.  (And to keep it
11017        in the game list nodes instead of having a bunch of fixed-size
11018        parallel arrays.)  Note this will require getting each game's
11019        termination from the PGN tags, as the game list builder does
11020        not process the game moves.  --mann
11021        */
11022     cmailMsgLoaded = TRUE;
11023     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11024
11025     /* Load first game in the file or popup game menu */
11026     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11027
11028 #endif /* !WIN32 */
11029     return;
11030 }
11031
11032 int
11033 RegisterMove()
11034 {
11035     FILE *f;
11036     char string[MSG_SIZ];
11037
11038     if (   cmailMailedMove
11039         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11040         return TRUE;            /* Allow free viewing  */
11041     }
11042
11043     /* Unregister move to ensure that we don't leave RegisterMove        */
11044     /* with the move registered when the conditions for registering no   */
11045     /* longer hold                                                       */
11046     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11047         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11048         nCmailMovesRegistered --;
11049
11050         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11051           {
11052               free(cmailCommentList[lastLoadGameNumber - 1]);
11053               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11054           }
11055     }
11056
11057     if (cmailOldMove == -1) {
11058         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11059         return FALSE;
11060     }
11061
11062     if (currentMove > cmailOldMove + 1) {
11063         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11064         return FALSE;
11065     }
11066
11067     if (currentMove < cmailOldMove) {
11068         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11069         return FALSE;
11070     }
11071
11072     if (forwardMostMove > currentMove) {
11073         /* Silently truncate extra moves */
11074         TruncateGame();
11075     }
11076
11077     if (   (currentMove == cmailOldMove + 1)
11078         || (   (currentMove == cmailOldMove)
11079             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11080                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11081         if (gameInfo.result != GameUnfinished) {
11082             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11083         }
11084
11085         if (commentList[currentMove] != NULL) {
11086             cmailCommentList[lastLoadGameNumber - 1]
11087               = StrSave(commentList[currentMove]);
11088         }
11089         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11090
11091         if (appData.debugMode)
11092           fprintf(debugFP, "Saving %s for game %d\n",
11093                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11094
11095         sprintf(string,
11096                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11097
11098         f = fopen(string, "w");
11099         if (appData.oldSaveStyle) {
11100             SaveGameOldStyle(f); /* also closes the file */
11101
11102             sprintf(string, "%s.pos.out", appData.cmailGameName);
11103             f = fopen(string, "w");
11104             SavePosition(f, 0, NULL); /* also closes the file */
11105         } else {
11106             fprintf(f, "{--------------\n");
11107             PrintPosition(f, currentMove);
11108             fprintf(f, "--------------}\n\n");
11109
11110             SaveGame(f, 0, NULL); /* also closes the file*/
11111         }
11112
11113         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11114         nCmailMovesRegistered ++;
11115     } else if (nCmailGames == 1) {
11116         DisplayError(_("You have not made a move yet"), 0);
11117         return FALSE;
11118     }
11119
11120     return TRUE;
11121 }
11122
11123 void
11124 MailMoveEvent()
11125 {
11126 #if !WIN32
11127     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11128     FILE *commandOutput;
11129     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11130     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11131     int nBuffers;
11132     int i;
11133     int archived;
11134     char *arcDir;
11135
11136     if (! cmailMsgLoaded) {
11137         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11138         return;
11139     }
11140
11141     if (nCmailGames == nCmailResults) {
11142         DisplayError(_("No unfinished games"), 0);
11143         return;
11144     }
11145
11146 #if CMAIL_PROHIBIT_REMAIL
11147     if (cmailMailedMove) {
11148         sprintf(msg, _("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);
11149         DisplayError(msg, 0);
11150         return;
11151     }
11152 #endif
11153
11154     if (! (cmailMailedMove || RegisterMove())) return;
11155
11156     if (   cmailMailedMove
11157         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11158         sprintf(string, partCommandString,
11159                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11160         commandOutput = popen(string, "r");
11161
11162         if (commandOutput == NULL) {
11163             DisplayError(_("Failed to invoke cmail"), 0);
11164         } else {
11165             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11166                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11167             }
11168             if (nBuffers > 1) {
11169                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11170                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11171                 nBytes = MSG_SIZ - 1;
11172             } else {
11173                 (void) memcpy(msg, buffer, nBytes);
11174             }
11175             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11176
11177             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11178                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11179
11180                 archived = TRUE;
11181                 for (i = 0; i < nCmailGames; i ++) {
11182                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11183                         archived = FALSE;
11184                     }
11185                 }
11186                 if (   archived
11187                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11188                         != NULL)) {
11189                     sprintf(buffer, "%s/%s.%s.archive",
11190                             arcDir,
11191                             appData.cmailGameName,
11192                             gameInfo.date);
11193                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11194                     cmailMsgLoaded = FALSE;
11195                 }
11196             }
11197
11198             DisplayInformation(msg);
11199             pclose(commandOutput);
11200         }
11201     } else {
11202         if ((*cmailMsg) != '\0') {
11203             DisplayInformation(cmailMsg);
11204         }
11205     }
11206
11207     return;
11208 #endif /* !WIN32 */
11209 }
11210
11211 char *
11212 CmailMsg()
11213 {
11214 #if WIN32
11215     return NULL;
11216 #else
11217     int  prependComma = 0;
11218     char number[5];
11219     char string[MSG_SIZ];       /* Space for game-list */
11220     int  i;
11221
11222     if (!cmailMsgLoaded) return "";
11223
11224     if (cmailMailedMove) {
11225         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11226     } else {
11227         /* Create a list of games left */
11228         sprintf(string, "[");
11229         for (i = 0; i < nCmailGames; i ++) {
11230             if (! (   cmailMoveRegistered[i]
11231                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11232                 if (prependComma) {
11233                     sprintf(number, ",%d", i + 1);
11234                 } else {
11235                     sprintf(number, "%d", i + 1);
11236                     prependComma = 1;
11237                 }
11238
11239                 strcat(string, number);
11240             }
11241         }
11242         strcat(string, "]");
11243
11244         if (nCmailMovesRegistered + nCmailResults == 0) {
11245             switch (nCmailGames) {
11246               case 1:
11247                 sprintf(cmailMsg,
11248                         _("Still need to make move for game\n"));
11249                 break;
11250
11251               case 2:
11252                 sprintf(cmailMsg,
11253                         _("Still need to make moves for both games\n"));
11254                 break;
11255
11256               default:
11257                 sprintf(cmailMsg,
11258                         _("Still need to make moves for all %d games\n"),
11259                         nCmailGames);
11260                 break;
11261             }
11262         } else {
11263             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11264               case 1:
11265                 sprintf(cmailMsg,
11266                         _("Still need to make a move for game %s\n"),
11267                         string);
11268                 break;
11269
11270               case 0:
11271                 if (nCmailResults == nCmailGames) {
11272                     sprintf(cmailMsg, _("No unfinished games\n"));
11273                 } else {
11274                     sprintf(cmailMsg, _("Ready to send mail\n"));
11275                 }
11276                 break;
11277
11278               default:
11279                 sprintf(cmailMsg,
11280                         _("Still need to make moves for games %s\n"),
11281                         string);
11282             }
11283         }
11284     }
11285     return cmailMsg;
11286 #endif /* WIN32 */
11287 }
11288
11289 void
11290 ResetGameEvent()
11291 {
11292     if (gameMode == Training)
11293       SetTrainingModeOff();
11294
11295     Reset(TRUE, TRUE);
11296     cmailMsgLoaded = FALSE;
11297     if (appData.icsActive) {
11298       SendToICS(ics_prefix);
11299       SendToICS("refresh\n");
11300     }
11301 }
11302
11303 void
11304 ExitEvent(status)
11305      int status;
11306 {
11307     exiting++;
11308     if (exiting > 2) {
11309       /* Give up on clean exit */
11310       exit(status);
11311     }
11312     if (exiting > 1) {
11313       /* Keep trying for clean exit */
11314       return;
11315     }
11316
11317     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11318
11319     if (telnetISR != NULL) {
11320       RemoveInputSource(telnetISR);
11321     }
11322     if (icsPR != NoProc) {
11323       DestroyChildProcess(icsPR, TRUE);
11324     }
11325
11326     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11327     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11328
11329     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11330     /* make sure this other one finishes before killing it!                  */
11331     if(endingGame) { int count = 0;
11332         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11333         while(endingGame && count++ < 10) DoSleep(1);
11334         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11335     }
11336
11337     /* Kill off chess programs */
11338     if (first.pr != NoProc) {
11339         ExitAnalyzeMode();
11340
11341         DoSleep( appData.delayBeforeQuit );
11342         SendToProgram("quit\n", &first);
11343         DoSleep( appData.delayAfterQuit );
11344         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11345     }
11346     if (second.pr != NoProc) {
11347         DoSleep( appData.delayBeforeQuit );
11348         SendToProgram("quit\n", &second);
11349         DoSleep( appData.delayAfterQuit );
11350         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11351     }
11352     if (first.isr != NULL) {
11353         RemoveInputSource(first.isr);
11354     }
11355     if (second.isr != NULL) {
11356         RemoveInputSource(second.isr);
11357     }
11358
11359     ShutDownFrontEnd();
11360     exit(status);
11361 }
11362
11363 void
11364 PauseEvent()
11365 {
11366     if (appData.debugMode)
11367         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11368     if (pausing) {
11369         pausing = FALSE;
11370         ModeHighlight();
11371         if (gameMode == MachinePlaysWhite ||
11372             gameMode == MachinePlaysBlack) {
11373             StartClocks();
11374         } else {
11375             DisplayBothClocks();
11376         }
11377         if (gameMode == PlayFromGameFile) {
11378             if (appData.timeDelay >= 0)
11379                 AutoPlayGameLoop();
11380         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11381             Reset(FALSE, TRUE);
11382             SendToICS(ics_prefix);
11383             SendToICS("refresh\n");
11384         } else if (currentMove < forwardMostMove) {
11385             ForwardInner(forwardMostMove);
11386         }
11387         pauseExamInvalid = FALSE;
11388     } else {
11389         switch (gameMode) {
11390           default:
11391             return;
11392           case IcsExamining:
11393             pauseExamForwardMostMove = forwardMostMove;
11394             pauseExamInvalid = FALSE;
11395             /* fall through */
11396           case IcsObserving:
11397           case IcsPlayingWhite:
11398           case IcsPlayingBlack:
11399             pausing = TRUE;
11400             ModeHighlight();
11401             return;
11402           case PlayFromGameFile:
11403             (void) StopLoadGameTimer();
11404             pausing = TRUE;
11405             ModeHighlight();
11406             break;
11407           case BeginningOfGame:
11408             if (appData.icsActive) return;
11409             /* else fall through */
11410           case MachinePlaysWhite:
11411           case MachinePlaysBlack:
11412           case TwoMachinesPlay:
11413             if (forwardMostMove == 0)
11414               return;           /* don't pause if no one has moved */
11415             if ((gameMode == MachinePlaysWhite &&
11416                  !WhiteOnMove(forwardMostMove)) ||
11417                 (gameMode == MachinePlaysBlack &&
11418                  WhiteOnMove(forwardMostMove))) {
11419                 StopClocks();
11420             }
11421             pausing = TRUE;
11422             ModeHighlight();
11423             break;
11424         }
11425     }
11426 }
11427
11428 void
11429 EditCommentEvent()
11430 {
11431     char title[MSG_SIZ];
11432
11433     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11434         strcpy(title, _("Edit comment"));
11435     } else {
11436         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11437                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11438                 parseList[currentMove - 1]);
11439     }
11440
11441     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11442 }
11443
11444
11445 void
11446 EditTagsEvent()
11447 {
11448     char *tags = PGNTags(&gameInfo);
11449     EditTagsPopUp(tags);
11450     free(tags);
11451 }
11452
11453 void
11454 AnalyzeModeEvent()
11455 {
11456     if (appData.noChessProgram || gameMode == AnalyzeMode)
11457       return;
11458
11459     if (gameMode != AnalyzeFile) {
11460         if (!appData.icsEngineAnalyze) {
11461                EditGameEvent();
11462                if (gameMode != EditGame) return;
11463         }
11464         ResurrectChessProgram();
11465         SendToProgram("analyze\n", &first);
11466         first.analyzing = TRUE;
11467         /*first.maybeThinking = TRUE;*/
11468         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11469         EngineOutputPopUp();
11470     }
11471     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11472     pausing = FALSE;
11473     ModeHighlight();
11474     SetGameInfo();
11475
11476     StartAnalysisClock();
11477     GetTimeMark(&lastNodeCountTime);
11478     lastNodeCount = 0;
11479 }
11480
11481 void
11482 AnalyzeFileEvent()
11483 {
11484     if (appData.noChessProgram || gameMode == AnalyzeFile)
11485       return;
11486
11487     if (gameMode != AnalyzeMode) {
11488         EditGameEvent();
11489         if (gameMode != EditGame) return;
11490         ResurrectChessProgram();
11491         SendToProgram("analyze\n", &first);
11492         first.analyzing = TRUE;
11493         /*first.maybeThinking = TRUE;*/
11494         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11495         EngineOutputPopUp();
11496     }
11497     gameMode = AnalyzeFile;
11498     pausing = FALSE;
11499     ModeHighlight();
11500     SetGameInfo();
11501
11502     StartAnalysisClock();
11503     GetTimeMark(&lastNodeCountTime);
11504     lastNodeCount = 0;
11505 }
11506
11507 void
11508 MachineWhiteEvent()
11509 {
11510     char buf[MSG_SIZ];
11511     char *bookHit = NULL;
11512
11513     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11514       return;
11515
11516
11517     if (gameMode == PlayFromGameFile ||
11518         gameMode == TwoMachinesPlay  ||
11519         gameMode == Training         ||
11520         gameMode == AnalyzeMode      ||
11521         gameMode == EndOfGame)
11522         EditGameEvent();
11523
11524     if (gameMode == EditPosition) 
11525         EditPositionDone(TRUE);
11526
11527     if (!WhiteOnMove(currentMove)) {
11528         DisplayError(_("It is not White's turn"), 0);
11529         return;
11530     }
11531
11532     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11533       ExitAnalyzeMode();
11534
11535     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11536         gameMode == AnalyzeFile)
11537         TruncateGame();
11538
11539     ResurrectChessProgram();    /* in case it isn't running */
11540     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11541         gameMode = MachinePlaysWhite;
11542         ResetClocks();
11543     } else
11544     gameMode = MachinePlaysWhite;
11545     pausing = FALSE;
11546     ModeHighlight();
11547     SetGameInfo();
11548     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11549     DisplayTitle(buf);
11550     if (first.sendName) {
11551       sprintf(buf, "name %s\n", gameInfo.black);
11552       SendToProgram(buf, &first);
11553     }
11554     if (first.sendTime) {
11555       if (first.useColors) {
11556         SendToProgram("black\n", &first); /*gnu kludge*/
11557       }
11558       SendTimeRemaining(&first, TRUE);
11559     }
11560     if (first.useColors) {
11561       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11562     }
11563     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11564     SetMachineThinkingEnables();
11565     first.maybeThinking = TRUE;
11566     StartClocks();
11567     firstMove = FALSE;
11568
11569     if (appData.autoFlipView && !flipView) {
11570       flipView = !flipView;
11571       DrawPosition(FALSE, NULL);
11572       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11573     }
11574
11575     if(bookHit) { // [HGM] book: simulate book reply
11576         static char bookMove[MSG_SIZ]; // a bit generous?
11577
11578         programStats.nodes = programStats.depth = programStats.time =
11579         programStats.score = programStats.got_only_move = 0;
11580         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11581
11582         strcpy(bookMove, "move ");
11583         strcat(bookMove, bookHit);
11584         HandleMachineMove(bookMove, &first);
11585     }
11586 }
11587
11588 void
11589 MachineBlackEvent()
11590 {
11591   char buf[MSG_SIZ];
11592   char *bookHit = NULL;
11593   
11594   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11595     return;
11596   
11597   
11598   if (gameMode == PlayFromGameFile 
11599       || gameMode == TwoMachinesPlay  
11600       || gameMode == Training     
11601       || gameMode == AnalyzeMode
11602       || gameMode == EndOfGame)
11603     EditGameEvent();
11604   
11605   if (gameMode == EditPosition) 
11606     EditPositionDone(TRUE);
11607   
11608   if (WhiteOnMove(currentMove)) 
11609     {
11610       DisplayError(_("It is not Black's turn"), 0);
11611       return;
11612     }
11613   
11614   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11615     ExitAnalyzeMode();
11616   
11617   if (gameMode == EditGame || gameMode == AnalyzeMode 
11618       || gameMode == AnalyzeFile)
11619     TruncateGame();
11620   
11621   ResurrectChessProgram();      /* in case it isn't running */
11622   gameMode = MachinePlaysBlack;
11623   pausing  = FALSE;
11624   ModeHighlight();
11625   SetGameInfo();
11626   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11627   DisplayTitle(buf);
11628   if (first.sendName) 
11629     {
11630       sprintf(buf, "name %s\n", gameInfo.white);
11631       SendToProgram(buf, &first);
11632     }
11633   if (first.sendTime) 
11634     {
11635       if (first.useColors) 
11636         {
11637           SendToProgram("white\n", &first); /*gnu kludge*/
11638         }
11639       SendTimeRemaining(&first, FALSE);
11640     }
11641   if (first.useColors) 
11642     {
11643       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11644     }
11645   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11646   SetMachineThinkingEnables();
11647   first.maybeThinking = TRUE;
11648   StartClocks();
11649   
11650   if (appData.autoFlipView && flipView) 
11651     {
11652       flipView = !flipView;
11653       DrawPosition(FALSE, NULL);
11654       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11655     }
11656   if(bookHit) 
11657     { // [HGM] book: simulate book reply
11658       static char bookMove[MSG_SIZ]; // a bit generous?
11659       
11660       programStats.nodes = programStats.depth = programStats.time 
11661         = programStats.score = programStats.got_only_move = 0;
11662       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11663       
11664       strcpy(bookMove, "move ");
11665       strcat(bookMove, bookHit);
11666       HandleMachineMove(bookMove, &first);
11667     }
11668   return;
11669 }
11670
11671
11672 void
11673 DisplayTwoMachinesTitle()
11674 {
11675     char buf[MSG_SIZ];
11676     if (appData.matchGames > 0) {
11677         if (first.twoMachinesColor[0] == 'w') {
11678             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11679                     gameInfo.white, gameInfo.black,
11680                     first.matchWins, second.matchWins,
11681                     matchGame - 1 - (first.matchWins + second.matchWins));
11682         } else {
11683             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11684                     gameInfo.white, gameInfo.black,
11685                     second.matchWins, first.matchWins,
11686                     matchGame - 1 - (first.matchWins + second.matchWins));
11687         }
11688     } else {
11689         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11690     }
11691     DisplayTitle(buf);
11692 }
11693
11694 void
11695 TwoMachinesEvent P((void))
11696 {
11697     int i;
11698     char buf[MSG_SIZ];
11699     ChessProgramState *onmove;
11700     char *bookHit = NULL;
11701
11702     if (appData.noChessProgram) return;
11703
11704     switch (gameMode) {
11705       case TwoMachinesPlay:
11706         return;
11707       case MachinePlaysWhite:
11708       case MachinePlaysBlack:
11709         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11710             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11711             return;
11712         }
11713         /* fall through */
11714       case BeginningOfGame:
11715       case PlayFromGameFile:
11716       case EndOfGame:
11717         EditGameEvent();
11718         if (gameMode != EditGame) return;
11719         break;
11720       case EditPosition:
11721         EditPositionDone(TRUE);
11722         break;
11723       case AnalyzeMode:
11724       case AnalyzeFile:
11725         ExitAnalyzeMode();
11726         break;
11727       case EditGame:
11728       default:
11729         break;
11730     }
11731
11732 //    forwardMostMove = currentMove;
11733     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11734     ResurrectChessProgram();    /* in case first program isn't running */
11735
11736     if (second.pr == NULL) {
11737         StartChessProgram(&second);
11738         if (second.protocolVersion == 1) {
11739           TwoMachinesEventIfReady();
11740         } else {
11741           /* kludge: allow timeout for initial "feature" command */
11742           FreezeUI();
11743           DisplayMessage("", _("Starting second chess program"));
11744           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11745         }
11746         return;
11747     }
11748     DisplayMessage("", "");
11749     InitChessProgram(&second, FALSE);
11750     SendToProgram("force\n", &second);
11751     if (startedFromSetupPosition) {
11752         SendBoard(&second, backwardMostMove);
11753     if (appData.debugMode) {
11754         fprintf(debugFP, "Two Machines\n");
11755     }
11756     }
11757     for (i = backwardMostMove; i < forwardMostMove; i++) {
11758         SendMoveToProgram(i, &second);
11759     }
11760
11761     gameMode = TwoMachinesPlay;
11762     pausing = FALSE;
11763     ModeHighlight();
11764     SetGameInfo();
11765     DisplayTwoMachinesTitle();
11766     firstMove = TRUE;
11767     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11768         onmove = &first;
11769     } else {
11770         onmove = &second;
11771     }
11772
11773     SendToProgram(first.computerString, &first);
11774     if (first.sendName) {
11775       sprintf(buf, "name %s\n", second.tidy);
11776       SendToProgram(buf, &first);
11777     }
11778     SendToProgram(second.computerString, &second);
11779     if (second.sendName) {
11780       sprintf(buf, "name %s\n", first.tidy);
11781       SendToProgram(buf, &second);
11782     }
11783
11784     ResetClocks();
11785     if (!first.sendTime || !second.sendTime) {
11786         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11787         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11788     }
11789     if (onmove->sendTime) {
11790       if (onmove->useColors) {
11791         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11792       }
11793       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11794     }
11795     if (onmove->useColors) {
11796       SendToProgram(onmove->twoMachinesColor, onmove);
11797     }
11798     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11799 //    SendToProgram("go\n", onmove);
11800     onmove->maybeThinking = TRUE;
11801     SetMachineThinkingEnables();
11802
11803     StartClocks();
11804
11805     if(bookHit) { // [HGM] book: simulate book reply
11806         static char bookMove[MSG_SIZ]; // a bit generous?
11807
11808         programStats.nodes = programStats.depth = programStats.time =
11809         programStats.score = programStats.got_only_move = 0;
11810         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11811
11812         strcpy(bookMove, "move ");
11813         strcat(bookMove, bookHit);
11814         savedMessage = bookMove; // args for deferred call
11815         savedState = onmove;
11816         ScheduleDelayedEvent(DeferredBookMove, 1);
11817     }
11818 }
11819
11820 void
11821 TrainingEvent()
11822 {
11823     if (gameMode == Training) {
11824       SetTrainingModeOff();
11825       gameMode = PlayFromGameFile;
11826       DisplayMessage("", _("Training mode off"));
11827     } else {
11828       gameMode = Training;
11829       animateTraining = appData.animate;
11830
11831       /* make sure we are not already at the end of the game */
11832       if (currentMove < forwardMostMove) {
11833         SetTrainingModeOn();
11834         DisplayMessage("", _("Training mode on"));
11835       } else {
11836         gameMode = PlayFromGameFile;
11837         DisplayError(_("Already at end of game"), 0);
11838       }
11839     }
11840     ModeHighlight();
11841 }
11842
11843 void
11844 IcsClientEvent()
11845 {
11846     if (!appData.icsActive) return;
11847     switch (gameMode) {
11848       case IcsPlayingWhite:
11849       case IcsPlayingBlack:
11850       case IcsObserving:
11851       case IcsIdle:
11852       case BeginningOfGame:
11853       case IcsExamining:
11854         return;
11855
11856       case EditGame:
11857         break;
11858
11859       case EditPosition:
11860         EditPositionDone(TRUE);
11861         break;
11862
11863       case AnalyzeMode:
11864       case AnalyzeFile:
11865         ExitAnalyzeMode();
11866         break;
11867
11868       default:
11869         EditGameEvent();
11870         break;
11871     }
11872
11873     gameMode = IcsIdle;
11874     ModeHighlight();
11875     return;
11876 }
11877
11878
11879 void
11880 EditGameEvent()
11881 {
11882     int i;
11883
11884     switch (gameMode) {
11885       case Training:
11886         SetTrainingModeOff();
11887         break;
11888       case MachinePlaysWhite:
11889       case MachinePlaysBlack:
11890       case BeginningOfGame:
11891         SendToProgram("force\n", &first);
11892         SetUserThinkingEnables();
11893         break;
11894       case PlayFromGameFile:
11895         (void) StopLoadGameTimer();
11896         if (gameFileFP != NULL) {
11897             gameFileFP = NULL;
11898         }
11899         break;
11900       case EditPosition:
11901         EditPositionDone(TRUE);
11902         break;
11903       case AnalyzeMode:
11904       case AnalyzeFile:
11905         ExitAnalyzeMode();
11906         SendToProgram("force\n", &first);
11907         break;
11908       case TwoMachinesPlay:
11909         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11910         ResurrectChessProgram();
11911         SetUserThinkingEnables();
11912         break;
11913       case EndOfGame:
11914         ResurrectChessProgram();
11915         break;
11916       case IcsPlayingBlack:
11917       case IcsPlayingWhite:
11918         DisplayError(_("Warning: You are still playing a game"), 0);
11919         break;
11920       case IcsObserving:
11921         DisplayError(_("Warning: You are still observing a game"), 0);
11922         break;
11923       case IcsExamining:
11924         DisplayError(_("Warning: You are still examining a game"), 0);
11925         break;
11926       case IcsIdle:
11927         break;
11928       case EditGame:
11929       default:
11930         return;
11931     }
11932
11933     pausing = FALSE;
11934     StopClocks();
11935     first.offeredDraw = second.offeredDraw = 0;
11936
11937     if (gameMode == PlayFromGameFile) {
11938         whiteTimeRemaining = timeRemaining[0][currentMove];
11939         blackTimeRemaining = timeRemaining[1][currentMove];
11940         DisplayTitle("");
11941     }
11942
11943     if (gameMode == MachinePlaysWhite ||
11944         gameMode == MachinePlaysBlack ||
11945         gameMode == TwoMachinesPlay ||
11946         gameMode == EndOfGame) {
11947         i = forwardMostMove;
11948         while (i > currentMove) {
11949             SendToProgram("undo\n", &first);
11950             i--;
11951         }
11952         whiteTimeRemaining = timeRemaining[0][currentMove];
11953         blackTimeRemaining = timeRemaining[1][currentMove];
11954         DisplayBothClocks();
11955         if (whiteFlag || blackFlag) {
11956             whiteFlag = blackFlag = 0;
11957         }
11958         DisplayTitle("");
11959     }
11960
11961     gameMode = EditGame;
11962     ModeHighlight();
11963     SetGameInfo();
11964 }
11965
11966 void
11967 EditPositionEvent()
11968 {
11969     if (gameMode == EditPosition) {
11970         EditGameEvent();
11971         return;
11972     }
11973
11974     EditGameEvent();
11975     if (gameMode != EditGame) return;
11976
11977     gameMode = EditPosition;
11978     ModeHighlight();
11979     SetGameInfo();
11980     if (currentMove > 0)
11981       CopyBoard(boards[0], boards[currentMove]);
11982     { int i, r, f; // [HGM] editrights: take note of existing rights
11983       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) rightsBoard[r][f] = 0;
11984       for(i=0; i<nrCastlingRights; i++) {
11985         if(boards[0][CASTLING][i] != NoRights)
11986           rightsBoard [castlingRank[i]] [boards[0][CASTLING][i]] = 1 + (i == 2 || i == 5);
11987       }
11988     }
11989     
11990     blackPlaysFirst = !WhiteOnMove(currentMove);
11991     ResetClocks();
11992     currentMove = forwardMostMove = backwardMostMove = 0;
11993     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11994     DisplayMove(-1);
11995 }
11996
11997 void
11998 ExitAnalyzeMode()
11999 {
12000     /* [DM] icsEngineAnalyze - possible call from other functions */
12001     if (appData.icsEngineAnalyze) {
12002         appData.icsEngineAnalyze = FALSE;
12003
12004         DisplayMessage("",_("Close ICS engine analyze..."));
12005     }
12006     if (first.analysisSupport && first.analyzing) {
12007       SendToProgram("exit\n", &first);
12008       first.analyzing = FALSE;
12009     }
12010     thinkOutput[0] = NULLCHAR;
12011 }
12012
12013 void
12014 EditPositionDone(Boolean fakeRights)
12015 {
12016     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12017
12018     startedFromSetupPosition = TRUE;
12019     InitChessProgram(&first, FALSE);
12020     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12021       int i, r, f, kf, err;
12022       for(i=0; i<nrCastlingRights; i++) boards[0][CASTLING][i] = NoRights;
12023       kf = NoRights; err = 0;
12024       for(f=BOARD_RGHT-1; f>=0; f--)
12025         if(rightsBoard[0][f] == 2) { if(kf != NoRights) err=10; boards[0][CASTLING][2] = kf = f; }
12026       if(kf == NoRights) kf = 4;
12027       for(f=BOARD_RGHT-1; f>=0; f--)
12028         if(rightsBoard[0][f] == 1) { err++; boards[0][CASTLING][f<kf] = f; }
12029       kf = NoRights; err = 0;
12030       for(f=BOARD_RGHT-1; f>=0; f--)
12031         if(rightsBoard[BOARD_HEIGHT-1][f] == 2) { if(kf != NoRights) err=10; boards[0][CASTLING][5] = kf = f; }
12032       if(kf == NoRights) kf = 4;
12033       for(f=BOARD_RGHT-1; f>=0; f--)
12034         if(rightsBoard[BOARD_HEIGHT-1][f] == 1) { err++; boards[0][CASTLING][3+(f<kf)] = f; }
12035       if(err + 2 > nrCastlingRights) DisplayError("unclear castling rights", 0);
12036
12037       boards[0][EP_STATUS] = EP_NONE;
12038 #if 0
12039       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12040     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12041         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12042         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12043       } else boards[0][CASTLING][2] = NoRights;
12044     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12045         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12046         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12047       } else boards[0][CASTLING][5] = NoRights;
12048 #endif
12049     }
12050     SendToProgram("force\n", &first);
12051     if (blackPlaysFirst) {
12052         strcpy(moveList[0], "");
12053         strcpy(parseList[0], "");
12054         currentMove = forwardMostMove = backwardMostMove = 1;
12055         CopyBoard(boards[1], boards[0]);
12056     } else {
12057         currentMove = forwardMostMove = backwardMostMove = 0;
12058     }
12059     SendBoard(&first, forwardMostMove);
12060     if (appData.debugMode) {
12061         fprintf(debugFP, "EditPosDone\n");
12062     }
12063     DisplayTitle("");
12064     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12065     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12066     gameMode = EditGame;
12067     ModeHighlight();
12068     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12069     ClearHighlights(); /* [AS] */
12070 }
12071
12072 /* Pause for `ms' milliseconds */
12073 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12074 void
12075 TimeDelay(ms)
12076      long ms;
12077 {
12078     TimeMark m1, m2;
12079
12080     GetTimeMark(&m1);
12081     do {
12082         GetTimeMark(&m2);
12083     } while (SubtractTimeMarks(&m2, &m1) < ms);
12084 }
12085
12086 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12087 void
12088 SendMultiLineToICS(buf)
12089      char *buf;
12090 {
12091     char temp[MSG_SIZ+1], *p;
12092     int len;
12093
12094     len = strlen(buf);
12095     if (len > MSG_SIZ)
12096       len = MSG_SIZ;
12097
12098     strncpy(temp, buf, len);
12099     temp[len] = 0;
12100
12101     p = temp;
12102     while (*p) {
12103         if (*p == '\n' || *p == '\r')
12104           *p = ' ';
12105         ++p;
12106     }
12107
12108     strcat(temp, "\n");
12109     SendToICS(temp);
12110     SendToPlayer(temp, strlen(temp));
12111 }
12112
12113 void
12114 SetWhiteToPlayEvent()
12115 {
12116     if (gameMode == EditPosition) {
12117         blackPlaysFirst = FALSE;
12118         DisplayBothClocks();    /* works because currentMove is 0 */
12119     } else if (gameMode == IcsExamining) {
12120         SendToICS(ics_prefix);
12121         SendToICS("tomove white\n");
12122     }
12123 }
12124
12125 void
12126 SetBlackToPlayEvent()
12127 {
12128     if (gameMode == EditPosition) {
12129         blackPlaysFirst = TRUE;
12130         currentMove = 1;        /* kludge */
12131         DisplayBothClocks();
12132         currentMove = 0;
12133     } else if (gameMode == IcsExamining) {
12134         SendToICS(ics_prefix);
12135         SendToICS("tomove black\n");
12136     }
12137 }
12138
12139 void
12140 EditPositionMenuEvent(selection, x, y)
12141      ChessSquare selection;
12142      int x, y;
12143 {
12144     char buf[MSG_SIZ];
12145     ChessSquare piece = boards[0][y][x];
12146     int rights = 0; // [HGM] editrights: most new pieces get no castling rights
12147
12148     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12149
12150     switch (selection) {
12151       case ClearBoard:
12152         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12153             SendToICS(ics_prefix);
12154             SendToICS("bsetup clear\n");
12155         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12156             SendToICS(ics_prefix);
12157             SendToICS("clearboard\n");
12158         } else {
12159             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12160                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12161                 for (y = 0; y < BOARD_HEIGHT; y++) {
12162                     if (gameMode == IcsExamining) {
12163                         if (boards[currentMove][y][x] != EmptySquare) {
12164                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12165                                     AAA + x, ONE + y);
12166                             SendToICS(buf);
12167                         }
12168                     } else {
12169                         boards[0][y][x] = p;
12170                         rightsBoard[y][x] = 0; // [HGM] editrights: clear all castling rights
12171                     }
12172                 }
12173             }
12174         }
12175         if (gameMode == EditPosition) {
12176             DrawPosition(FALSE, boards[0]);
12177         }
12178         break;
12179
12180       case WhitePlay:
12181         SetWhiteToPlayEvent();
12182         break;
12183
12184       case BlackPlay:
12185         SetBlackToPlayEvent();
12186         break;
12187
12188       case NoRights:
12189         rightsBoard[y][x] = 0;
12190         break;
12191
12192       case GrantRights:
12193         { ChessSquare p = boards[0][y][x];
12194           rightsBoard[y][x] = 1;
12195           if(p == WhiteKing || p == WhiteUnicorn || p == BlackKing || p == BlackUnicorn)
12196                 rightsBoard[y][x] = 2;
12197         }
12198         break;
12199
12200       case EmptySquare:
12201         if (gameMode == IcsExamining) {
12202             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12203             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12204             SendToICS(buf);
12205         } else {
12206             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12207                 if(x == BOARD_LEFT-2) {
12208                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12209                     boards[0][y][1] = 0;
12210                 } else
12211                 if(x == BOARD_RGHT+1) {
12212                     if(y >= gameInfo.holdingsSize) break;
12213                     boards[0][y][BOARD_WIDTH-2] = 0;
12214                 } else break;
12215             }
12216             boards[0][y][x] = EmptySquare;
12217             DrawPosition(FALSE, boards[0]);
12218         }
12219         break;
12220
12221       case PromotePiece:
12222         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12223            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12224             selection = (ChessSquare) (PROMOTED piece);
12225         } else if(piece == EmptySquare) selection = WhiteSilver;
12226         else selection = (ChessSquare)((int)piece - 1);
12227         goto defaultlabel;
12228
12229       case DemotePiece:
12230         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12231            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12232             selection = (ChessSquare) (DEMOTED piece);
12233         } else if(piece == EmptySquare) selection = BlackSilver;
12234         else selection = (ChessSquare)((int)piece + 1);
12235         goto defaultlabel;
12236
12237       case WhiteQueen:
12238       case BlackQueen:
12239         if(gameInfo.variant == VariantShatranj ||
12240            gameInfo.variant == VariantXiangqi  ||
12241            gameInfo.variant == VariantCourier  ||
12242            gameInfo.variant == VariantMakruk     )
12243             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12244         goto defaultlabel;
12245
12246       case WhiteRook: // [HGM] editrights: corner Rooks get castling rights by default
12247         if(y == 0 && (x == BOARD_LEFT || x == BOARD_RGHT-1)) rights = 1;
12248         goto defaultlabel;
12249
12250       case BlackRook:
12251         if(y == BOARD_HEIGHT-1 && (x == BOARD_LEFT || x == BOARD_RGHT-1)) rights = 1;
12252         goto defaultlabel;
12253
12254       case WhiteKing:
12255       case BlackKing:
12256         if(gameInfo.variant == VariantXiangqi)
12257             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12258         if(gameInfo.variant == VariantKnightmate)
12259             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12260       case WhiteUnicorn:
12261       case BlackUnicorn:
12262         if(y == (selection <= WhiteKing ? 0 : BOARD_HEIGHT-1) && (x == BOARD_WIDTH>>1)) 
12263             rights = 2; // [HGM] editrights: King on right-center file gets rights
12264       default:
12265         defaultlabel:
12266         if (gameMode == IcsExamining) {
12267             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12268             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12269                     PieceToChar(selection), AAA + x, ONE + y);
12270             SendToICS(buf);
12271         } else {
12272             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12273                 int n;
12274                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12275                     n = PieceToNumber(selection - BlackPawn);
12276                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12277                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12278                     boards[0][BOARD_HEIGHT-1-n][1]++;
12279                 } else
12280                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12281                     n = PieceToNumber(selection);
12282                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12283                     boards[0][n][BOARD_WIDTH-1] = selection;
12284                     boards[0][n][BOARD_WIDTH-2]++;
12285                 }
12286             } else
12287             boards[0][y][x] = selection;
12288             rightsBoard[y][x] = rights; // [HGM] editrights: set default rights of created piece
12289             DrawPosition(TRUE, boards[0]);
12290         }
12291         break;
12292     }
12293 }
12294
12295
12296 void
12297 DropMenuEvent(selection, x, y)
12298      ChessSquare selection;
12299      int x, y;
12300 {
12301     ChessMove moveType;
12302
12303     switch (gameMode) {
12304       case IcsPlayingWhite:
12305       case MachinePlaysBlack:
12306         if (!WhiteOnMove(currentMove)) {
12307             DisplayMoveError(_("It is Black's turn"));
12308             return;
12309         }
12310         moveType = WhiteDrop;
12311         break;
12312       case IcsPlayingBlack:
12313       case MachinePlaysWhite:
12314         if (WhiteOnMove(currentMove)) {
12315             DisplayMoveError(_("It is White's turn"));
12316             return;
12317         }
12318         moveType = BlackDrop;
12319         break;
12320       case EditGame:
12321         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12322         break;
12323       default:
12324         return;
12325     }
12326
12327     if (moveType == BlackDrop && selection < BlackPawn) {
12328       selection = (ChessSquare) ((int) selection
12329                                  + (int) BlackPawn - (int) WhitePawn);
12330     }
12331     if (boards[currentMove][y][x] != EmptySquare) {
12332         DisplayMoveError(_("That square is occupied"));
12333         return;
12334     }
12335
12336     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12337 }
12338
12339 void
12340 AcceptEvent()
12341 {
12342     /* Accept a pending offer of any kind from opponent */
12343
12344     if (appData.icsActive) {
12345         SendToICS(ics_prefix);
12346         SendToICS("accept\n");
12347     } else if (cmailMsgLoaded) {
12348         if (currentMove == cmailOldMove &&
12349             commentList[cmailOldMove] != NULL &&
12350             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12351                    "Black offers a draw" : "White offers a draw")) {
12352             TruncateGame();
12353             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12354             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12355         } else {
12356             DisplayError(_("There is no pending offer on this move"), 0);
12357             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12358         }
12359     } else {
12360         /* Not used for offers from chess program */
12361     }
12362 }
12363
12364 void
12365 DeclineEvent()
12366 {
12367     /* Decline a pending offer of any kind from opponent */
12368
12369     if (appData.icsActive) {
12370         SendToICS(ics_prefix);
12371         SendToICS("decline\n");
12372     } else if (cmailMsgLoaded) {
12373         if (currentMove == cmailOldMove &&
12374             commentList[cmailOldMove] != NULL &&
12375             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12376                    "Black offers a draw" : "White offers a draw")) {
12377 #ifdef NOTDEF
12378             AppendComment(cmailOldMove, "Draw declined", TRUE);
12379             DisplayComment(cmailOldMove - 1, "Draw declined");
12380 #endif /*NOTDEF*/
12381         } else {
12382             DisplayError(_("There is no pending offer on this move"), 0);
12383         }
12384     } else {
12385         /* Not used for offers from chess program */
12386     }
12387 }
12388
12389 void
12390 RematchEvent()
12391 {
12392     /* Issue ICS rematch command */
12393     if (appData.icsActive) {
12394         SendToICS(ics_prefix);
12395         SendToICS("rematch\n");
12396     }
12397 }
12398
12399 void
12400 CallFlagEvent()
12401 {
12402     /* Call your opponent's flag (claim a win on time) */
12403     if (appData.icsActive) {
12404         SendToICS(ics_prefix);
12405         SendToICS("flag\n");
12406     } else {
12407         switch (gameMode) {
12408           default:
12409             return;
12410           case MachinePlaysWhite:
12411             if (whiteFlag) {
12412                 if (blackFlag)
12413                   GameEnds(GameIsDrawn, "Both players ran out of time",
12414                            GE_PLAYER);
12415                 else
12416                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12417             } else {
12418                 DisplayError(_("Your opponent is not out of time"), 0);
12419             }
12420             break;
12421           case MachinePlaysBlack:
12422             if (blackFlag) {
12423                 if (whiteFlag)
12424                   GameEnds(GameIsDrawn, "Both players ran out of time",
12425                            GE_PLAYER);
12426                 else
12427                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12428             } else {
12429                 DisplayError(_("Your opponent is not out of time"), 0);
12430             }
12431             break;
12432         }
12433     }
12434 }
12435
12436 void
12437 DrawEvent()
12438 {
12439     /* Offer draw or accept pending draw offer from opponent */
12440
12441     if (appData.icsActive) {
12442         /* Note: tournament rules require draw offers to be
12443            made after you make your move but before you punch
12444            your clock.  Currently ICS doesn't let you do that;
12445            instead, you immediately punch your clock after making
12446            a move, but you can offer a draw at any time. */
12447
12448         SendToICS(ics_prefix);
12449         SendToICS("draw\n");
12450         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12451     } else if (cmailMsgLoaded) {
12452         if (currentMove == cmailOldMove &&
12453             commentList[cmailOldMove] != NULL &&
12454             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12455                    "Black offers a draw" : "White offers a draw")) {
12456             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12457             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12458         } else if (currentMove == cmailOldMove + 1) {
12459             char *offer = WhiteOnMove(cmailOldMove) ?
12460               "White offers a draw" : "Black offers a draw";
12461             AppendComment(currentMove, offer, TRUE);
12462             DisplayComment(currentMove - 1, offer);
12463             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12464         } else {
12465             DisplayError(_("You must make your move before offering a draw"), 0);
12466             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12467         }
12468     } else if (first.offeredDraw) {
12469         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12470     } else {
12471         if (first.sendDrawOffers) {
12472             SendToProgram("draw\n", &first);
12473             userOfferedDraw = TRUE;
12474         }
12475     }
12476 }
12477
12478 void
12479 AdjournEvent()
12480 {
12481     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12482
12483     if (appData.icsActive) {
12484         SendToICS(ics_prefix);
12485         SendToICS("adjourn\n");
12486     } else {
12487         /* Currently GNU Chess doesn't offer or accept Adjourns */
12488     }
12489 }
12490
12491
12492 void
12493 AbortEvent()
12494 {
12495     /* Offer Abort or accept pending Abort offer from opponent */
12496
12497     if (appData.icsActive) {
12498         SendToICS(ics_prefix);
12499         SendToICS("abort\n");
12500     } else {
12501         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12502     }
12503 }
12504
12505 void
12506 ResignEvent()
12507 {
12508     /* Resign.  You can do this even if it's not your turn. */
12509
12510     if (appData.icsActive) {
12511         SendToICS(ics_prefix);
12512         SendToICS("resign\n");
12513     } else {
12514         switch (gameMode) {
12515           case MachinePlaysWhite:
12516             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12517             break;
12518           case MachinePlaysBlack:
12519             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12520             break;
12521           case EditGame:
12522             if (cmailMsgLoaded) {
12523                 TruncateGame();
12524                 if (WhiteOnMove(cmailOldMove)) {
12525                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12526                 } else {
12527                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12528                 }
12529                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12530             }
12531             break;
12532           default:
12533             break;
12534         }
12535     }
12536 }
12537
12538
12539 void
12540 StopObservingEvent()
12541 {
12542     /* Stop observing current games */
12543     SendToICS(ics_prefix);
12544     SendToICS("unobserve\n");
12545 }
12546
12547 void
12548 StopExaminingEvent()
12549 {
12550     /* Stop observing current game */
12551     SendToICS(ics_prefix);
12552     SendToICS("unexamine\n");
12553 }
12554
12555 void
12556 ForwardInner(target)
12557      int target;
12558 {
12559     int limit;
12560
12561     if (appData.debugMode)
12562         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12563                 target, currentMove, forwardMostMove);
12564
12565     if (gameMode == EditPosition)
12566       return;
12567
12568     if (gameMode == PlayFromGameFile && !pausing)
12569       PauseEvent();
12570
12571     if (gameMode == IcsExamining && pausing)
12572       limit = pauseExamForwardMostMove;
12573     else
12574       limit = forwardMostMove;
12575
12576     if (target > limit) target = limit;
12577
12578     if (target > 0 && moveList[target - 1][0]) {
12579         int fromX, fromY, toX, toY;
12580         toX = moveList[target - 1][2] - AAA;
12581         toY = moveList[target - 1][3] - ONE;
12582         if (moveList[target - 1][1] == '@') {
12583             if (appData.highlightLastMove) {
12584                 SetHighlights(-1, -1, toX, toY);
12585             }
12586         } else {
12587             fromX = moveList[target - 1][0] - AAA;
12588             fromY = moveList[target - 1][1] - ONE;
12589             if (target == currentMove + 1) {
12590                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12591             }
12592             if (appData.highlightLastMove) {
12593                 SetHighlights(fromX, fromY, toX, toY);
12594             }
12595         }
12596     }
12597     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12598         gameMode == Training || gameMode == PlayFromGameFile ||
12599         gameMode == AnalyzeFile) {
12600         while (currentMove < target) {
12601             SendMoveToProgram(currentMove++, &first);
12602         }
12603     } else {
12604         currentMove = target;
12605     }
12606
12607     if (gameMode == EditGame || gameMode == EndOfGame) {
12608         whiteTimeRemaining = timeRemaining[0][currentMove];
12609         blackTimeRemaining = timeRemaining[1][currentMove];
12610     }
12611     DisplayBothClocks();
12612     DisplayMove(currentMove - 1);
12613     DrawPosition(FALSE, boards[currentMove]);
12614     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12615     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12616         DisplayComment(currentMove - 1, commentList[currentMove]);
12617     }
12618 }
12619
12620
12621 void
12622 ForwardEvent()
12623 {
12624     if (gameMode == IcsExamining && !pausing) {
12625         SendToICS(ics_prefix);
12626         SendToICS("forward\n");
12627     } else {
12628         ForwardInner(currentMove + 1);
12629     }
12630 }
12631
12632 void
12633 ToEndEvent()
12634 {
12635     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12636         /* to optimze, we temporarily turn off analysis mode while we feed
12637          * the remaining moves to the engine. Otherwise we get analysis output
12638          * after each move.
12639          */
12640         if (first.analysisSupport) {
12641           SendToProgram("exit\nforce\n", &first);
12642           first.analyzing = FALSE;
12643         }
12644     }
12645
12646     if (gameMode == IcsExamining && !pausing) {
12647         SendToICS(ics_prefix);
12648         SendToICS("forward 999999\n");
12649     } else {
12650         ForwardInner(forwardMostMove);
12651     }
12652
12653     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12654         /* we have fed all the moves, so reactivate analysis mode */
12655         SendToProgram("analyze\n", &first);
12656         first.analyzing = TRUE;
12657         /*first.maybeThinking = TRUE;*/
12658         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12659     }
12660 }
12661
12662 void
12663 BackwardInner(target)
12664      int target;
12665 {
12666     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12667
12668     if (appData.debugMode)
12669         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12670                 target, currentMove, forwardMostMove);
12671
12672     if (gameMode == EditPosition) return;
12673     if (currentMove <= backwardMostMove) {
12674         ClearHighlights();
12675         DrawPosition(full_redraw, boards[currentMove]);
12676         return;
12677     }
12678     if (gameMode == PlayFromGameFile && !pausing)
12679       PauseEvent();
12680
12681     if (moveList[target][0]) {
12682         int fromX, fromY, toX, toY;
12683         toX = moveList[target][2] - AAA;
12684         toY = moveList[target][3] - ONE;
12685         if (moveList[target][1] == '@') {
12686             if (appData.highlightLastMove) {
12687                 SetHighlights(-1, -1, toX, toY);
12688             }
12689         } else {
12690             fromX = moveList[target][0] - AAA;
12691             fromY = moveList[target][1] - ONE;
12692             if (target == currentMove - 1) {
12693                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12694             }
12695             if (appData.highlightLastMove) {
12696                 SetHighlights(fromX, fromY, toX, toY);
12697             }
12698         }
12699     }
12700     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12701         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12702         while (currentMove > target) {
12703             SendToProgram("undo\n", &first);
12704             currentMove--;
12705         }
12706     } else {
12707         currentMove = target;
12708     }
12709
12710     if (gameMode == EditGame || gameMode == EndOfGame) {
12711         whiteTimeRemaining = timeRemaining[0][currentMove];
12712         blackTimeRemaining = timeRemaining[1][currentMove];
12713     }
12714     DisplayBothClocks();
12715     DisplayMove(currentMove - 1);
12716     DrawPosition(full_redraw, boards[currentMove]);
12717     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12718     // [HGM] PV info: routine tests if comment empty
12719     DisplayComment(currentMove - 1, commentList[currentMove]);
12720 }
12721
12722 void
12723 BackwardEvent()
12724 {
12725     if (gameMode == IcsExamining && !pausing) {
12726         SendToICS(ics_prefix);
12727         SendToICS("backward\n");
12728     } else {
12729         BackwardInner(currentMove - 1);
12730     }
12731 }
12732
12733 void
12734 ToStartEvent()
12735 {
12736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12737         /* to optimize, we temporarily turn off analysis mode while we undo
12738          * all the moves. Otherwise we get analysis output after each undo.
12739          */
12740         if (first.analysisSupport) {
12741           SendToProgram("exit\nforce\n", &first);
12742           first.analyzing = FALSE;
12743         }
12744     }
12745
12746     if (gameMode == IcsExamining && !pausing) {
12747         SendToICS(ics_prefix);
12748         SendToICS("backward 999999\n");
12749     } else {
12750         BackwardInner(backwardMostMove);
12751     }
12752
12753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12754         /* we have fed all the moves, so reactivate analysis mode */
12755         SendToProgram("analyze\n", &first);
12756         first.analyzing = TRUE;
12757         /*first.maybeThinking = TRUE;*/
12758         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12759     }
12760 }
12761
12762 void
12763 ToNrEvent(int to)
12764 {
12765   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12766   if (to >= forwardMostMove) to = forwardMostMove;
12767   if (to <= backwardMostMove) to = backwardMostMove;
12768   if (to < currentMove) {
12769     BackwardInner(to);
12770   } else {
12771     ForwardInner(to);
12772   }
12773 }
12774
12775 void
12776 RevertEvent()
12777 {
12778     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12779         return;
12780     }
12781     if (gameMode != IcsExamining) {
12782         DisplayError(_("You are not examining a game"), 0);
12783         return;
12784     }
12785     if (pausing) {
12786         DisplayError(_("You can't revert while pausing"), 0);
12787         return;
12788     }
12789     SendToICS(ics_prefix);
12790     SendToICS("revert\n");
12791 }
12792
12793 void
12794 RetractMoveEvent()
12795 {
12796     switch (gameMode) {
12797       case MachinePlaysWhite:
12798       case MachinePlaysBlack:
12799         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12800             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12801             return;
12802         }
12803         if (forwardMostMove < 2) return;
12804         currentMove = forwardMostMove = forwardMostMove - 2;
12805         whiteTimeRemaining = timeRemaining[0][currentMove];
12806         blackTimeRemaining = timeRemaining[1][currentMove];
12807         DisplayBothClocks();
12808         DisplayMove(currentMove - 1);
12809         ClearHighlights();/*!! could figure this out*/
12810         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12811         SendToProgram("remove\n", &first);
12812         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12813         break;
12814
12815       case BeginningOfGame:
12816       default:
12817         break;
12818
12819       case IcsPlayingWhite:
12820       case IcsPlayingBlack:
12821         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12822             SendToICS(ics_prefix);
12823             SendToICS("takeback 2\n");
12824         } else {
12825             SendToICS(ics_prefix);
12826             SendToICS("takeback 1\n");
12827         }
12828         break;
12829     }
12830 }
12831
12832 void
12833 MoveNowEvent()
12834 {
12835     ChessProgramState *cps;
12836
12837     switch (gameMode) {
12838       case MachinePlaysWhite:
12839         if (!WhiteOnMove(forwardMostMove)) {
12840             DisplayError(_("It is your turn"), 0);
12841             return;
12842         }
12843         cps = &first;
12844         break;
12845       case MachinePlaysBlack:
12846         if (WhiteOnMove(forwardMostMove)) {
12847             DisplayError(_("It is your turn"), 0);
12848             return;
12849         }
12850         cps = &first;
12851         break;
12852       case TwoMachinesPlay:
12853         if (WhiteOnMove(forwardMostMove) ==
12854             (first.twoMachinesColor[0] == 'w')) {
12855             cps = &first;
12856         } else {
12857             cps = &second;
12858         }
12859         break;
12860       case BeginningOfGame:
12861       default:
12862         return;
12863     }
12864     SendToProgram("?\n", cps);
12865 }
12866
12867 void
12868 TruncateGameEvent()
12869 {
12870     EditGameEvent();
12871     if (gameMode != EditGame) return;
12872     TruncateGame();
12873 }
12874
12875 void
12876 TruncateGame()
12877 {
12878     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12879     if (forwardMostMove > currentMove) {
12880         if (gameInfo.resultDetails != NULL) {
12881             free(gameInfo.resultDetails);
12882             gameInfo.resultDetails = NULL;
12883             gameInfo.result = GameUnfinished;
12884         }
12885         forwardMostMove = currentMove;
12886         HistorySet(parseList, backwardMostMove, forwardMostMove,
12887                    currentMove-1);
12888     }
12889 }
12890
12891 void
12892 HintEvent()
12893 {
12894     if (appData.noChessProgram) return;
12895     switch (gameMode) {
12896       case MachinePlaysWhite:
12897         if (WhiteOnMove(forwardMostMove)) {
12898             DisplayError(_("Wait until your turn"), 0);
12899             return;
12900         }
12901         break;
12902       case BeginningOfGame:
12903       case MachinePlaysBlack:
12904         if (!WhiteOnMove(forwardMostMove)) {
12905             DisplayError(_("Wait until your turn"), 0);
12906             return;
12907         }
12908         break;
12909       default:
12910         DisplayError(_("No hint available"), 0);
12911         return;
12912     }
12913     SendToProgram("hint\n", &first);
12914     hintRequested = TRUE;
12915 }
12916
12917 void
12918 BookEvent()
12919 {
12920     if (appData.noChessProgram) return;
12921     switch (gameMode) {
12922       case MachinePlaysWhite:
12923         if (WhiteOnMove(forwardMostMove)) {
12924             DisplayError(_("Wait until your turn"), 0);
12925             return;
12926         }
12927         break;
12928       case BeginningOfGame:
12929       case MachinePlaysBlack:
12930         if (!WhiteOnMove(forwardMostMove)) {
12931             DisplayError(_("Wait until your turn"), 0);
12932             return;
12933         }
12934         break;
12935       case EditPosition:
12936         EditPositionDone(TRUE);
12937         break;
12938       case TwoMachinesPlay:
12939         return;
12940       default:
12941         break;
12942     }
12943     SendToProgram("bk\n", &first);
12944     bookOutput[0] = NULLCHAR;
12945     bookRequested = TRUE;
12946 }
12947
12948 void
12949 AboutGameEvent()
12950 {
12951     char *tags = PGNTags(&gameInfo);
12952     TagsPopUp(tags, CmailMsg());
12953     free(tags);
12954 }
12955
12956 /* end button procedures */
12957
12958 void
12959 PrintPosition(fp, move)
12960      FILE *fp;
12961      int move;
12962 {
12963     int i, j;
12964
12965     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12966         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12967             char c = PieceToChar(boards[move][i][j]);
12968             fputc(c == 'x' ? '.' : c, fp);
12969             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12970         }
12971     }
12972     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12973       fprintf(fp, "white to play\n");
12974     else
12975       fprintf(fp, "black to play\n");
12976 }
12977
12978 void
12979 PrintOpponents(fp)
12980      FILE *fp;
12981 {
12982     if (gameInfo.white != NULL) {
12983         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12984     } else {
12985         fprintf(fp, "\n");
12986     }
12987 }
12988
12989 /* Find last component of program's own name, using some heuristics */
12990 void
12991 TidyProgramName(prog, host, buf)
12992      char *prog, *host, buf[MSG_SIZ];
12993 {
12994     char *p, *q;
12995     int local = (strcmp(host, "localhost") == 0);
12996     while (!local && (p = strchr(prog, ';')) != NULL) {
12997         p++;
12998         while (*p == ' ') p++;
12999         prog = p;
13000     }
13001     if (*prog == '"' || *prog == '\'') {
13002         q = strchr(prog + 1, *prog);
13003     } else {
13004         q = strchr(prog, ' ');
13005     }
13006     if (q == NULL) q = prog + strlen(prog);
13007     p = q;
13008     while (p >= prog && *p != '/' && *p != '\\') p--;
13009     p++;
13010     if(p == prog && *p == '"') p++;
13011     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13012     memcpy(buf, p, q - p);
13013     buf[q - p] = NULLCHAR;
13014     if (!local) {
13015         strcat(buf, "@");
13016         strcat(buf, host);
13017     }
13018 }
13019
13020 char *
13021 TimeControlTagValue()
13022 {
13023     char buf[MSG_SIZ];
13024     if (!appData.clockMode) {
13025         strcpy(buf, "-");
13026     } else if (movesPerSession > 0) {
13027         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13028     } else if (timeIncrement == 0) {
13029         sprintf(buf, "%ld", timeControl/1000);
13030     } else {
13031         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13032     }
13033     return StrSave(buf);
13034 }
13035
13036 void
13037 SetGameInfo()
13038 {
13039     /* This routine is used only for certain modes */
13040     VariantClass v = gameInfo.variant;
13041     ChessMove r = GameUnfinished;
13042     char *p = NULL;
13043
13044     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13045         r = gameInfo.result; 
13046         p = gameInfo.resultDetails; 
13047         gameInfo.resultDetails = NULL;
13048     }
13049     ClearGameInfo(&gameInfo);
13050     gameInfo.variant = v;
13051
13052     switch (gameMode) {
13053       case MachinePlaysWhite:
13054         gameInfo.event = StrSave( appData.pgnEventHeader );
13055         gameInfo.site = StrSave(HostName());
13056         gameInfo.date = PGNDate();
13057         gameInfo.round = StrSave("-");
13058         gameInfo.white = StrSave(first.tidy);
13059         gameInfo.black = StrSave(UserName());
13060         gameInfo.timeControl = TimeControlTagValue();
13061         break;
13062
13063       case MachinePlaysBlack:
13064         gameInfo.event = StrSave( appData.pgnEventHeader );
13065         gameInfo.site = StrSave(HostName());
13066         gameInfo.date = PGNDate();
13067         gameInfo.round = StrSave("-");
13068         gameInfo.white = StrSave(UserName());
13069         gameInfo.black = StrSave(first.tidy);
13070         gameInfo.timeControl = TimeControlTagValue();
13071         break;
13072
13073       case TwoMachinesPlay:
13074         gameInfo.event = StrSave( appData.pgnEventHeader );
13075         gameInfo.site = StrSave(HostName());
13076         gameInfo.date = PGNDate();
13077         if (matchGame > 0) {
13078             char buf[MSG_SIZ];
13079             sprintf(buf, "%d", matchGame);
13080             gameInfo.round = StrSave(buf);
13081         } else {
13082             gameInfo.round = StrSave("-");
13083         }
13084         if (first.twoMachinesColor[0] == 'w') {
13085             gameInfo.white = StrSave(first.tidy);
13086             gameInfo.black = StrSave(second.tidy);
13087         } else {
13088             gameInfo.white = StrSave(second.tidy);
13089             gameInfo.black = StrSave(first.tidy);
13090         }
13091         gameInfo.timeControl = TimeControlTagValue();
13092         break;
13093
13094       case EditGame:
13095         gameInfo.event = StrSave("Edited game");
13096         gameInfo.site = StrSave(HostName());
13097         gameInfo.date = PGNDate();
13098         gameInfo.round = StrSave("-");
13099         gameInfo.white = StrSave("-");
13100         gameInfo.black = StrSave("-");
13101         gameInfo.result = r;
13102         gameInfo.resultDetails = p;
13103         break;
13104
13105       case EditPosition:
13106         gameInfo.event = StrSave("Edited position");
13107         gameInfo.site = StrSave(HostName());
13108         gameInfo.date = PGNDate();
13109         gameInfo.round = StrSave("-");
13110         gameInfo.white = StrSave("-");
13111         gameInfo.black = StrSave("-");
13112         break;
13113
13114       case IcsPlayingWhite:
13115       case IcsPlayingBlack:
13116       case IcsObserving:
13117       case IcsExamining:
13118         break;
13119
13120       case PlayFromGameFile:
13121         gameInfo.event = StrSave("Game from non-PGN file");
13122         gameInfo.site = StrSave(HostName());
13123         gameInfo.date = PGNDate();
13124         gameInfo.round = StrSave("-");
13125         gameInfo.white = StrSave("?");
13126         gameInfo.black = StrSave("?");
13127         break;
13128
13129       default:
13130         break;
13131     }
13132 }
13133
13134 void
13135 ReplaceComment(index, text)
13136      int index;
13137      char *text;
13138 {
13139     int len;
13140
13141     while (*text == '\n') text++;
13142     len = strlen(text);
13143     while (len > 0 && text[len - 1] == '\n') len--;
13144
13145     if (commentList[index] != NULL)
13146       free(commentList[index]);
13147
13148     if (len == 0) {
13149         commentList[index] = NULL;
13150         return;
13151     }
13152   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13153       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13154       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13155     commentList[index] = (char *) malloc(len + 2);
13156     strncpy(commentList[index], text, len);
13157     commentList[index][len] = '\n';
13158     commentList[index][len + 1] = NULLCHAR;
13159   } else { 
13160     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13161     char *p;
13162     commentList[index] = (char *) malloc(len + 6);
13163     strcpy(commentList[index], "{\n");
13164     strncpy(commentList[index]+2, text, len);
13165     commentList[index][len+2] = NULLCHAR;
13166     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13167     strcat(commentList[index], "\n}\n");
13168   }
13169 }
13170
13171 void
13172 CrushCRs(text)
13173      char *text;
13174 {
13175   char *p = text;
13176   char *q = text;
13177   char ch;
13178
13179   do {
13180     ch = *p++;
13181     if (ch == '\r') continue;
13182     *q++ = ch;
13183   } while (ch != '\0');
13184 }
13185
13186 void
13187 AppendComment(index, text, addBraces)
13188      int index;
13189      char *text;
13190      Boolean addBraces; // [HGM] braces: tells if we should add {}
13191 {
13192     int oldlen, len;
13193     char *old;
13194
13195 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13196     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13197
13198     CrushCRs(text);
13199     while (*text == '\n') text++;
13200     len = strlen(text);
13201     while (len > 0 && text[len - 1] == '\n') len--;
13202
13203     if (len == 0) return;
13204
13205     if (commentList[index] != NULL) {
13206         old = commentList[index];
13207         oldlen = strlen(old);
13208         while(commentList[index][oldlen-1] ==  '\n')
13209           commentList[index][--oldlen] = NULLCHAR;
13210         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13211         strcpy(commentList[index], old);
13212         free(old);
13213         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13214         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13215           if(addBraces) addBraces = FALSE; else { text++; len--; }
13216           while (*text == '\n') { text++; len--; }
13217           commentList[index][--oldlen] = NULLCHAR;
13218       }
13219         if(addBraces) strcat(commentList[index], "\n{\n");
13220         else          strcat(commentList[index], "\n");
13221         strcat(commentList[index], text);
13222         if(addBraces) strcat(commentList[index], "\n}\n");
13223         else          strcat(commentList[index], "\n");
13224     } else {
13225         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13226         if(addBraces)
13227              strcpy(commentList[index], "{\n");
13228         else commentList[index][0] = NULLCHAR;
13229         strcat(commentList[index], text);
13230         strcat(commentList[index], "\n");
13231         if(addBraces) strcat(commentList[index], "}\n");
13232     }
13233 }
13234
13235 static char * FindStr( char * text, char * sub_text )
13236 {
13237     char * result = strstr( text, sub_text );
13238
13239     if( result != NULL ) {
13240         result += strlen( sub_text );
13241     }
13242
13243     return result;
13244 }
13245
13246 /* [AS] Try to extract PV info from PGN comment */
13247 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13248 char *GetInfoFromComment( int index, char * text )
13249 {
13250     char * sep = text;
13251
13252     if( text != NULL && index > 0 ) {
13253         int score = 0;
13254         int depth = 0;
13255         int time = -1, sec = 0, deci;
13256         char * s_eval = FindStr( text, "[%eval " );
13257         char * s_emt = FindStr( text, "[%emt " );
13258
13259         if( s_eval != NULL || s_emt != NULL ) {
13260             /* New style */
13261             char delim;
13262
13263             if( s_eval != NULL ) {
13264                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13265                     return text;
13266                 }
13267
13268                 if( delim != ']' ) {
13269                     return text;
13270                 }
13271             }
13272
13273             if( s_emt != NULL ) {
13274             }
13275                 return text;
13276         }
13277         else {
13278             /* We expect something like: [+|-]nnn.nn/dd */
13279             int score_lo = 0;
13280
13281             if(*text != '{') return text; // [HGM] braces: must be normal comment
13282
13283             sep = strchr( text, '/' );
13284             if( sep == NULL || sep < (text+4) ) {
13285                 return text;
13286             }
13287
13288             time = -1; sec = -1; deci = -1;
13289             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13290                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13291                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13292                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13293                 return text;
13294             }
13295
13296             if( score_lo < 0 || score_lo >= 100 ) {
13297                 return text;
13298             }
13299
13300             if(sec >= 0) time = 600*time + 10*sec; else
13301             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13302
13303             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13304
13305             /* [HGM] PV time: now locate end of PV info */
13306             while( *++sep >= '0' && *sep <= '9'); // strip depth
13307             if(time >= 0)
13308             while( *++sep >= '0' && *sep <= '9'); // strip time
13309             if(sec >= 0)
13310             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13311             if(deci >= 0)
13312             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13313             while(*sep == ' ') sep++;
13314         }
13315
13316         if( depth <= 0 ) {
13317             return text;
13318         }
13319
13320         if( time < 0 ) {
13321             time = -1;
13322         }
13323
13324         pvInfoList[index-1].depth = depth;
13325         pvInfoList[index-1].score = score;
13326         pvInfoList[index-1].time  = 10*time; // centi-sec
13327         if(*sep == '}') *sep = 0; else *--sep = '{';
13328     }
13329     return sep;
13330 }
13331
13332 void
13333 SendToProgram(message, cps)
13334      char *message;
13335      ChessProgramState *cps;
13336 {
13337     int count, outCount, error;
13338     char buf[MSG_SIZ];
13339
13340     if (cps->pr == NULL) return;
13341     Attention(cps);
13342
13343     if (appData.debugMode) {
13344         TimeMark now;
13345         GetTimeMark(&now);
13346         fprintf(debugFP, "%ld >%-6s: %s",
13347                 SubtractTimeMarks(&now, &programStartTime),
13348                 cps->which, message);
13349     }
13350
13351     count = strlen(message);
13352     outCount = OutputToProcess(cps->pr, message, count, &error);
13353     if (outCount < count && !exiting
13354                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13355         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13356         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13357             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13358                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13359                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13360             } else {
13361                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13362             }
13363             gameInfo.resultDetails = StrSave(buf);
13364         }
13365         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13366     }
13367 }
13368
13369 void
13370 ReceiveFromProgram(isr, closure, message, count, error)
13371      InputSourceRef isr;
13372      VOIDSTAR closure;
13373      char *message;
13374      int count;
13375      int error;
13376 {
13377     char *end_str;
13378     char buf[MSG_SIZ];
13379     ChessProgramState *cps = (ChessProgramState *)closure;
13380
13381     if (isr != cps->isr) return; /* Killed intentionally */
13382     if (count <= 0) {
13383         if (count == 0) {
13384             sprintf(buf,
13385                     _("Error: %s chess program (%s) exited unexpectedly"),
13386                     cps->which, cps->program);
13387         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13388                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13389                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13390                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13391                 } else {
13392                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13393                 }
13394                 gameInfo.resultDetails = StrSave(buf);
13395             }
13396             RemoveInputSource(cps->isr);
13397             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13398         } else {
13399             sprintf(buf,
13400                     _("Error reading from %s chess program (%s)"),
13401                     cps->which, cps->program);
13402             RemoveInputSource(cps->isr);
13403
13404             /* [AS] Program is misbehaving badly... kill it */
13405             if( count == -2 ) {
13406                 DestroyChildProcess( cps->pr, 9 );
13407                 cps->pr = NoProc;
13408             }
13409
13410             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13411         }
13412         return;
13413     }
13414
13415     if ((end_str = strchr(message, '\r')) != NULL)
13416       *end_str = NULLCHAR;
13417     if ((end_str = strchr(message, '\n')) != NULL)
13418       *end_str = NULLCHAR;
13419
13420     if (appData.debugMode) {
13421         TimeMark now; int print = 1;
13422         char *quote = ""; char c; int i;
13423
13424         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13425                 char start = message[0];
13426                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13427                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13428                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13429                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13430                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13431                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13432                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13433                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13434                         { quote = "# "; print = (appData.engineComments == 2); }
13435                 message[0] = start; // restore original message
13436         }
13437         if(print) {
13438                 GetTimeMark(&now);
13439                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13440                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13441                         quote,
13442                         message);
13443         }
13444     }
13445
13446     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13447     if (appData.icsEngineAnalyze) {
13448         if (strstr(message, "whisper") != NULL ||
13449              strstr(message, "kibitz") != NULL ||
13450             strstr(message, "tellics") != NULL) return;
13451     }
13452
13453     HandleMachineMove(message, cps);
13454 }
13455
13456
13457 void
13458 SendTimeControl(cps, mps, tc, inc, sd, st)
13459      ChessProgramState *cps;
13460      int mps, inc, sd, st;
13461      long tc;
13462 {
13463     char buf[MSG_SIZ];
13464     int seconds;
13465
13466     if( timeControl_2 > 0 ) {
13467         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13468             tc = timeControl_2;
13469         }
13470     }
13471     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13472     inc /= cps->timeOdds;
13473     st  /= cps->timeOdds;
13474
13475     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13476
13477     if (st > 0) {
13478       /* Set exact time per move, normally using st command */
13479       if (cps->stKludge) {
13480         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13481         seconds = st % 60;
13482         if (seconds == 0) {
13483           sprintf(buf, "level 1 %d\n", st/60);
13484         } else {
13485           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13486         }
13487       } else {
13488         sprintf(buf, "st %d\n", st);
13489       }
13490     } else {
13491       /* Set conventional or incremental time control, using level command */
13492       if (seconds == 0) {
13493         /* Note old gnuchess bug -- minutes:seconds used to not work.
13494            Fixed in later versions, but still avoid :seconds
13495            when seconds is 0. */
13496         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13497       } else {
13498         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13499                 seconds, inc/1000);
13500       }
13501     }
13502     SendToProgram(buf, cps);
13503
13504     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13505     /* Orthogonally, limit search to given depth */
13506     if (sd > 0) {
13507       if (cps->sdKludge) {
13508         sprintf(buf, "depth\n%d\n", sd);
13509       } else {
13510         sprintf(buf, "sd %d\n", sd);
13511       }
13512       SendToProgram(buf, cps);
13513     }
13514
13515     if(cps->nps > 0) { /* [HGM] nps */
13516         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13517         else {
13518                 sprintf(buf, "nps %d\n", cps->nps);
13519               SendToProgram(buf, cps);
13520         }
13521     }
13522 }
13523
13524 ChessProgramState *WhitePlayer()
13525 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13526 {
13527     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13528        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13529         return &second;
13530     return &first;
13531 }
13532
13533 void
13534 SendTimeRemaining(cps, machineWhite)
13535      ChessProgramState *cps;
13536      int /*boolean*/ machineWhite;
13537 {
13538     char message[MSG_SIZ];
13539     long time, otime;
13540
13541     /* Note: this routine must be called when the clocks are stopped
13542        or when they have *just* been set or switched; otherwise
13543        it will be off by the time since the current tick started.
13544     */
13545     if (machineWhite) {
13546         time = whiteTimeRemaining / 10;
13547         otime = blackTimeRemaining / 10;
13548     } else {
13549         time = blackTimeRemaining / 10;
13550         otime = whiteTimeRemaining / 10;
13551     }
13552     /* [HGM] translate opponent's time by time-odds factor */
13553     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13554     if (appData.debugMode) {
13555         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13556     }
13557
13558     if (time <= 0) time = 1;
13559     if (otime <= 0) otime = 1;
13560
13561     sprintf(message, "time %ld\n", time);
13562     SendToProgram(message, cps);
13563
13564     sprintf(message, "otim %ld\n", otime);
13565     SendToProgram(message, cps);
13566 }
13567
13568 int
13569 BoolFeature(p, name, loc, cps)
13570      char **p;
13571      char *name;
13572      int *loc;
13573      ChessProgramState *cps;
13574 {
13575   char buf[MSG_SIZ];
13576   int len = strlen(name);
13577   int val;
13578   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13579     (*p) += len + 1;
13580     sscanf(*p, "%d", &val);
13581     *loc = (val != 0);
13582     while (**p && **p != ' ') (*p)++;
13583     sprintf(buf, "accepted %s\n", name);
13584     SendToProgram(buf, cps);
13585     return TRUE;
13586   }
13587   return FALSE;
13588 }
13589
13590 int
13591 IntFeature(p, name, loc, cps)
13592      char **p;
13593      char *name;
13594      int *loc;
13595      ChessProgramState *cps;
13596 {
13597   char buf[MSG_SIZ];
13598   int len = strlen(name);
13599   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13600     (*p) += len + 1;
13601     sscanf(*p, "%d", loc);
13602     while (**p && **p != ' ') (*p)++;
13603     sprintf(buf, "accepted %s\n", name);
13604     SendToProgram(buf, cps);
13605     return TRUE;
13606   }
13607   return FALSE;
13608 }
13609
13610 int
13611 StringFeature(p, name, loc, cps)
13612      char **p;
13613      char *name;
13614      char loc[];
13615      ChessProgramState *cps;
13616 {
13617   char buf[MSG_SIZ];
13618   int len = strlen(name);
13619   if (strncmp((*p), name, len) == 0
13620       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13621     (*p) += len + 2;
13622     sscanf(*p, "%[^\"]", loc);
13623     while (**p && **p != '\"') (*p)++;
13624     if (**p == '\"') (*p)++;
13625     sprintf(buf, "accepted %s\n", name);
13626     SendToProgram(buf, cps);
13627     return TRUE;
13628   }
13629   return FALSE;
13630 }
13631
13632 int
13633 ParseOption(Option *opt, ChessProgramState *cps)
13634 // [HGM] options: process the string that defines an engine option, and determine
13635 // name, type, default value, and allowed value range
13636 {
13637         char *p, *q, buf[MSG_SIZ];
13638         int n, min = (-1)<<31, max = 1<<31, def;
13639
13640         if(p = strstr(opt->name, " -spin ")) {
13641             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13642             if(max < min) max = min; // enforce consistency
13643             if(def < min) def = min;
13644             if(def > max) def = max;
13645             opt->value = def;
13646             opt->min = min;
13647             opt->max = max;
13648             opt->type = Spin;
13649         } else if((p = strstr(opt->name, " -slider "))) {
13650             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13651             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13652             if(max < min) max = min; // enforce consistency
13653             if(def < min) def = min;
13654             if(def > max) def = max;
13655             opt->value = def;
13656             opt->min = min;
13657             opt->max = max;
13658             opt->type = Spin; // Slider;
13659         } else if((p = strstr(opt->name, " -string "))) {
13660             opt->textValue = p+9;
13661             opt->type = TextBox;
13662         } else if((p = strstr(opt->name, " -file "))) {
13663             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13664             opt->textValue = p+7;
13665             opt->type = TextBox; // FileName;
13666         } else if((p = strstr(opt->name, " -path "))) {
13667             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13668             opt->textValue = p+7;
13669             opt->type = TextBox; // PathName;
13670         } else if(p = strstr(opt->name, " -check ")) {
13671             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13672             opt->value = (def != 0);
13673             opt->type = CheckBox;
13674         } else if(p = strstr(opt->name, " -combo ")) {
13675             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13676             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13677             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13678             opt->value = n = 0;
13679             while(q = StrStr(q, " /// ")) {
13680                 n++; *q = 0;    // count choices, and null-terminate each of them
13681                 q += 5;
13682                 if(*q == '*') { // remember default, which is marked with * prefix
13683                     q++;
13684                     opt->value = n;
13685                 }
13686                 cps->comboList[cps->comboCnt++] = q;
13687             }
13688             cps->comboList[cps->comboCnt++] = NULL;
13689             opt->max = n + 1;
13690             opt->type = ComboBox;
13691         } else if(p = strstr(opt->name, " -button")) {
13692             opt->type = Button;
13693         } else if(p = strstr(opt->name, " -save")) {
13694             opt->type = SaveButton;
13695         } else return FALSE;
13696         *p = 0; // terminate option name
13697         // now look if the command-line options define a setting for this engine option.
13698         if(cps->optionSettings && cps->optionSettings[0])
13699             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13700         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13701                 sprintf(buf, "option %s", p);
13702                 if(p = strstr(buf, ",")) *p = 0;
13703                 strcat(buf, "\n");
13704                 SendToProgram(buf, cps);
13705         }
13706         return TRUE;
13707 }
13708
13709 void
13710 FeatureDone(cps, val)
13711      ChessProgramState* cps;
13712      int val;
13713 {
13714   DelayedEventCallback cb = GetDelayedEvent();
13715   if ((cb == InitBackEnd3 && cps == &first) ||
13716       (cb == TwoMachinesEventIfReady && cps == &second)) {
13717     CancelDelayedEvent();
13718     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13719   }
13720   cps->initDone = val;
13721 }
13722
13723 /* Parse feature command from engine */
13724 void
13725 ParseFeatures(args, cps)
13726      char* args;
13727      ChessProgramState *cps;
13728 {
13729   char *p = args;
13730   char *q;
13731   int val;
13732   char buf[MSG_SIZ];
13733
13734   for (;;) {
13735     while (*p == ' ') p++;
13736     if (*p == NULLCHAR) return;
13737
13738     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13739     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13740     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13741     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13742     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13743     if (BoolFeature(&p, "reuse", &val, cps)) {
13744       /* Engine can disable reuse, but can't enable it if user said no */
13745       if (!val) cps->reuse = FALSE;
13746       continue;
13747     }
13748     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13749     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13750       if (gameMode == TwoMachinesPlay) {
13751         DisplayTwoMachinesTitle();
13752       } else {
13753         DisplayTitle("");
13754       }
13755       continue;
13756     }
13757     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13758     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13759     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13760     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13761     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13762     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13763     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13764     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13765     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13766     if (IntFeature(&p, "done", &val, cps)) {
13767       FeatureDone(cps, val);
13768       continue;
13769     }
13770     /* Added by Tord: */
13771     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13772     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13773     /* End of additions by Tord */
13774
13775     /* [HGM] added features: */
13776     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13777     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13778     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13779     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13780     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13781     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13782     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13783         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13784             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13785             SendToProgram(buf, cps);
13786             continue;
13787         }
13788         if(cps->nrOptions >= MAX_OPTIONS) {
13789             cps->nrOptions--;
13790             sprintf(buf, "%s engine has too many options\n", cps->which);
13791             DisplayError(buf, 0);
13792         }
13793         continue;
13794     }
13795     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13796     /* End of additions by HGM */
13797
13798     /* unknown feature: complain and skip */
13799     q = p;
13800     while (*q && *q != '=') q++;
13801     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13802     SendToProgram(buf, cps);
13803     p = q;
13804     if (*p == '=') {
13805       p++;
13806       if (*p == '\"') {
13807         p++;
13808         while (*p && *p != '\"') p++;
13809         if (*p == '\"') p++;
13810       } else {
13811         while (*p && *p != ' ') p++;
13812       }
13813     }
13814   }
13815
13816 }
13817
13818 void
13819 PeriodicUpdatesEvent(newState)
13820      int newState;
13821 {
13822     if (newState == appData.periodicUpdates)
13823       return;
13824
13825     appData.periodicUpdates=newState;
13826
13827     /* Display type changes, so update it now */
13828 //    DisplayAnalysis();
13829
13830     /* Get the ball rolling again... */
13831     if (newState) {
13832         AnalysisPeriodicEvent(1);
13833         StartAnalysisClock();
13834     }
13835 }
13836
13837 void
13838 PonderNextMoveEvent(newState)
13839      int newState;
13840 {
13841     if (newState == appData.ponderNextMove) return;
13842     if (gameMode == EditPosition) EditPositionDone(TRUE);
13843     if (newState) {
13844         SendToProgram("hard\n", &first);
13845         if (gameMode == TwoMachinesPlay) {
13846             SendToProgram("hard\n", &second);
13847         }
13848     } else {
13849         SendToProgram("easy\n", &first);
13850         thinkOutput[0] = NULLCHAR;
13851         if (gameMode == TwoMachinesPlay) {
13852             SendToProgram("easy\n", &second);
13853         }
13854     }
13855     appData.ponderNextMove = newState;
13856 }
13857
13858 void
13859 NewSettingEvent(option, command, value)
13860      char *command;
13861      int option, value;
13862 {
13863     char buf[MSG_SIZ];
13864
13865     if (gameMode == EditPosition) EditPositionDone(TRUE);
13866     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13867     SendToProgram(buf, &first);
13868     if (gameMode == TwoMachinesPlay) {
13869         SendToProgram(buf, &second);
13870     }
13871 }
13872
13873 void
13874 ShowThinkingEvent()
13875 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13876 {
13877     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13878     int newState = appData.showThinking
13879         // [HGM] thinking: other features now need thinking output as well
13880         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13881
13882     if (oldState == newState) return;
13883     oldState = newState;
13884     if (gameMode == EditPosition) EditPositionDone(TRUE);
13885     if (oldState) {
13886         SendToProgram("post\n", &first);
13887         if (gameMode == TwoMachinesPlay) {
13888             SendToProgram("post\n", &second);
13889         }
13890     } else {
13891         SendToProgram("nopost\n", &first);
13892         thinkOutput[0] = NULLCHAR;
13893         if (gameMode == TwoMachinesPlay) {
13894             SendToProgram("nopost\n", &second);
13895         }
13896     }
13897 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13898 }
13899
13900 void
13901 AskQuestionEvent(title, question, replyPrefix, which)
13902      char *title; char *question; char *replyPrefix; char *which;
13903 {
13904   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13905   if (pr == NoProc) return;
13906   AskQuestion(title, question, replyPrefix, pr);
13907 }
13908
13909 void
13910 DisplayMove(moveNumber)
13911      int moveNumber;
13912 {
13913     char message[MSG_SIZ];
13914     char res[MSG_SIZ];
13915     char cpThinkOutput[MSG_SIZ];
13916
13917     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13918
13919     if (moveNumber == forwardMostMove - 1 ||
13920         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13921
13922         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13923
13924         if (strchr(cpThinkOutput, '\n')) {
13925             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13926         }
13927     } else {
13928         *cpThinkOutput = NULLCHAR;
13929     }
13930
13931     /* [AS] Hide thinking from human user */
13932     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13933         *cpThinkOutput = NULLCHAR;
13934         if( thinkOutput[0] != NULLCHAR ) {
13935             int i;
13936
13937             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13938                 cpThinkOutput[i] = '.';
13939             }
13940             cpThinkOutput[i] = NULLCHAR;
13941             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13942         }
13943     }
13944
13945     if (moveNumber == forwardMostMove - 1 &&
13946         gameInfo.resultDetails != NULL) {
13947         if (gameInfo.resultDetails[0] == NULLCHAR) {
13948             sprintf(res, " %s", PGNResult(gameInfo.result));
13949         } else {
13950             sprintf(res, " {%s} %s",
13951                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13952         }
13953     } else {
13954         res[0] = NULLCHAR;
13955     }
13956
13957     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13958         DisplayMessage(res, cpThinkOutput);
13959     } else {
13960         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13961                 WhiteOnMove(moveNumber) ? " " : ".. ",
13962                 parseList[moveNumber], res);
13963         DisplayMessage(message, cpThinkOutput);
13964     }
13965 }
13966
13967 void
13968 DisplayComment(moveNumber, text)
13969      int moveNumber;
13970      char *text;
13971 {
13972     char title[MSG_SIZ];
13973     char buf[8000]; // comment can be long!
13974     int score, depth;
13975     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13976       strcpy(title, "Comment");
13977     } else {
13978       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13979               WhiteOnMove(moveNumber) ? " " : ".. ",
13980               parseList[moveNumber]);
13981     }
13982     // [HGM] PV info: display PV info together with (or as) comment
13983     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13984       if(text == NULL) text = "";                                           
13985       score = pvInfoList[moveNumber].score;
13986       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13987               depth, (pvInfoList[moveNumber].time+50)/100, text);
13988       text = buf;
13989     }
13990     if (text != NULL && (appData.autoDisplayComment || commentUp))
13991       CommentPopUp(title, text);
13992 }
13993
13994 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13995  * might be busy thinking or pondering.  It can be omitted if your
13996  * gnuchess is configured to stop thinking immediately on any user
13997  * input.  However, that gnuchess feature depends on the FIONREAD
13998  * ioctl, which does not work properly on some flavors of Unix.
13999  */
14000 void
14001 Attention(cps)
14002      ChessProgramState *cps;
14003 {
14004 #if ATTENTION
14005     if (!cps->useSigint) return;
14006     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14007     switch (gameMode) {
14008       case MachinePlaysWhite:
14009       case MachinePlaysBlack:
14010       case TwoMachinesPlay:
14011       case IcsPlayingWhite:
14012       case IcsPlayingBlack:
14013       case AnalyzeMode:
14014       case AnalyzeFile:
14015         /* Skip if we know it isn't thinking */
14016         if (!cps->maybeThinking) return;
14017         if (appData.debugMode)
14018           fprintf(debugFP, "Interrupting %s\n", cps->which);
14019         InterruptChildProcess(cps->pr);
14020         cps->maybeThinking = FALSE;
14021         break;
14022       default:
14023         break;
14024     }
14025 #endif /*ATTENTION*/
14026 }
14027
14028 int
14029 CheckFlags()
14030 {
14031     if (whiteTimeRemaining <= 0) {
14032         if (!whiteFlag) {
14033             whiteFlag = TRUE;
14034             if (appData.icsActive) {
14035                 if (appData.autoCallFlag &&
14036                     gameMode == IcsPlayingBlack && !blackFlag) {
14037                   SendToICS(ics_prefix);
14038                   SendToICS("flag\n");
14039                 }
14040             } else {
14041                 if (blackFlag) {
14042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14043                 } else {
14044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14045                     if (appData.autoCallFlag) {
14046                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14047                         return TRUE;
14048                     }
14049                 }
14050             }
14051         }
14052     }
14053     if (blackTimeRemaining <= 0) {
14054         if (!blackFlag) {
14055             blackFlag = TRUE;
14056             if (appData.icsActive) {
14057                 if (appData.autoCallFlag &&
14058                     gameMode == IcsPlayingWhite && !whiteFlag) {
14059                   SendToICS(ics_prefix);
14060                   SendToICS("flag\n");
14061                 }
14062             } else {
14063                 if (whiteFlag) {
14064                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14065                 } else {
14066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14067                     if (appData.autoCallFlag) {
14068                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14069                         return TRUE;
14070                     }
14071                 }
14072             }
14073         }
14074     }
14075     return FALSE;
14076 }
14077
14078 void
14079 CheckTimeControl()
14080 {
14081     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14082         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14083
14084     /*
14085      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14086      */
14087     if ( !WhiteOnMove(forwardMostMove) )
14088         /* White made time control */
14089         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14090         /* [HGM] time odds: correct new time quota for time odds! */
14091                                             / WhitePlayer()->timeOdds;
14092       else
14093         /* Black made time control */
14094         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14095                                             / WhitePlayer()->other->timeOdds;
14096 }
14097
14098 void
14099 DisplayBothClocks()
14100 {
14101     int wom = gameMode == EditPosition ?
14102       !blackPlaysFirst : WhiteOnMove(currentMove);
14103     DisplayWhiteClock(whiteTimeRemaining, wom);
14104     DisplayBlackClock(blackTimeRemaining, !wom);
14105 }
14106
14107
14108 /* Timekeeping seems to be a portability nightmare.  I think everyone
14109    has ftime(), but I'm really not sure, so I'm including some ifdefs
14110    to use other calls if you don't.  Clocks will be less accurate if
14111    you have neither ftime nor gettimeofday.
14112 */
14113
14114 /* VS 2008 requires the #include outside of the function */
14115 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14116 #include <sys/timeb.h>
14117 #endif
14118
14119 /* Get the current time as a TimeMark */
14120 void
14121 GetTimeMark(tm)
14122      TimeMark *tm;
14123 {
14124 #if HAVE_GETTIMEOFDAY
14125
14126     struct timeval timeVal;
14127     struct timezone timeZone;
14128
14129     gettimeofday(&timeVal, &timeZone);
14130     tm->sec = (long) timeVal.tv_sec;
14131     tm->ms = (int) (timeVal.tv_usec / 1000L);
14132
14133 #else /*!HAVE_GETTIMEOFDAY*/
14134 #if HAVE_FTIME
14135
14136 // include <sys/timeb.h> / moved to just above start of function
14137     struct timeb timeB;
14138
14139     ftime(&timeB);
14140     tm->sec = (long) timeB.time;
14141     tm->ms = (int) timeB.millitm;
14142
14143 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14144     tm->sec = (long) time(NULL);
14145     tm->ms = 0;
14146 #endif
14147 #endif
14148 }
14149
14150 /* Return the difference in milliseconds between two
14151    time marks.  We assume the difference will fit in a long!
14152 */
14153 long
14154 SubtractTimeMarks(tm2, tm1)
14155      TimeMark *tm2, *tm1;
14156 {
14157     return 1000L*(tm2->sec - tm1->sec) +
14158            (long) (tm2->ms - tm1->ms);
14159 }
14160
14161
14162 /*
14163  * Code to manage the game clocks.
14164  *
14165  * In tournament play, black starts the clock and then white makes a move.
14166  * We give the human user a slight advantage if he is playing white---the
14167  * clocks don't run until he makes his first move, so it takes zero time.
14168  * Also, we don't account for network lag, so we could get out of sync
14169  * with GNU Chess's clock -- but then, referees are always right.
14170  */
14171
14172 static TimeMark tickStartTM;
14173 static long intendedTickLength;
14174
14175 long
14176 NextTickLength(timeRemaining)
14177      long timeRemaining;
14178 {
14179     long nominalTickLength, nextTickLength;
14180
14181     if (timeRemaining > 0L && timeRemaining <= 10000L)
14182       nominalTickLength = 100L;
14183     else
14184       nominalTickLength = 1000L;
14185     nextTickLength = timeRemaining % nominalTickLength;
14186     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14187
14188     return nextTickLength;
14189 }
14190
14191 /* Adjust clock one minute up or down */
14192 void
14193 AdjustClock(Boolean which, int dir)
14194 {
14195     if(which) blackTimeRemaining += 60000*dir;
14196     else      whiteTimeRemaining += 60000*dir;
14197     DisplayBothClocks();
14198 }
14199
14200 /* Stop clocks and reset to a fresh time control */
14201 void
14202 ResetClocks()
14203 {
14204     (void) StopClockTimer();
14205     if (appData.icsActive) {
14206         whiteTimeRemaining = blackTimeRemaining = 0;
14207     } else if (searchTime) {
14208         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14209         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14210     } else { /* [HGM] correct new time quote for time odds */
14211         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14212         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14213     }
14214     if (whiteFlag || blackFlag) {
14215         DisplayTitle("");
14216         whiteFlag = blackFlag = FALSE;
14217     }
14218     DisplayBothClocks();
14219 }
14220
14221 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14222
14223 /* Decrement running clock by amount of time that has passed */
14224 void
14225 DecrementClocks()
14226 {
14227     long timeRemaining;
14228     long lastTickLength, fudge;
14229     TimeMark now;
14230
14231     if (!appData.clockMode) return;
14232     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14233
14234     GetTimeMark(&now);
14235
14236     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14237
14238     /* Fudge if we woke up a little too soon */
14239     fudge = intendedTickLength - lastTickLength;
14240     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14241
14242     if (WhiteOnMove(forwardMostMove)) {
14243         if(whiteNPS >= 0) lastTickLength = 0;
14244         timeRemaining = whiteTimeRemaining -= lastTickLength;
14245         DisplayWhiteClock(whiteTimeRemaining - fudge,
14246                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14247     } else {
14248         if(blackNPS >= 0) lastTickLength = 0;
14249         timeRemaining = blackTimeRemaining -= lastTickLength;
14250         DisplayBlackClock(blackTimeRemaining - fudge,
14251                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14252     }
14253
14254     if (CheckFlags()) return;
14255
14256     tickStartTM = now;
14257     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14258     StartClockTimer(intendedTickLength);
14259
14260     /* if the time remaining has fallen below the alarm threshold, sound the
14261      * alarm. if the alarm has sounded and (due to a takeback or time control
14262      * with increment) the time remaining has increased to a level above the
14263      * threshold, reset the alarm so it can sound again.
14264      */
14265
14266     if (appData.icsActive && appData.icsAlarm) {
14267
14268         /* make sure we are dealing with the user's clock */
14269         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14270                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14271            )) return;
14272
14273         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14274             alarmSounded = FALSE;
14275         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14276             PlayAlarmSound();
14277             alarmSounded = TRUE;
14278         }
14279     }
14280 }
14281
14282
14283 /* A player has just moved, so stop the previously running
14284    clock and (if in clock mode) start the other one.
14285    We redisplay both clocks in case we're in ICS mode, because
14286    ICS gives us an update to both clocks after every move.
14287    Note that this routine is called *after* forwardMostMove
14288    is updated, so the last fractional tick must be subtracted
14289    from the color that is *not* on move now.
14290 */
14291 void
14292 SwitchClocks()
14293 {
14294     long lastTickLength;
14295     TimeMark now;
14296     int flagged = FALSE;
14297
14298     GetTimeMark(&now);
14299
14300     if (StopClockTimer() && appData.clockMode) {
14301         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14302         if (WhiteOnMove(forwardMostMove)) {
14303             if(blackNPS >= 0) lastTickLength = 0;
14304             blackTimeRemaining -= lastTickLength;
14305            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14306 //         if(pvInfoList[forwardMostMove-1].time == -1)
14307                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14308                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14309         } else {
14310            if(whiteNPS >= 0) lastTickLength = 0;
14311            whiteTimeRemaining -= lastTickLength;
14312            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14313 //         if(pvInfoList[forwardMostMove-1].time == -1)
14314                  pvInfoList[forwardMostMove-1].time =
14315                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14316         }
14317         flagged = CheckFlags();
14318     }
14319     CheckTimeControl();
14320
14321     if (flagged || !appData.clockMode) return;
14322
14323     switch (gameMode) {
14324       case MachinePlaysBlack:
14325       case MachinePlaysWhite:
14326       case BeginningOfGame:
14327         if (pausing) return;
14328         break;
14329
14330       case EditGame:
14331       case PlayFromGameFile:
14332       case IcsExamining:
14333         return;
14334
14335       default:
14336         break;
14337     }
14338
14339     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14340         if(WhiteOnMove(forwardMostMove))
14341              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14342         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14343     }
14344
14345     tickStartTM = now;
14346     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14347       whiteTimeRemaining : blackTimeRemaining);
14348     StartClockTimer(intendedTickLength);
14349 }
14350
14351
14352 /* Stop both clocks */
14353 void
14354 StopClocks()
14355 {
14356     long lastTickLength;
14357     TimeMark now;
14358
14359     if (!StopClockTimer()) return;
14360     if (!appData.clockMode) return;
14361
14362     GetTimeMark(&now);
14363
14364     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14365     if (WhiteOnMove(forwardMostMove)) {
14366         if(whiteNPS >= 0) lastTickLength = 0;
14367         whiteTimeRemaining -= lastTickLength;
14368         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14369     } else {
14370         if(blackNPS >= 0) lastTickLength = 0;
14371         blackTimeRemaining -= lastTickLength;
14372         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14373     }
14374     CheckFlags();
14375 }
14376
14377 /* Start clock of player on move.  Time may have been reset, so
14378    if clock is already running, stop and restart it. */
14379 void
14380 StartClocks()
14381 {
14382     (void) StopClockTimer(); /* in case it was running already */
14383     DisplayBothClocks();
14384     if (CheckFlags()) return;
14385
14386     if (!appData.clockMode) return;
14387     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14388
14389     GetTimeMark(&tickStartTM);
14390     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14391       whiteTimeRemaining : blackTimeRemaining);
14392
14393    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14394     whiteNPS = blackNPS = -1;
14395     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14396        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14397         whiteNPS = first.nps;
14398     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14399        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14400         blackNPS = first.nps;
14401     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14402         whiteNPS = second.nps;
14403     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14404         blackNPS = second.nps;
14405     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14406
14407     StartClockTimer(intendedTickLength);
14408 }
14409
14410 char *
14411 TimeString(ms)
14412      long ms;
14413 {
14414     long second, minute, hour, day;
14415     char *sign = "";
14416     static char buf[32];
14417
14418     if (ms > 0 && ms <= 9900) {
14419       /* convert milliseconds to tenths, rounding up */
14420       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14421
14422       sprintf(buf, " %03.1f ", tenths/10.0);
14423       return buf;
14424     }
14425
14426     /* convert milliseconds to seconds, rounding up */
14427     /* use floating point to avoid strangeness of integer division
14428        with negative dividends on many machines */
14429     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14430
14431     if (second < 0) {
14432         sign = "-";
14433         second = -second;
14434     }
14435
14436     day = second / (60 * 60 * 24);
14437     second = second % (60 * 60 * 24);
14438     hour = second / (60 * 60);
14439     second = second % (60 * 60);
14440     minute = second / 60;
14441     second = second % 60;
14442
14443     if (day > 0)
14444       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14445               sign, day, hour, minute, second);
14446     else if (hour > 0)
14447       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14448     else
14449       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14450
14451     return buf;
14452 }
14453
14454
14455 /*
14456  * This is necessary because some C libraries aren't ANSI C compliant yet.
14457  */
14458 char *
14459 StrStr(string, match)
14460      char *string, *match;
14461 {
14462     int i, length;
14463
14464     length = strlen(match);
14465
14466     for (i = strlen(string) - length; i >= 0; i--, string++)
14467       if (!strncmp(match, string, length))
14468         return string;
14469
14470     return NULL;
14471 }
14472
14473 char *
14474 StrCaseStr(string, match)
14475      char *string, *match;
14476 {
14477     int i, j, length;
14478
14479     length = strlen(match);
14480
14481     for (i = strlen(string) - length; i >= 0; i--, string++) {
14482         for (j = 0; j < length; j++) {
14483             if (ToLower(match[j]) != ToLower(string[j]))
14484               break;
14485         }
14486         if (j == length) return string;
14487     }
14488
14489     return NULL;
14490 }
14491
14492 #ifndef _amigados
14493 int
14494 StrCaseCmp(s1, s2)
14495      char *s1, *s2;
14496 {
14497     char c1, c2;
14498
14499     for (;;) {
14500         c1 = ToLower(*s1++);
14501         c2 = ToLower(*s2++);
14502         if (c1 > c2) return 1;
14503         if (c1 < c2) return -1;
14504         if (c1 == NULLCHAR) return 0;
14505     }
14506 }
14507
14508
14509 int
14510 ToLower(c)
14511      int c;
14512 {
14513     return isupper(c) ? tolower(c) : c;
14514 }
14515
14516
14517 int
14518 ToUpper(c)
14519      int c;
14520 {
14521     return islower(c) ? toupper(c) : c;
14522 }
14523 #endif /* !_amigados    */
14524
14525 char *
14526 StrSave(s)
14527      char *s;
14528 {
14529     char *ret;
14530
14531     if ((ret = (char *) malloc(strlen(s) + 1))) {
14532         strcpy(ret, s);
14533     }
14534     return ret;
14535 }
14536
14537 char *
14538 StrSavePtr(s, savePtr)
14539      char *s, **savePtr;
14540 {
14541     if (*savePtr) {
14542         free(*savePtr);
14543     }
14544     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14545         strcpy(*savePtr, s);
14546     }
14547     return(*savePtr);
14548 }
14549
14550 char *
14551 PGNDate()
14552 {
14553     time_t clock;
14554     struct tm *tm;
14555     char buf[MSG_SIZ];
14556
14557     clock = time((time_t *)NULL);
14558     tm = localtime(&clock);
14559     sprintf(buf, "%04d.%02d.%02d",
14560             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14561     return StrSave(buf);
14562 }
14563
14564
14565 char *
14566 PositionToFEN(move, overrideCastling)
14567      int move;
14568      char *overrideCastling;
14569 {
14570     int i, j, fromX, fromY, toX, toY;
14571     int whiteToPlay;
14572     char buf[128];
14573     char *p, *q;
14574     int emptycount;
14575     ChessSquare piece;
14576
14577     whiteToPlay = (gameMode == EditPosition) ?
14578       !blackPlaysFirst : (move % 2 == 0);
14579     p = buf;
14580
14581     /* Piece placement data */
14582     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14583         emptycount = 0;
14584         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14585             if (boards[move][i][j] == EmptySquare) {
14586                 emptycount++;
14587             } else { ChessSquare piece = boards[move][i][j];
14588                 if (emptycount > 0) {
14589                     if(emptycount<10) /* [HGM] can be >= 10 */
14590                         *p++ = '0' + emptycount;
14591                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14592                     emptycount = 0;
14593                 }
14594                 if(PieceToChar(piece) == '+') {
14595                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14596                     *p++ = '+';
14597                     piece = (ChessSquare)(DEMOTED piece);
14598                 }
14599                 *p++ = PieceToChar(piece);
14600                 if(p[-1] == '~') {
14601                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14602                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14603                     *p++ = '~';
14604                 }
14605             }
14606         }
14607         if (emptycount > 0) {
14608             if(emptycount<10) /* [HGM] can be >= 10 */
14609                 *p++ = '0' + emptycount;
14610             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14611             emptycount = 0;
14612         }
14613         *p++ = '/';
14614     }
14615     *(p - 1) = ' ';
14616
14617     /* [HGM] print Crazyhouse or Shogi holdings */
14618     if( gameInfo.holdingsWidth ) {
14619         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14620         q = p;
14621         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14622             piece = boards[move][i][BOARD_WIDTH-1];
14623             if( piece != EmptySquare )
14624               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14625                   *p++ = PieceToChar(piece);
14626         }
14627         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14628             piece = boards[move][BOARD_HEIGHT-i-1][0];
14629             if( piece != EmptySquare )
14630               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14631                   *p++ = PieceToChar(piece);
14632         }
14633
14634         if( q == p ) *p++ = '-';
14635         *p++ = ']';
14636         *p++ = ' ';
14637     }
14638
14639     /* Active color */
14640     *p++ = whiteToPlay ? 'w' : 'b';
14641     *p++ = ' ';
14642
14643   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14644     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14645   } else {
14646   if(nrCastlingRights) {
14647      q = p;
14648      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14649        /* [HGM] write directly from rights */
14650            if(boards[move][CASTLING][2] != NoRights &&
14651               boards[move][CASTLING][0] != NoRights   )
14652                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14653            if(boards[move][CASTLING][2] != NoRights &&
14654               boards[move][CASTLING][1] != NoRights   )
14655                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14656            if(boards[move][CASTLING][5] != NoRights &&
14657               boards[move][CASTLING][3] != NoRights   )
14658                 *p++ = boards[move][CASTLING][3] + AAA;
14659            if(boards[move][CASTLING][5] != NoRights &&
14660               boards[move][CASTLING][4] != NoRights   )
14661                 *p++ = boards[move][CASTLING][4] + AAA;
14662      } else {
14663
14664         /* [HGM] write true castling rights */
14665         if( nrCastlingRights == 6 ) {
14666             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14667                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14668             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14669                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14670             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14671                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14672             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14673                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14674         }
14675      }
14676      if (q == p) *p++ = '-'; /* No castling rights */
14677      *p++ = ' ';
14678   }
14679
14680   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14681      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14682     /* En passant target square */
14683     if (move > backwardMostMove) {
14684         fromX = moveList[move - 1][0] - AAA;
14685         fromY = moveList[move - 1][1] - ONE;
14686         toX = moveList[move - 1][2] - AAA;
14687         toY = moveList[move - 1][3] - ONE;
14688         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14689             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14690             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14691             fromX == toX) {
14692             /* 2-square pawn move just happened */
14693             *p++ = toX + AAA;
14694             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14695         } else {
14696             *p++ = '-';
14697         }
14698     } else if(move == backwardMostMove) {
14699         // [HGM] perhaps we should always do it like this, and forget the above?
14700         if((signed char)boards[move][EP_STATUS] >= 0) {
14701             *p++ = boards[move][EP_STATUS] + AAA;
14702             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14703         } else {
14704             *p++ = '-';
14705         }
14706     } else {
14707         *p++ = '-';
14708     }
14709     *p++ = ' ';
14710   }
14711   }
14712
14713     /* [HGM] find reversible plies */
14714     {   int i = 0, j=move;
14715
14716         if (appData.debugMode) { int k;
14717             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14718             for(k=backwardMostMove; k<=forwardMostMove; k++)
14719                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14720
14721         }
14722
14723         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14724         if( j == backwardMostMove ) i += initialRulePlies;
14725         sprintf(p, "%d ", i);
14726         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14727     }
14728     /* Fullmove number */
14729     sprintf(p, "%d", (move / 2) + 1);
14730
14731     return StrSave(buf);
14732 }
14733
14734 Boolean
14735 ParseFEN(board, blackPlaysFirst, fen)
14736     Board board;
14737      int *blackPlaysFirst;
14738      char *fen;
14739 {
14740     int i, j;
14741     char *p;
14742     int emptycount;
14743     ChessSquare piece;
14744
14745     p = fen;
14746
14747     /* [HGM] by default clear Crazyhouse holdings, if present */
14748     if(gameInfo.holdingsWidth) {
14749        for(i=0; i<BOARD_HEIGHT; i++) {
14750            board[i][0]             = EmptySquare; /* black holdings */
14751            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14752            board[i][1]             = (ChessSquare) 0; /* black counts */
14753            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14754        }
14755     }
14756
14757     /* Piece placement data */
14758     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14759         j = 0;
14760         for (;;) {
14761             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14762                 if (*p == '/') p++;
14763                 emptycount = gameInfo.boardWidth - j;
14764                 while (emptycount--)
14765                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14766                 break;
14767 #if(BOARD_FILES >= 10)
14768             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14769                 p++; emptycount=10;
14770                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14771                 while (emptycount--)
14772                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14773 #endif
14774             } else if (isdigit(*p)) {
14775                 emptycount = *p++ - '0';
14776                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14777                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14778                 while (emptycount--)
14779                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14780             } else if (*p == '+' || isalpha(*p)) {
14781                 if (j >= gameInfo.boardWidth) return FALSE;
14782                 if(*p=='+') {
14783                     piece = CharToPiece(*++p);
14784                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14785                     piece = (ChessSquare) (PROMOTED piece ); p++;
14786                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14787                 } else piece = CharToPiece(*p++);
14788
14789                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14790                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14791                     piece = (ChessSquare) (PROMOTED piece);
14792                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14793                     p++;
14794                 }
14795                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14796             } else {
14797                 return FALSE;
14798             }
14799         }
14800     }
14801     while (*p == '/' || *p == ' ') p++;
14802
14803     /* [HGM] look for Crazyhouse holdings here */
14804     while(*p==' ') p++;
14805     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14806         if(*p == '[') p++;
14807         if(*p == '-' ) *p++; /* empty holdings */ else {
14808             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14809             /* if we would allow FEN reading to set board size, we would   */
14810             /* have to add holdings and shift the board read so far here   */
14811             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14812                 *p++;
14813                 if((int) piece >= (int) BlackPawn ) {
14814                     i = (int)piece - (int)BlackPawn;
14815                     i = PieceToNumber((ChessSquare)i);
14816                     if( i >= gameInfo.holdingsSize ) return FALSE;
14817                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14818                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14819                 } else {
14820                     i = (int)piece - (int)WhitePawn;
14821                     i = PieceToNumber((ChessSquare)i);
14822                     if( i >= gameInfo.holdingsSize ) return FALSE;
14823                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14824                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14825                 }
14826             }
14827         }
14828         if(*p == ']') *p++;
14829     }
14830
14831     while(*p == ' ') p++;
14832
14833     /* Active color */
14834     switch (*p++) {
14835       case 'w':
14836         *blackPlaysFirst = FALSE;
14837         break;
14838       case 'b':
14839         *blackPlaysFirst = TRUE;
14840         break;
14841       default:
14842         return FALSE;
14843     }
14844
14845     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14846     /* return the extra info in global variiables             */
14847
14848     /* set defaults in case FEN is incomplete */
14849     board[EP_STATUS] = EP_UNKNOWN;
14850     for(i=0; i<nrCastlingRights; i++ ) {
14851         board[CASTLING][i] =
14852             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14853     }   /* assume possible unless obviously impossible */
14854     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14855     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14856     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14857                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14858     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14859     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14860     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14861                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14862     FENrulePlies = 0;
14863
14864     while(*p==' ') p++;
14865     if(nrCastlingRights) {
14866       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14867           /* castling indicator present, so default becomes no castlings */
14868           for(i=0; i<nrCastlingRights; i++ ) {
14869                  board[CASTLING][i] = NoRights;
14870           }
14871       }
14872       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14873              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14874              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14875              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14876         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14877
14878         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14879             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14880             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14881         }
14882         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14883             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14884         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14885                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14886         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14887                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14888         switch(c) {
14889           case'K':
14890               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14891               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14892               board[CASTLING][2] = whiteKingFile;
14893               break;
14894           case'Q':
14895               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14896               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14897               board[CASTLING][2] = whiteKingFile;
14898               break;
14899           case'k':
14900               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14901               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14902               board[CASTLING][5] = blackKingFile;
14903               break;
14904           case'q':
14905               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14906               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14907               board[CASTLING][5] = blackKingFile;
14908           case '-':
14909               break;
14910           default: /* FRC castlings */
14911               if(c >= 'a') { /* black rights */
14912                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14913                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14914                   if(i == BOARD_RGHT) break;
14915                   board[CASTLING][5] = i;
14916                   c -= AAA;
14917                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14918                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14919                   if(c > i)
14920                       board[CASTLING][3] = c;
14921                   else
14922                       board[CASTLING][4] = c;
14923               } else { /* white rights */
14924                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14925                     if(board[0][i] == WhiteKing) break;
14926                   if(i == BOARD_RGHT) break;
14927                   board[CASTLING][2] = i;
14928                   c -= AAA - 'a' + 'A';
14929                   if(board[0][c] >= WhiteKing) break;
14930                   if(c > i)
14931                       board[CASTLING][0] = c;
14932                   else
14933                       board[CASTLING][1] = c;
14934               }
14935         }
14936       }
14937       for(i=0; i<nrCastlingRights; i++)
14938         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14939     if (appData.debugMode) {
14940         fprintf(debugFP, "FEN castling rights:");
14941         for(i=0; i<nrCastlingRights; i++)
14942         fprintf(debugFP, " %d", board[CASTLING][i]);
14943         fprintf(debugFP, "\n");
14944     }
14945
14946       while(*p==' ') p++;
14947     }
14948
14949     /* read e.p. field in games that know e.p. capture */
14950     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14951        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14952       if(*p=='-') {
14953         p++; board[EP_STATUS] = EP_NONE;
14954       } else {
14955          char c = *p++ - AAA;
14956
14957          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14958          if(*p >= '0' && *p <='9') *p++;
14959          board[EP_STATUS] = c;
14960       }
14961     }
14962
14963
14964     if(sscanf(p, "%d", &i) == 1) {
14965         FENrulePlies = i; /* 50-move ply counter */
14966         /* (The move number is still ignored)    */
14967     }
14968
14969     return TRUE;
14970 }
14971
14972 void
14973 EditPositionPasteFEN(char *fen)
14974 {
14975   if (fen != NULL) {
14976     Board initial_position;
14977
14978     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14979       DisplayError(_("Bad FEN position in clipboard"), 0);
14980       return ;
14981     } else {
14982       int savedBlackPlaysFirst = blackPlaysFirst;
14983       EditPositionEvent();
14984       blackPlaysFirst = savedBlackPlaysFirst;
14985       CopyBoard(boards[0], initial_position);
14986       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14987       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14988       DisplayBothClocks();
14989       DrawPosition(FALSE, boards[currentMove]);
14990     }
14991   }
14992 }
14993
14994 static char cseq[12] = "\\   ";
14995
14996 Boolean set_cont_sequence(char *new_seq)
14997 {
14998     int len;
14999     Boolean ret;
15000
15001     // handle bad attempts to set the sequence
15002         if (!new_seq)
15003                 return 0; // acceptable error - no debug
15004
15005     len = strlen(new_seq);
15006     ret = (len > 0) && (len < sizeof(cseq));
15007     if (ret)
15008         strcpy(cseq, new_seq);
15009     else if (appData.debugMode)
15010         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15011     return ret;
15012 }
15013
15014 /*
15015     reformat a source message so words don't cross the width boundary.  internal
15016     newlines are not removed.  returns the wrapped size (no null character unless
15017     included in source message).  If dest is NULL, only calculate the size required
15018     for the dest buffer.  lp argument indicats line position upon entry, and it's
15019     passed back upon exit.
15020 */
15021 int wrap(char *dest, char *src, int count, int width, int *lp)
15022 {
15023     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15024
15025     cseq_len = strlen(cseq);
15026     old_line = line = *lp;
15027     ansi = len = clen = 0;
15028
15029     for (i=0; i < count; i++)
15030     {
15031         if (src[i] == '\033')
15032             ansi = 1;
15033
15034         // if we hit the width, back up
15035         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15036         {
15037             // store i & len in case the word is too long
15038             old_i = i, old_len = len;
15039
15040             // find the end of the last word
15041             while (i && src[i] != ' ' && src[i] != '\n')
15042             {
15043                 i--;
15044                 len--;
15045             }
15046
15047             // word too long?  restore i & len before splitting it
15048             if ((old_i-i+clen) >= width)
15049             {
15050                 i = old_i;
15051                 len = old_len;
15052             }
15053
15054             // extra space?
15055             if (i && src[i-1] == ' ')
15056                 len--;
15057
15058             if (src[i] != ' ' && src[i] != '\n')
15059             {
15060                 i--;
15061                 if (len)
15062                     len--;
15063             }
15064
15065             // now append the newline and continuation sequence
15066             if (dest)
15067                 dest[len] = '\n';
15068             len++;
15069             if (dest)
15070                 strncpy(dest+len, cseq, cseq_len);
15071             len += cseq_len;
15072             line = cseq_len;
15073             clen = cseq_len;
15074             continue;
15075         }
15076
15077         if (dest)
15078             dest[len] = src[i];
15079         len++;
15080         if (!ansi)
15081             line++;
15082         if (src[i] == '\n')
15083             line = 0;
15084         if (src[i] == 'm')
15085             ansi = 0;
15086     }
15087     if (dest && appData.debugMode)
15088     {
15089         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15090             count, width, line, len, *lp);
15091         show_bytes(debugFP, src, count);
15092         fprintf(debugFP, "\ndest: ");
15093         show_bytes(debugFP, dest, len);
15094         fprintf(debugFP, "\n");
15095     }
15096     *lp = dest ? line : old_line;
15097
15098     return len;
15099 }
15100
15101 // [HGM] vari: routines for shelving variations
15102
15103 void 
15104 PushTail(int firstMove, int lastMove)
15105 {
15106         int i, j, nrMoves = lastMove - firstMove;
15107
15108         if(appData.icsActive) { // only in local mode
15109                 forwardMostMove = currentMove; // mimic old ICS behavior
15110                 return;
15111         }
15112         if(storedGames >= MAX_VARIATIONS-1) return;
15113
15114         // push current tail of game on stack
15115         savedResult[storedGames] = gameInfo.result;
15116         savedDetails[storedGames] = gameInfo.resultDetails;
15117         gameInfo.resultDetails = NULL;
15118         savedFirst[storedGames] = firstMove;
15119         savedLast [storedGames] = lastMove;
15120         savedFramePtr[storedGames] = framePtr;
15121         framePtr -= nrMoves; // reserve space for the boards
15122         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15123             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15124             for(j=0; j<MOVE_LEN; j++)
15125                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15126             for(j=0; j<2*MOVE_LEN; j++)
15127                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15128             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15129             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15130             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15131             pvInfoList[firstMove+i-1].depth = 0;
15132             commentList[framePtr+i] = commentList[firstMove+i];
15133             commentList[firstMove+i] = NULL;
15134         }
15135
15136         storedGames++;
15137         forwardMostMove = currentMove; // truncte game so we can start variation
15138         if(storedGames == 1) GreyRevert(FALSE);
15139 }
15140
15141 Boolean
15142 PopTail(Boolean annotate)
15143 {
15144         int i, j, nrMoves;
15145         char buf[8000], moveBuf[20];
15146
15147         if(appData.icsActive) return FALSE; // only in local mode
15148         if(!storedGames) return FALSE; // sanity
15149
15150         storedGames--;
15151         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15152         nrMoves = savedLast[storedGames] - currentMove;
15153         if(annotate) {
15154                 int cnt = 10;
15155                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15156                 else strcpy(buf, "(");
15157                 for(i=currentMove; i<forwardMostMove; i++) {
15158                         if(WhiteOnMove(i))
15159                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15160                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15161                         strcat(buf, moveBuf);
15162                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15163                 }
15164                 strcat(buf, ")");
15165         }
15166         for(i=1; i<nrMoves; i++) { // copy last variation back
15167             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15168             for(j=0; j<MOVE_LEN; j++)
15169                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15170             for(j=0; j<2*MOVE_LEN; j++)
15171                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15172             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15173             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15174             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15175             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15176             commentList[currentMove+i] = commentList[framePtr+i];
15177             commentList[framePtr+i] = NULL;
15178         }
15179         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15180         framePtr = savedFramePtr[storedGames];
15181         gameInfo.result = savedResult[storedGames];
15182         if(gameInfo.resultDetails != NULL) {
15183             free(gameInfo.resultDetails);
15184       }
15185         gameInfo.resultDetails = savedDetails[storedGames];
15186         forwardMostMove = currentMove + nrMoves;
15187         if(storedGames == 0) GreyRevert(TRUE);
15188         return TRUE;
15189 }
15190
15191 void 
15192 CleanupTail()
15193 {       // remove all shelved variations
15194         int i;
15195         for(i=0; i<storedGames; i++) {
15196             if(savedDetails[i])
15197                 free(savedDetails[i]);
15198             savedDetails[i] = NULL;
15199         }
15200         for(i=framePtr; i<MAX_MOVES; i++) {
15201                 if(commentList[i]) free(commentList[i]);
15202                 commentList[i] = NULL;
15203         }
15204         framePtr = MAX_MOVES-1;
15205         storedGames = 0;
15206 }