1a94cf7a441d1232dfc0a11f46f12938c2f9a85d
[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 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom:
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /*
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0;
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000;
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068   if (appData.debugMode) {
1069     fprintf(debugFP, "%s\n", programVersion);
1070   }
1071
1072   set_cont_sequence(appData.wrapContSeq);
1073   if (appData.matchGames > 0) {
1074     appData.matchMode = TRUE;
1075   } else if (appData.matchMode) {
1076     appData.matchGames = 1;
1077   }
1078   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079     appData.matchGames = appData.sameColorGames;
1080   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083   }
1084   Reset(TRUE, FALSE);
1085   if (appData.noChessProgram || first.protocolVersion == 1) {
1086     InitBackEnd3();
1087     } else {
1088     /* kludge: allow timeout for initial "feature" commands */
1089     FreezeUI();
1090     DisplayMessage("", _("Starting chess program"));
1091     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092   }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098   GameMode initialMode;
1099   char buf[MSG_SIZ];
1100   int err;
1101   
1102   InitChessProgram(&first, startedFromSetupPosition);
1103   
1104   
1105   if (appData.icsActive) 
1106     {
1107       err = establish();
1108       if (err != 0) 
1109         {
1110           if (*appData.icsCommPort != NULLCHAR) 
1111             {
1112               sprintf(buf, _("Could not open comm port %s"),
1113                       appData.icsCommPort);
1114             }
1115           else 
1116             {
1117               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118                        appData.icsHost, appData.icsPort);
1119             }
1120           DisplayFatalError(buf, err, 1);
1121           return;
1122         }
1123
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     }
1132   else if (appData.noChessProgram) 
1133     {
1134       SetNCPMode();
1135     } 
1136   else 
1137     {
1138       SetGNUMode();
1139     }
1140   
1141   if (*appData.cmailGameName != NULLCHAR) 
1142     {
1143       SetCmailMode();
1144       OpenLoopback(&cmailPR);
1145       cmailISR =
1146         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1147     }
1148   
1149   ThawUI();
1150   DisplayMessage("", "");
1151   if (StrCaseCmp(appData.initialMode, "") == 0) 
1152     {
1153       initialMode = BeginningOfGame;
1154     } 
1155   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1156     {
1157       initialMode = TwoMachinesPlay;
1158     } 
1159   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1160     {
1161       initialMode = AnalyzeFile;
1162     } 
1163   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1164     {
1165       initialMode = AnalyzeMode;
1166     } 
1167   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1168     {
1169       initialMode = MachinePlaysWhite;
1170     } 
1171   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1172     {
1173       initialMode = MachinePlaysBlack;
1174     } 
1175   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1176     {
1177       initialMode = EditGame;
1178     } 
1179   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1180     {
1181       initialMode = EditPosition;
1182     } 
1183   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1184     {
1185       initialMode = Training;
1186     } 
1187   else 
1188     {
1189       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190       DisplayFatalError(buf, 0, 2);
1191       return;
1192     }
1193   
1194   if (appData.matchMode) 
1195     {
1196       /* Set up machine vs. machine match */
1197       if (appData.noChessProgram) 
1198         {
1199           DisplayFatalError(_("Can't have a match with no chess programs"),
1200                             0, 2);
1201           return;
1202         }
1203       matchMode = TRUE;
1204       matchGame = 1;
1205       if (*appData.loadGameFile != NULLCHAR) 
1206         {
1207           int index = appData.loadGameIndex; // [HGM] autoinc
1208           if(index<0) lastIndex = index = 1;
1209           if (!LoadGameFromFile(appData.loadGameFile,
1210                                 index,
1211                                 appData.loadGameFile, FALSE)) 
1212             {
1213               DisplayFatalError(_("Bad game file"), 0, 1);
1214               return;
1215             }
1216         } 
1217       else if (*appData.loadPositionFile != NULLCHAR) 
1218         {
1219           int index = appData.loadPositionIndex; // [HGM] autoinc
1220           if(index<0) lastIndex = index = 1;
1221           if (!LoadPositionFromFile(appData.loadPositionFile,
1222                                     index,
1223                                     appData.loadPositionFile)) 
1224             {
1225               DisplayFatalError(_("Bad position file"), 0, 1);
1226               return;
1227             }
1228         }
1229       TwoMachinesEvent();
1230     } 
1231   else if (*appData.cmailGameName != NULLCHAR) 
1232     {
1233       /* Set up cmail mode */
1234       ReloadCmailMsgEvent(TRUE);
1235     } 
1236   else 
1237     {
1238       /* Set up other modes */
1239       if (initialMode == AnalyzeFile) 
1240         {
1241           if (*appData.loadGameFile == NULLCHAR) 
1242             {
1243               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1244               return;
1245             }
1246         }
1247       if (*appData.loadGameFile != NULLCHAR) 
1248         {
1249           (void) LoadGameFromFile(appData.loadGameFile,
1250                                   appData.loadGameIndex,
1251                                   appData.loadGameFile, TRUE);
1252         } 
1253       else if (*appData.loadPositionFile != NULLCHAR) 
1254         {
1255           (void) LoadPositionFromFile(appData.loadPositionFile,
1256                                       appData.loadPositionIndex,
1257                                       appData.loadPositionFile);
1258           /* [HGM] try to make self-starting even after FEN load */
1259           /* to allow automatic setup of fairy variants with wtm */
1260           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1261             {
1262               gameMode = BeginningOfGame;
1263               setboardSpoiledMachineBlack = 1;
1264             }
1265           /* [HGM] loadPos: make that every new game uses the setup */
1266           /* from file as long as we do not switch variant          */
1267           if(!blackPlaysFirst) 
1268             {
1269               startedFromPositionFile = TRUE;
1270               CopyBoard(filePosition, boards[0]);
1271             }
1272         }
1273       if (initialMode == AnalyzeMode) 
1274         {
1275           if (appData.noChessProgram) 
1276             {
1277               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1278               return;
1279             }
1280           if (appData.icsActive) 
1281             {
1282               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1283               return;
1284             }
1285           AnalyzeModeEvent();
1286         } 
1287       else if (initialMode == AnalyzeFile) 
1288         {
1289           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290           ShowThinkingEvent();
1291           AnalyzeFileEvent();
1292           AnalysisPeriodicEvent(1);
1293         } 
1294       else if (initialMode == MachinePlaysWhite) 
1295         {
1296           if (appData.noChessProgram) 
1297             {
1298               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1299                                 0, 2);
1300               return;
1301             }
1302           if (appData.icsActive) 
1303             {
1304               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1305                                 0, 2);
1306               return;
1307             }
1308           MachineWhiteEvent();
1309         } 
1310       else if (initialMode == MachinePlaysBlack) 
1311         {
1312           if (appData.noChessProgram) 
1313             {
1314               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1315                                 0, 2);
1316               return;
1317             }
1318           if (appData.icsActive) 
1319             {
1320               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1321                                 0, 2);
1322               return;
1323             }
1324           MachineBlackEvent();
1325         } 
1326       else if (initialMode == TwoMachinesPlay) 
1327         {
1328           if (appData.noChessProgram) 
1329             {
1330               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1331                                 0, 2);
1332               return;
1333             }
1334           if (appData.icsActive) 
1335             {
1336               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1337                                 0, 2);
1338               return;
1339             }
1340           TwoMachinesEvent();
1341         } 
1342       else if (initialMode == EditGame) 
1343         {
1344           EditGameEvent();
1345         } 
1346       else if (initialMode == EditPosition) 
1347         {
1348           EditPositionEvent();
1349         } 
1350       else if (initialMode == Training) 
1351         {
1352           if (*appData.loadGameFile == NULLCHAR) 
1353             {
1354               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1355               return;
1356             }
1357           TrainingEvent();
1358         }
1359     }
1360   
1361   return;
1362 }
1363
1364 /*
1365  * Establish will establish a contact to a remote host.port.
1366  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367  *  used to talk to the host.
1368  * Returns 0 if okay, error code if not.
1369  */
1370 int
1371 establish()
1372 {
1373     char buf[MSG_SIZ];
1374
1375     if (*appData.icsCommPort != NULLCHAR) {
1376         /* Talk to the host through a serial comm port */
1377         return OpenCommPort(appData.icsCommPort, &icsPR);
1378
1379     } else if (*appData.gateway != NULLCHAR) {
1380         if (*appData.remoteShell == NULLCHAR) {
1381             /* Use the rcmd protocol to run telnet program on a gateway host */
1382             snprintf(buf, sizeof(buf), "%s %s %s",
1383                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1384             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1385
1386         } else {
1387             /* Use the rsh program to run telnet program on a gateway host */
1388             if (*appData.remoteUser == NULLCHAR) {
1389                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390                         appData.gateway, appData.telnetProgram,
1391                         appData.icsHost, appData.icsPort);
1392             } else {
1393                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394                         appData.remoteShell, appData.gateway,
1395                         appData.remoteUser, appData.telnetProgram,
1396                         appData.icsHost, appData.icsPort);
1397             }
1398             return StartChildProcess(buf, "", &icsPR);
1399
1400         }
1401     } else if (appData.useTelnet) {
1402         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1403
1404     } else {
1405         /* TCP socket interface differs somewhat between
1406            Unix and NT; handle details in the front end.
1407            */
1408         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1409     }
1410 }
1411
1412 void
1413 show_bytes(fp, buf, count)
1414      FILE *fp;
1415      char *buf;
1416      int count;
1417 {
1418     while (count--) {
1419         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420             fprintf(fp, "\\%03o", *buf & 0xff);
1421         } else {
1422             putc(*buf, fp);
1423         }
1424         buf++;
1425     }
1426     fflush(fp);
1427 }
1428
1429 /* Returns an errno value */
1430 int
1431 OutputMaybeTelnet(pr, message, count, outError)
1432      ProcRef pr;
1433      char *message;
1434      int count;
1435      int *outError;
1436 {
1437     char buf[8192], *p, *q, *buflim;
1438     int left, newcount, outcount;
1439
1440     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441         *appData.gateway != NULLCHAR) {
1442         if (appData.debugMode) {
1443             fprintf(debugFP, ">ICS: ");
1444             show_bytes(debugFP, message, count);
1445             fprintf(debugFP, "\n");
1446         }
1447         return OutputToProcess(pr, message, count, outError);
1448     }
1449
1450     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1451     p = message;
1452     q = buf;
1453     left = count;
1454     newcount = 0;
1455     while (left) {
1456         if (q >= buflim) {
1457             if (appData.debugMode) {
1458                 fprintf(debugFP, ">ICS: ");
1459                 show_bytes(debugFP, buf, newcount);
1460                 fprintf(debugFP, "\n");
1461             }
1462             outcount = OutputToProcess(pr, buf, newcount, outError);
1463             if (outcount < newcount) return -1; /* to be sure */
1464             q = buf;
1465             newcount = 0;
1466         }
1467         if (*p == '\n') {
1468             *q++ = '\r';
1469             newcount++;
1470         } else if (((unsigned char) *p) == TN_IAC) {
1471             *q++ = (char) TN_IAC;
1472             newcount ++;
1473         }
1474         *q++ = *p++;
1475         newcount++;
1476         left--;
1477     }
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, buf, newcount);
1481         fprintf(debugFP, "\n");
1482     }
1483     outcount = OutputToProcess(pr, buf, newcount, outError);
1484     if (outcount < newcount) return -1; /* to be sure */
1485     return count;
1486 }
1487
1488 void
1489 read_from_player(isr, closure, message, count, error)
1490      InputSourceRef isr;
1491      VOIDSTAR closure;
1492      char *message;
1493      int count;
1494      int error;
1495 {
1496     int outError, outCount;
1497     static int gotEof = 0;
1498
1499     /* Pass data read from player on to ICS */
1500     if (count > 0) {
1501         gotEof = 0;
1502         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503         if (outCount < count) {
1504             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505         }
1506     } else if (count < 0) {
1507         RemoveInputSource(isr);
1508         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509     } else if (gotEof++ > 0) {
1510         RemoveInputSource(isr);
1511         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1512     }
1513 }
1514
1515 void
1516 KeepAlive()
1517 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520     SendToICS("date\n");
1521     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1522 }
1523
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1526 {
1527     char buffer[MSG_SIZ];
1528     va_list args;
1529
1530     va_start(args, format);
1531     vsnprintf(buffer, sizeof(buffer), format, args);
1532     buffer[sizeof(buffer)-1] = '\0';
1533     SendToICS(buffer);
1534     va_end(args);
1535 }
1536
1537 void
1538 SendToICS(s)
1539      char *s;
1540 {
1541     int count, outCount, outError;
1542
1543     if (icsPR == NULL) return;
1544
1545     count = strlen(s);
1546     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547     if (outCount < count) {
1548         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1549     }
1550 }
1551
1552 /* This is used for sending logon scripts to the ICS. Sending
1553    without a delay causes problems when using timestamp on ICC
1554    (at least on my machine). */
1555 void
1556 SendToICSDelayed(s,msdelay)
1557      char *s;
1558      long msdelay;
1559 {
1560     int count, outCount, outError;
1561
1562     if (icsPR == NULL) return;
1563
1564     count = strlen(s);
1565     if (appData.debugMode) {
1566         fprintf(debugFP, ">ICS: ");
1567         show_bytes(debugFP, s, count);
1568         fprintf(debugFP, "\n");
1569     }
1570     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1571                                       msdelay);
1572     if (outCount < count) {
1573         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1574     }
1575 }
1576
1577
1578 /* Remove all highlighting escape sequences in s
1579    Also deletes any suffix starting with '('
1580    */
1581 char *
1582 StripHighlightAndTitle(s)
1583      char *s;
1584 {
1585     static char retbuf[MSG_SIZ];
1586     char *p = retbuf;
1587
1588     while (*s != NULLCHAR) {
1589         while (*s == '\033') {
1590             while (*s != NULLCHAR && !isalpha(*s)) s++;
1591             if (*s != NULLCHAR) s++;
1592         }
1593         while (*s != NULLCHAR && *s != '\033') {
1594             if (*s == '(' || *s == '[') {
1595                 *p = NULLCHAR;
1596                 return retbuf;
1597             }
1598             *p++ = *s++;
1599         }
1600     }
1601     *p = NULLCHAR;
1602     return retbuf;
1603 }
1604
1605 /* Remove all highlighting escape sequences in s */
1606 char *
1607 StripHighlight(s)
1608      char *s;
1609 {
1610     static char retbuf[MSG_SIZ];
1611     char *p = retbuf;
1612
1613     while (*s != NULLCHAR) {
1614         while (*s == '\033') {
1615             while (*s != NULLCHAR && !isalpha(*s)) s++;
1616             if (*s != NULLCHAR) s++;
1617         }
1618         while (*s != NULLCHAR && *s != '\033') {
1619             *p++ = *s++;
1620         }
1621     }
1622     *p = NULLCHAR;
1623     return retbuf;
1624 }
1625
1626 char *variantNames[] = VARIANT_NAMES;
1627 char *
1628 VariantName(v)
1629      VariantClass v;
1630 {
1631     return variantNames[v];
1632 }
1633
1634
1635 /* Identify a variant from the strings the chess servers use or the
1636    PGN Variant tag names we use. */
1637 VariantClass
1638 StringToVariant(e)
1639      char *e;
1640 {
1641     char *p;
1642     int wnum = -1;
1643     VariantClass v = VariantNormal;
1644     int i, found = FALSE;
1645     char buf[MSG_SIZ];
1646
1647     if (!e) return v;
1648
1649     /* [HGM] skip over optional board-size prefixes */
1650     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652         while( *e++ != '_');
1653     }
1654
1655     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1656         v = VariantNormal;
1657         found = TRUE;
1658     } else
1659     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660       if (StrCaseStr(e, variantNames[i])) {
1661         v = (VariantClass) i;
1662         found = TRUE;
1663         break;
1664       }
1665     }
1666
1667     if (!found) {
1668       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669           || StrCaseStr(e, "wild/fr")
1670           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671         v = VariantFischeRandom;
1672       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673                  (i = 1, p = StrCaseStr(e, "w"))) {
1674         p += i;
1675         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1676         if (isdigit(*p)) {
1677           wnum = atoi(p);
1678         } else {
1679           wnum = -1;
1680         }
1681         switch (wnum) {
1682         case 0: /* FICS only, actually */
1683         case 1:
1684           /* Castling legal even if K starts on d-file */
1685           v = VariantWildCastle;
1686           break;
1687         case 2:
1688         case 3:
1689         case 4:
1690           /* Castling illegal even if K & R happen to start in
1691              normal positions. */
1692           v = VariantNoCastle;
1693           break;
1694         case 5:
1695         case 7:
1696         case 8:
1697         case 10:
1698         case 11:
1699         case 12:
1700         case 13:
1701         case 14:
1702         case 15:
1703         case 18:
1704         case 19:
1705           /* Castling legal iff K & R start in normal positions */
1706           v = VariantNormal;
1707           break;
1708         case 6:
1709         case 20:
1710         case 21:
1711           /* Special wilds for position setup; unclear what to do here */
1712           v = VariantLoadable;
1713           break;
1714         case 9:
1715           /* Bizarre ICC game */
1716           v = VariantTwoKings;
1717           break;
1718         case 16:
1719           v = VariantKriegspiel;
1720           break;
1721         case 17:
1722           v = VariantLosers;
1723           break;
1724         case 22:
1725           v = VariantFischeRandom;
1726           break;
1727         case 23:
1728           v = VariantCrazyhouse;
1729           break;
1730         case 24:
1731           v = VariantBughouse;
1732           break;
1733         case 25:
1734           v = Variant3Check;
1735           break;
1736         case 26:
1737           /* Not quite the same as FICS suicide! */
1738           v = VariantGiveaway;
1739           break;
1740         case 27:
1741           v = VariantAtomic;
1742           break;
1743         case 28:
1744           v = VariantShatranj;
1745           break;
1746
1747         /* Temporary names for future ICC types.  The name *will* change in
1748            the next xboard/WinBoard release after ICC defines it. */
1749         case 29:
1750           v = Variant29;
1751           break;
1752         case 30:
1753           v = Variant30;
1754           break;
1755         case 31:
1756           v = Variant31;
1757           break;
1758         case 32:
1759           v = Variant32;
1760           break;
1761         case 33:
1762           v = Variant33;
1763           break;
1764         case 34:
1765           v = Variant34;
1766           break;
1767         case 35:
1768           v = Variant35;
1769           break;
1770         case 36:
1771           v = Variant36;
1772           break;
1773         case 37:
1774           v = VariantShogi;
1775           break;
1776         case 38:
1777           v = VariantXiangqi;
1778           break;
1779         case 39:
1780           v = VariantCourier;
1781           break;
1782         case 40:
1783           v = VariantGothic;
1784           break;
1785         case 41:
1786           v = VariantCapablanca;
1787           break;
1788         case 42:
1789           v = VariantKnightmate;
1790           break;
1791         case 43:
1792           v = VariantFairy;
1793           break;
1794         case 44:
1795           v = VariantCylinder;
1796           break;
1797         case 45:
1798           v = VariantFalcon;
1799           break;
1800         case 46:
1801           v = VariantCapaRandom;
1802           break;
1803         case 47:
1804           v = VariantBerolina;
1805           break;
1806         case 48:
1807           v = VariantJanus;
1808           break;
1809         case 49:
1810           v = VariantSuper;
1811           break;
1812         case 50:
1813           v = VariantGreat;
1814           break;
1815         case -1:
1816           /* Found "wild" or "w" in the string but no number;
1817              must assume it's normal chess. */
1818           v = VariantNormal;
1819           break;
1820         default:
1821           sprintf(buf, _("Unknown wild type %d"), wnum);
1822           DisplayError(buf, 0);
1823           v = VariantUnknown;
1824           break;
1825         }
1826       }
1827     }
1828     if (appData.debugMode) {
1829       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830               e, wnum, VariantName(v));
1831     }
1832     return v;
1833 }
1834
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1837
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839    advance *index beyond it, and set leftover_start to the new value of
1840    *index; else return FALSE.  If pattern contains the character '*', it
1841    matches any sequence of characters not containing '\r', '\n', or the
1842    character following the '*' (if any), and the matched sequence(s) are
1843    copied into star_match.
1844    */
1845 int
1846 looking_at(buf, index, pattern)
1847      char *buf;
1848      int *index;
1849      char *pattern;
1850 {
1851     char *bufp = &buf[*index], *patternp = pattern;
1852     int star_count = 0;
1853     char *matchp = star_match[0];
1854
1855     for (;;) {
1856         if (*patternp == NULLCHAR) {
1857             *index = leftover_start = bufp - buf;
1858             *matchp = NULLCHAR;
1859             return TRUE;
1860         }
1861         if (*bufp == NULLCHAR) return FALSE;
1862         if (*patternp == '*') {
1863             if (*bufp == *(patternp + 1)) {
1864                 *matchp = NULLCHAR;
1865                 matchp = star_match[++star_count];
1866                 patternp += 2;
1867                 bufp++;
1868                 continue;
1869             } else if (*bufp == '\n' || *bufp == '\r') {
1870                 patternp++;
1871                 if (*patternp == NULLCHAR)
1872                   continue;
1873                 else
1874                   return FALSE;
1875             } else {
1876                 *matchp++ = *bufp++;
1877                 continue;
1878             }
1879         }
1880         if (*patternp != *bufp) return FALSE;
1881         patternp++;
1882         bufp++;
1883     }
1884 }
1885
1886 void
1887 SendToPlayer(data, length)
1888      char *data;
1889      int length;
1890 {
1891     int error, outCount;
1892     outCount = OutputToProcess(NoProc, data, length, &error);
1893     if (outCount < length) {
1894         DisplayFatalError(_("Error writing to display"), error, 1);
1895     }
1896 }
1897
1898 void
1899 PackHolding(packed, holding)
1900      char packed[];
1901      char *holding;
1902 {
1903     char *p = holding;
1904     char *q = packed;
1905     int runlength = 0;
1906     int curr = 9999;
1907     do {
1908         if (*p == curr) {
1909             runlength++;
1910         } else {
1911             switch (runlength) {
1912               case 0:
1913                 break;
1914               case 1:
1915                 *q++ = curr;
1916                 break;
1917               case 2:
1918                 *q++ = curr;
1919                 *q++ = curr;
1920                 break;
1921               default:
1922                 sprintf(q, "%d", runlength);
1923                 while (*q) q++;
1924                 *q++ = curr;
1925                 break;
1926             }
1927             runlength = 1;
1928             curr = *p;
1929         }
1930     } while (*p++);
1931     *q = NULLCHAR;
1932 }
1933
1934 /* Telnet protocol requests from the front end */
1935 void
1936 TelnetRequest(ddww, option)
1937      unsigned char ddww, option;
1938 {
1939     unsigned char msg[3];
1940     int outCount, outError;
1941
1942     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1943
1944     if (appData.debugMode) {
1945         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1946         switch (ddww) {
1947           case TN_DO:
1948             ddwwStr = "DO";
1949             break;
1950           case TN_DONT:
1951             ddwwStr = "DONT";
1952             break;
1953           case TN_WILL:
1954             ddwwStr = "WILL";
1955             break;
1956           case TN_WONT:
1957             ddwwStr = "WONT";
1958             break;
1959           default:
1960             ddwwStr = buf1;
1961             sprintf(buf1, "%d", ddww);
1962             break;
1963         }
1964         switch (option) {
1965           case TN_ECHO:
1966             optionStr = "ECHO";
1967             break;
1968           default:
1969             optionStr = buf2;
1970             sprintf(buf2, "%d", option);
1971             break;
1972         }
1973         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1974     }
1975     msg[0] = TN_IAC;
1976     msg[1] = ddww;
1977     msg[2] = option;
1978     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1979     if (outCount < 3) {
1980         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1981     }
1982 }
1983
1984 void
1985 DoEcho()
1986 {
1987     if (!appData.icsActive) return;
1988     TelnetRequest(TN_DO, TN_ECHO);
1989 }
1990
1991 void
1992 DontEcho()
1993 {
1994     if (!appData.icsActive) return;
1995     TelnetRequest(TN_DONT, TN_ECHO);
1996 }
1997
1998 void
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2000 {
2001     /* put the holdings sent to us by the server on the board holdings area */
2002     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2003     char p;
2004     ChessSquare piece;
2005
2006     if(gameInfo.holdingsWidth < 2)  return;
2007     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008         return; // prevent overwriting by pre-board holdings
2009
2010     if( (int)lowestPiece >= BlackPawn ) {
2011         holdingsColumn = 0;
2012         countsColumn = 1;
2013         holdingsStartRow = BOARD_HEIGHT-1;
2014         direction = -1;
2015     } else {
2016         holdingsColumn = BOARD_WIDTH-1;
2017         countsColumn = BOARD_WIDTH-2;
2018         holdingsStartRow = 0;
2019         direction = 1;
2020     }
2021
2022     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023         board[i][holdingsColumn] = EmptySquare;
2024         board[i][countsColumn]   = (ChessSquare) 0;
2025     }
2026     while( (p=*holdings++) != NULLCHAR ) {
2027         piece = CharToPiece( ToUpper(p) );
2028         if(piece == EmptySquare) continue;
2029         /*j = (int) piece - (int) WhitePawn;*/
2030         j = PieceToNumber(piece);
2031         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032         if(j < 0) continue;               /* should not happen */
2033         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035         board[holdingsStartRow+j*direction][countsColumn]++;
2036     }
2037 }
2038
2039
2040 void
2041 VariantSwitch(Board board, VariantClass newVariant)
2042 {
2043    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2044    Board oldBoard;
2045
2046    startedFromPositionFile = FALSE;
2047    if(gameInfo.variant == newVariant) return;
2048
2049    /* [HGM] This routine is called each time an assignment is made to
2050     * gameInfo.variant during a game, to make sure the board sizes
2051     * are set to match the new variant. If that means adding or deleting
2052     * holdings, we shift the playing board accordingly
2053     * This kludge is needed because in ICS observe mode, we get boards
2054     * of an ongoing game without knowing the variant, and learn about the
2055     * latter only later. This can be because of the move list we requested,
2056     * in which case the game history is refilled from the beginning anyway,
2057     * but also when receiving holdings of a crazyhouse game. In the latter
2058     * case we want to add those holdings to the already received position.
2059     */
2060    
2061    if (appData.debugMode) {
2062      fprintf(debugFP, "Switch board from %s to %s\n",
2063              VariantName(gameInfo.variant), VariantName(newVariant));
2064      setbuf(debugFP, NULL);
2065    }
2066    shuffleOpenings = 0;       /* [HGM] shuffle */
2067    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2068    switch(newVariant) 
2069      {
2070      case VariantShogi:
2071        newWidth = 9;  newHeight = 9;
2072        gameInfo.holdingsSize = 7;
2073      case VariantBughouse:
2074      case VariantCrazyhouse:
2075        newHoldingsWidth = 2; break;
2076      case VariantGreat:
2077        newWidth = 10;
2078      case VariantSuper:
2079        newHoldingsWidth = 2;
2080        gameInfo.holdingsSize = 8;
2081        break;
2082      case VariantGothic:
2083      case VariantCapablanca:
2084      case VariantCapaRandom:
2085        newWidth = 10;
2086      default:
2087        newHoldingsWidth = gameInfo.holdingsSize = 0;
2088      };
2089    
2090    if(newWidth  != gameInfo.boardWidth  ||
2091       newHeight != gameInfo.boardHeight ||
2092       newHoldingsWidth != gameInfo.holdingsWidth ) {
2093      
2094      /* shift position to new playing area, if needed */
2095      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096        for(i=0; i<BOARD_HEIGHT; i++) 
2097          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099              board[i][j];
2100        for(i=0; i<newHeight; i++) {
2101          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2103        }
2104      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105        for(i=0; i<BOARD_HEIGHT; i++)
2106          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2108              board[i][j];
2109      }
2110      gameInfo.boardWidth  = newWidth;
2111      gameInfo.boardHeight = newHeight;
2112      gameInfo.holdingsWidth = newHoldingsWidth;
2113      gameInfo.variant = newVariant;
2114      InitDrawingSizes(-2, 0);
2115    } else gameInfo.variant = newVariant;
2116    CopyBoard(oldBoard, board);   // remember correctly formatted board
2117      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2118    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2119 }
2120
2121 static int loggedOn = FALSE;
2122
2123 /*-- Game start info cache: --*/
2124 int gs_gamenum;
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\   ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2132
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2135
2136 // [HGM] seekgraph
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2140 #define SQUARE 0x80
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2149
2150 void
2151 PlotSeekAd(int i)
2152 {
2153         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155         if(r < minRating+100 && r >=0 ) r = minRating+100;
2156         if(r > maxRating) r = maxRating;
2157         if(tc < 1.) tc = 1.;
2158         if(tc > 95.) tc = 95.;
2159         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160         y = ((double)r - minRating)/(maxRating - minRating)
2161             * (h-vMargin-squareSize/8-1) + vMargin;
2162         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163         if(strstr(seekAdList[i], " u ")) color = 1;
2164         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165            !strstr(seekAdList[i], "bullet") &&
2166            !strstr(seekAdList[i], "blitz") &&
2167            !strstr(seekAdList[i], "standard") ) color = 2;
2168         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2170 }
2171
2172 void
2173 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2174 {
2175         char buf[MSG_SIZ], *ext = "";
2176         VariantClass v = StringToVariant(type);
2177         if(strstr(type, "wild")) {
2178             ext = type + 4; // append wild number
2179             if(v == VariantFischeRandom) type = "chess960"; else
2180             if(v == VariantLoadable) type = "setup"; else
2181             type = VariantName(v);
2182         }
2183         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189             seekNrList[nrOfSeekAds] = nr;
2190             zList[nrOfSeekAds] = 0;
2191             seekAdList[nrOfSeekAds++] = StrSave(buf);
2192             if(plot) PlotSeekAd(nrOfSeekAds-1);
2193         }
2194 }
2195
2196 void
2197 EraseSeekDot(int i)
2198 {
2199     int x = xList[i], y = yList[i], d=squareSize/4, k;
2200     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202     // now replot every dot that overlapped
2203     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204         int xx = xList[k], yy = yList[k];
2205         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206             DrawSeekDot(xx, yy, colorList[k]);
2207     }
2208 }
2209
2210 void
2211 RemoveSeekAd(int nr)
2212 {
2213         int i;
2214         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2215             EraseSeekDot(i);
2216             if(seekAdList[i]) free(seekAdList[i]);
2217             seekAdList[i] = seekAdList[--nrOfSeekAds];
2218             seekNrList[i] = seekNrList[nrOfSeekAds];
2219             ratingList[i] = ratingList[nrOfSeekAds];
2220             colorList[i]  = colorList[nrOfSeekAds];
2221             tcList[i] = tcList[nrOfSeekAds];
2222             xList[i]  = xList[nrOfSeekAds];
2223             yList[i]  = yList[nrOfSeekAds];
2224             zList[i]  = zList[nrOfSeekAds];
2225             seekAdList[nrOfSeekAds] = NULL;
2226             break;
2227         }
2228 }
2229
2230 Boolean
2231 MatchSoughtLine(char *line)
2232 {
2233     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234     int nr, base, inc, u=0; char dummy;
2235
2236     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2238        (u=1) &&
2239        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2241         // match: compact and save the line
2242         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2243         return TRUE;
2244     }
2245     return FALSE;
2246 }
2247
2248 int
2249 DrawSeekGraph()
2250 {
2251     if(!seekGraphUp) return FALSE;
2252     int i;
2253     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2255
2256     DrawSeekBackground(0, 0, w, h);
2257     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2261         yy = h-1-yy;
2262         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2263         if(i%500 == 0) {
2264             char buf[MSG_SIZ];
2265             sprintf(buf, "%d", i);
2266             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2267         }
2268     }
2269     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270     for(i=1; i<100; i+=(i<10?1:5)) {
2271         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2274             char buf[MSG_SIZ];
2275             sprintf(buf, "%d", i);
2276             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2277         }
2278     }
2279     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2280     return TRUE;
2281 }
2282
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2284 {
2285     static int lastDown = 0, displayed = 0, lastSecond;
2286     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287         if(click == Release || moving) return FALSE;
2288         nrOfSeekAds = 0;
2289         soughtPending = TRUE;
2290         SendToICS(ics_prefix);
2291         SendToICS("sought\n"); // should this be "sought all"?
2292     } else { // issue challenge based on clicked ad
2293         int dist = 10000; int i, closest = 0, second = 0;
2294         for(i=0; i<nrOfSeekAds; i++) {
2295             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2296             if(d < dist) { dist = d; closest = i; }
2297             second += (d - zList[i] < 120); // count in-range ads
2298             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2299         }
2300         if(dist < 120) {
2301             char buf[MSG_SIZ];
2302             second = (second > 1);
2303             if(displayed != closest || second != lastSecond) {
2304                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305                 lastSecond = second; displayed = closest;
2306             }
2307             sprintf(buf, "play %d\n", seekNrList[closest]);
2308             if(click == Press) {
2309                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2310                 lastDown = closest;
2311                 return TRUE;
2312             } // on press 'hit', only show info
2313             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314             SendToICS(ics_prefix);
2315             SendToICS(buf); // should this be "sought all"?
2316         } else if(click == Release) { // release 'miss' is ignored
2317             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318             if(moving == 2) { // right up-click
2319                 nrOfSeekAds = 0; // refresh graph
2320                 soughtPending = TRUE;
2321                 SendToICS(ics_prefix);
2322                 SendToICS("sought\n"); // should this be "sought all"?
2323             }
2324             return TRUE;
2325         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326         // press miss or release hit 'pop down' seek graph
2327         seekGraphUp = FALSE;
2328         DrawPosition(TRUE, NULL);
2329     }
2330     return TRUE;
2331 }
2332
2333 void
2334 read_from_ics(isr, closure, data, count, error)
2335      InputSourceRef isr;
2336      VOIDSTAR closure;
2337      char *data;
2338      int count;
2339      int error;
2340 {
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2350
2351     static int started = STARTED_NONE;
2352     static char parse[20000];
2353     static int parse_pos = 0;
2354     static char buf[BUF_SIZE + 1];
2355     static int firstTime = TRUE, intfSet = FALSE;
2356     static ColorClass prevColor = ColorNormal;
2357     static int savingComment = FALSE;
2358     static int cmatch = 0; // continuation sequence match
2359     char *bp;
2360     char str[500];
2361     int i, oldi;
2362     int buf_len;
2363     int next_out;
2364     int tkind;
2365     int backup;    /* [DM] For zippy color lines */
2366     char *p;
2367     char talker[MSG_SIZ]; // [HGM] chat
2368     int channel;
2369
2370     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2371
2372     if (appData.debugMode) {
2373       if (!error) {
2374         fprintf(debugFP, "<ICS: ");
2375         show_bytes(debugFP, data, count);
2376         fprintf(debugFP, "\n");
2377       }
2378     }
2379
2380     if (appData.debugMode) { int f = forwardMostMove;
2381         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2384     }
2385     if (count > 0) {
2386         /* If last read ended with a partial line that we couldn't parse,
2387            prepend it to the new read and try again. */
2388         if (leftover_len > 0) {
2389             for (i=0; i<leftover_len; i++)
2390               buf[i] = buf[leftover_start + i];
2391         }
2392
2393     /* copy new characters into the buffer */
2394     bp = buf + leftover_len;
2395     buf_len=leftover_len;
2396     for (i=0; i<count; i++)
2397     {
2398         // ignore these
2399         if (data[i] == '\r')
2400             continue;
2401
2402         // join lines split by ICS?
2403         if (!appData.noJoin)
2404         {
2405             /*
2406                 Joining just consists of finding matches against the
2407                 continuation sequence, and discarding that sequence
2408                 if found instead of copying it.  So, until a match
2409                 fails, there's nothing to do since it might be the
2410                 complete sequence, and thus, something we don't want
2411                 copied.
2412             */
2413             if (data[i] == cont_seq[cmatch])
2414             {
2415                 cmatch++;
2416                 if (cmatch == strlen(cont_seq))
2417                 {
2418                     cmatch = 0; // complete match.  just reset the counter
2419
2420                     /*
2421                         it's possible for the ICS to not include the space
2422                         at the end of the last word, making our [correct]
2423                         join operation fuse two separate words.  the server
2424                         does this when the space occurs at the width setting.
2425                     */
2426                     if (!buf_len || buf[buf_len-1] != ' ')
2427                     {
2428                         *bp++ = ' ';
2429                         buf_len++;
2430                     }
2431                 }
2432                 continue;
2433             }
2434             else if (cmatch)
2435             {
2436                 /*
2437                     match failed, so we have to copy what matched before
2438                     falling through and copying this character.  In reality,
2439                     this will only ever be just the newline character, but
2440                     it doesn't hurt to be precise.
2441                 */
2442                 strncpy(bp, cont_seq, cmatch);
2443                 bp += cmatch;
2444                 buf_len += cmatch;
2445                 cmatch = 0;
2446             }
2447         }
2448
2449         // copy this char
2450         *bp++ = data[i];
2451         buf_len++;
2452     }
2453
2454         buf[buf_len] = NULLCHAR;
2455 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2456         next_out = 0;
2457         leftover_start = 0;
2458
2459         i = 0;
2460         while (i < buf_len) {
2461             /* Deal with part of the TELNET option negotiation
2462                protocol.  We refuse to do anything beyond the
2463                defaults, except that we allow the WILL ECHO option,
2464                which ICS uses to turn off password echoing when we are
2465                directly connected to it.  We reject this option
2466                if localLineEditing mode is on (always on in xboard)
2467                and we are talking to port 23, which might be a real
2468                telnet server that will try to keep WILL ECHO on permanently.
2469              */
2470             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472                 unsigned char option;
2473                 oldi = i;
2474                 switch ((unsigned char) buf[++i]) {
2475                   case TN_WILL:
2476                     if (appData.debugMode)
2477                       fprintf(debugFP, "\n<WILL ");
2478                     switch (option = (unsigned char) buf[++i]) {
2479                       case TN_ECHO:
2480                         if (appData.debugMode)
2481                           fprintf(debugFP, "ECHO ");
2482                         /* Reply only if this is a change, according
2483                            to the protocol rules. */
2484                         if (remoteEchoOption) break;
2485                         if (appData.localLineEditing &&
2486                             atoi(appData.icsPort) == TN_PORT) {
2487                             TelnetRequest(TN_DONT, TN_ECHO);
2488                         } else {
2489                             EchoOff();
2490                             TelnetRequest(TN_DO, TN_ECHO);
2491                             remoteEchoOption = TRUE;
2492                         }
2493                         break;
2494                       default:
2495                         if (appData.debugMode)
2496                           fprintf(debugFP, "%d ", option);
2497                         /* Whatever this is, we don't want it. */
2498                         TelnetRequest(TN_DONT, option);
2499                         break;
2500                     }
2501                     break;
2502                   case TN_WONT:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<WONT ");
2505                     switch (option = (unsigned char) buf[++i]) {
2506                       case TN_ECHO:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "ECHO ");
2509                         /* Reply only if this is a change, according
2510                            to the protocol rules. */
2511                         if (!remoteEchoOption) break;
2512                         EchoOn();
2513                         TelnetRequest(TN_DONT, TN_ECHO);
2514                         remoteEchoOption = FALSE;
2515                         break;
2516                       default:
2517                         if (appData.debugMode)
2518                           fprintf(debugFP, "%d ", (unsigned char) option);
2519                         /* Whatever this is, it must already be turned
2520                            off, because we never agree to turn on
2521                            anything non-default, so according to the
2522                            protocol rules, we don't reply. */
2523                         break;
2524                     }
2525                     break;
2526                   case TN_DO:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<DO ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       default:
2531                         /* Whatever this is, we refuse to do it. */
2532                         if (appData.debugMode)
2533                           fprintf(debugFP, "%d ", option);
2534                         TelnetRequest(TN_WONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_DONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<DONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       default:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "%d ", option);
2545                         /* Whatever this is, we are already not doing
2546                            it, because we never agree to do anything
2547                            non-default, so according to the protocol
2548                            rules, we don't reply. */
2549                         break;
2550                     }
2551                     break;
2552                   case TN_IAC:
2553                     if (appData.debugMode)
2554                       fprintf(debugFP, "\n<IAC ");
2555                     /* Doubled IAC; pass it through */
2556                     i--;
2557                     break;
2558                   default:
2559                     if (appData.debugMode)
2560                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561                     /* Drop all other telnet commands on the floor */
2562                     break;
2563                 }
2564                 if (oldi > next_out)
2565                   SendToPlayer(&buf[next_out], oldi - next_out);
2566                 if (++i > next_out)
2567                   next_out = i;
2568                 continue;
2569             }
2570
2571             /* OK, this at least will *usually* work */
2572             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2573                 loggedOn = TRUE;
2574             }
2575
2576             if (loggedOn && !intfSet) {
2577                 if (ics_type == ICS_ICC) {
2578                   sprintf(str,
2579                           "/set-quietly interface %s\n/set-quietly style 12\n",
2580                           programVersion);
2581                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2583                 } else if (ics_type == ICS_CHESSNET) {
2584                   sprintf(str, "/style 12\n");
2585                 } else {
2586                   strcpy(str, "alias $ @\n$set interface ");
2587                   strcat(str, programVersion);
2588                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2591 #ifdef WIN32
2592                   strcat(str, "$iset nohighlight 1\n");
2593 #endif
2594                   strcat(str, "$iset lock 1\n$style 12\n");
2595                 }
2596                 SendToICS(str);
2597                 NotifyFrontendLogin();
2598                 intfSet = TRUE;
2599             }
2600
2601             if (started == STARTED_COMMENT) {
2602                 /* Accumulate characters in comment */
2603                 parse[parse_pos++] = buf[i];
2604                 if (buf[i] == '\n') {
2605                     parse[parse_pos] = NULLCHAR;
2606                     if(chattingPartner>=0) {
2607                         char mess[MSG_SIZ];
2608                         sprintf(mess, "%s%s", talker, parse);
2609                         OutputChatMessage(chattingPartner, mess);
2610                         chattingPartner = -1;
2611                     } else
2612                     if(!suppressKibitz) // [HGM] kibitz
2613                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615                         int nrDigit = 0, nrAlph = 0, j;
2616                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618                         parse[parse_pos] = NULLCHAR;
2619                         // try to be smart: if it does not look like search info, it should go to
2620                         // ICS interaction window after all, not to engine-output window.
2621                         for(j=0; j<parse_pos; j++) { // count letters and digits
2622                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2624                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2625                         }
2626                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627                             int depth=0; float score;
2628                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630                                 pvInfoList[forwardMostMove-1].depth = depth;
2631                                 pvInfoList[forwardMostMove-1].score = 100*score;
2632                             }
2633                             OutputKibitz(suppressKibitz, parse);
2634                             next_out = i+1; // [HGM] suppress printing in ICS window
2635                         } else {
2636                             char tmp[MSG_SIZ];
2637                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638                             SendToPlayer(tmp, strlen(tmp));
2639                         }
2640                     }
2641                     started = STARTED_NONE;
2642                 } else {
2643                     /* Don't match patterns against characters in comment */
2644                     i++;
2645                     continue;
2646                 }
2647             }
2648             if (started == STARTED_CHATTER) {
2649                 if (buf[i] != '\n') {
2650                     /* Don't match patterns against characters in chatter */
2651                     i++;
2652                     continue;
2653                 }
2654                 started = STARTED_NONE;
2655             }
2656
2657             /* Kludge to deal with rcmd protocol */
2658             if (firstTime && looking_at(buf, &i, "\001*")) {
2659                 DisplayFatalError(&buf[1], 0, 1);
2660                 continue;
2661             } else {
2662                 firstTime = FALSE;
2663             }
2664
2665             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2666                 ics_type = ICS_ICC;
2667                 ics_prefix = "/";
2668                 if (appData.debugMode)
2669                   fprintf(debugFP, "ics_type %d\n", ics_type);
2670                 continue;
2671             }
2672             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673                 ics_type = ICS_FICS;
2674                 ics_prefix = "$";
2675                 if (appData.debugMode)
2676                   fprintf(debugFP, "ics_type %d\n", ics_type);
2677                 continue;
2678             }
2679             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680                 ics_type = ICS_CHESSNET;
2681                 ics_prefix = "/";
2682                 if (appData.debugMode)
2683                   fprintf(debugFP, "ics_type %d\n", ics_type);
2684                 continue;
2685             }
2686
2687             if (!loggedOn &&
2688                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2690                  looking_at(buf, &i, "will be \"*\""))) {
2691               strcpy(ics_handle, star_match[0]);
2692               continue;
2693             }
2694
2695             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2696               char buf[MSG_SIZ];
2697               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698               DisplayIcsInteractionTitle(buf);
2699               have_set_title = TRUE;
2700             }
2701
2702             /* skip finger notes */
2703             if (started == STARTED_NONE &&
2704                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705                  (buf[i] == '1' && buf[i+1] == '0')) &&
2706                 buf[i+2] == ':' && buf[i+3] == ' ') {
2707               started = STARTED_CHATTER;
2708               i += 3;
2709               continue;
2710             }
2711
2712             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713             if(appData.seekGraph) {
2714                 if(soughtPending && MatchSoughtLine(buf+i)) {
2715                     i = strstr(buf+i, "rated") - buf;
2716                     next_out = leftover_start = i;
2717                     started = STARTED_CHATTER;
2718                     suppressKibitz = TRUE;
2719                     continue;
2720                 }
2721                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722                         && looking_at(buf, &i, "* ads displayed")) {
2723                     soughtPending = FALSE;
2724                     seekGraphUp = TRUE;
2725                     DrawSeekGraph();
2726                     continue;
2727                 }
2728                 if(appData.autoRefresh) {
2729                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730                         int s = (ics_type == ICS_ICC); // ICC format differs
2731                         if(seekGraphUp)
2732                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2733                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734                         looking_at(buf, &i, "*% "); // eat prompt
2735                         next_out = i; // suppress
2736                         continue;
2737                     }
2738                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739                         char *p = star_match[0];
2740                         while(*p) {
2741                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2742                             while(*p && *p++ != ' '); // next
2743                         }
2744                         looking_at(buf, &i, "*% "); // eat prompt
2745                         next_out = i;
2746                         continue;
2747                     }
2748                 }
2749             }
2750
2751             /* skip formula vars */
2752             if (started == STARTED_NONE &&
2753                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754               started = STARTED_CHATTER;
2755               i += 3;
2756               continue;
2757             }
2758
2759             oldi = i;
2760             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761             if (appData.autoKibitz && started == STARTED_NONE &&
2762                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2763                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764                 if(looking_at(buf, &i, "* kibitzes: ") &&
2765                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2767                         suppressKibitz = TRUE;
2768                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769                                 && (gameMode == IcsPlayingWhite)) ||
2770                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2772                             started = STARTED_CHATTER; // own kibitz we simply discard
2773                         else {
2774                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775                             parse_pos = 0; parse[0] = NULLCHAR;
2776                             savingComment = TRUE;
2777                             suppressKibitz = gameMode != IcsObserving ? 2 :
2778                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2779                         }
2780                         continue;
2781                 } else
2782                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783                     // suppress the acknowledgements of our own autoKibitz
2784                     char *p;
2785                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786                     SendToPlayer(star_match[0], strlen(star_match[0]));
2787                     looking_at(buf, &i, "*% "); // eat prompt
2788                     next_out = i;
2789                 }
2790             } // [HGM] kibitz: end of patch
2791
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2793
2794             // [HGM] chat: intercept tells by users for which we have an open chat window
2795             channel = -1;
2796             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2797                                            looking_at(buf, &i, "* whispers:") ||
2798                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2800                 int p;
2801                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802                 chattingPartner = -1;
2803
2804                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805                 for(p=0; p<MAX_CHAT; p++) {
2806                     if(channel == atoi(chatPartner[p])) {
2807                     talker[0] = '['; strcat(talker, "] ");
2808                     chattingPartner = p; break;
2809                     }
2810                 } else
2811                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812                 for(p=0; p<MAX_CHAT; p++) {
2813                     if(!strcmp("WHISPER", chatPartner[p])) {
2814                         talker[0] = '['; strcat(talker, "] ");
2815                         chattingPartner = p; break;
2816                     }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0;
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     started = STARTED_COMMENT;
2825                     parse_pos = 0; parse[0] = NULLCHAR;
2826                     savingComment = 3 + chattingPartner; // counts as TRUE
2827                     suppressKibitz = TRUE;
2828                 }
2829             } // [HGM] chat: end of patch
2830
2831             if (appData.zippyTalk || appData.zippyPlay) {
2832                 /* [DM] Backup address for color zippy lines */
2833                 backup = i;
2834 #if ZIPPY
2835        #ifdef WIN32
2836                if (loggedOn == TRUE)
2837                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2839        #else
2840                 if (ZippyControl(buf, &i) ||
2841                     ZippyConverse(buf, &i) ||
2842                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2843                       loggedOn = TRUE;
2844                       if (!appData.colorize) continue;
2845                 }
2846        #endif
2847 #endif
2848             } // [DM] 'else { ' deleted
2849                 if (
2850                     /* Regular tells and says */
2851                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2853                     looking_at(buf, &i, "* says: ") ||
2854                     /* Don't color "message" or "messages" output */
2855                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856                     looking_at(buf, &i, "*. * at *:*: ") ||
2857                     looking_at(buf, &i, "--* (*:*): ") ||
2858                     /* Message notifications (same color as tells) */
2859                     looking_at(buf, &i, "* has left a message ") ||
2860                     looking_at(buf, &i, "* just sent you a message:\n") ||
2861                     /* Whispers and kibitzes */
2862                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863                     looking_at(buf, &i, "* kibitzes: ") ||
2864                     /* Channel tells */
2865                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2866
2867                   if (tkind == 1 && strchr(star_match[0], ':')) {
2868                       /* Avoid "tells you:" spoofs in channels */
2869                      tkind = 3;
2870                   }
2871                   if (star_match[0][0] == NULLCHAR ||
2872                       strchr(star_match[0], ' ') ||
2873                       (tkind == 3 && strchr(star_match[1], ' '))) {
2874                     /* Reject bogus matches */
2875                     i = oldi;
2876                   } else {
2877                     if (appData.colorize) {
2878                       if (oldi > next_out) {
2879                         SendToPlayer(&buf[next_out], oldi - next_out);
2880                         next_out = oldi;
2881                       }
2882                       switch (tkind) {
2883                       case 1:
2884                         Colorize(ColorTell, FALSE);
2885                         curColor = ColorTell;
2886                         break;
2887                       case 2:
2888                         Colorize(ColorKibitz, FALSE);
2889                         curColor = ColorKibitz;
2890                         break;
2891                       case 3:
2892                         p = strrchr(star_match[1], '(');
2893                         if (p == NULL) {
2894                           p = star_match[1];
2895                         } else {
2896                           p++;
2897                         }
2898                         if (atoi(p) == 1) {
2899                           Colorize(ColorChannel1, FALSE);
2900                           curColor = ColorChannel1;
2901                         } else {
2902                           Colorize(ColorChannel, FALSE);
2903                           curColor = ColorChannel;
2904                         }
2905                         break;
2906                       case 5:
2907                         curColor = ColorNormal;
2908                         break;
2909                       }
2910                     }
2911                     if (started == STARTED_NONE && appData.autoComment &&
2912                         (gameMode == IcsObserving ||
2913                          gameMode == IcsPlayingWhite ||
2914                          gameMode == IcsPlayingBlack)) {
2915                       parse_pos = i - oldi;
2916                       memcpy(parse, &buf[oldi], parse_pos);
2917                       parse[parse_pos] = NULLCHAR;
2918                       started = STARTED_COMMENT;
2919                       savingComment = TRUE;
2920                     } else {
2921                       started = STARTED_CHATTER;
2922                       savingComment = FALSE;
2923                     }
2924                     loggedOn = TRUE;
2925                     continue;
2926                   }
2927                 }
2928
2929                 if (looking_at(buf, &i, "* s-shouts: ") ||
2930                     looking_at(buf, &i, "* c-shouts: ")) {
2931                     if (appData.colorize) {
2932                         if (oldi > next_out) {
2933                             SendToPlayer(&buf[next_out], oldi - next_out);
2934                             next_out = oldi;
2935                         }
2936                         Colorize(ColorSShout, FALSE);
2937                         curColor = ColorSShout;
2938                     }
2939                     loggedOn = TRUE;
2940                     started = STARTED_CHATTER;
2941                     continue;
2942                 }
2943
2944                 if (looking_at(buf, &i, "--->")) {
2945                     loggedOn = TRUE;
2946                     continue;
2947                 }
2948
2949                 if (looking_at(buf, &i, "* shouts: ") ||
2950                     looking_at(buf, &i, "--> ")) {
2951                     if (appData.colorize) {
2952                         if (oldi > next_out) {
2953                             SendToPlayer(&buf[next_out], oldi - next_out);
2954                             next_out = oldi;
2955                         }
2956                         Colorize(ColorShout, FALSE);
2957                         curColor = ColorShout;
2958                     }
2959                     loggedOn = TRUE;
2960                     started = STARTED_CHATTER;
2961                     continue;
2962                 }
2963
2964                 if (looking_at( buf, &i, "Challenge:")) {
2965                     if (appData.colorize) {
2966                         if (oldi > next_out) {
2967                             SendToPlayer(&buf[next_out], oldi - next_out);
2968                             next_out = oldi;
2969                         }
2970                         Colorize(ColorChallenge, FALSE);
2971                         curColor = ColorChallenge;
2972                     }
2973                     loggedOn = TRUE;
2974                     continue;
2975                 }
2976
2977                 if (looking_at(buf, &i, "* offers you") ||
2978                     looking_at(buf, &i, "* offers to be") ||
2979                     looking_at(buf, &i, "* would like to") ||
2980                     looking_at(buf, &i, "* requests to") ||
2981                     looking_at(buf, &i, "Your opponent offers") ||
2982                     looking_at(buf, &i, "Your opponent requests")) {
2983
2984                     if (appData.colorize) {
2985                         if (oldi > next_out) {
2986                             SendToPlayer(&buf[next_out], oldi - next_out);
2987                             next_out = oldi;
2988                         }
2989                         Colorize(ColorRequest, FALSE);
2990                         curColor = ColorRequest;
2991                     }
2992                     continue;
2993                 }
2994
2995                 if (looking_at(buf, &i, "* (*) seeking")) {
2996                     if (appData.colorize) {
2997                         if (oldi > next_out) {
2998                             SendToPlayer(&buf[next_out], oldi - next_out);
2999                             next_out = oldi;
3000                         }
3001                         Colorize(ColorSeek, FALSE);
3002                         curColor = ColorSeek;
3003                     }
3004                     continue;
3005             }
3006
3007             if (looking_at(buf, &i, "\\   ")) {
3008                 if (prevColor != ColorNormal) {
3009                     if (oldi > next_out) {
3010                         SendToPlayer(&buf[next_out], oldi - next_out);
3011                         next_out = oldi;
3012                     }
3013                     Colorize(prevColor, TRUE);
3014                     curColor = prevColor;
3015                 }
3016                 if (savingComment) {
3017                     parse_pos = i - oldi;
3018                     memcpy(parse, &buf[oldi], parse_pos);
3019                     parse[parse_pos] = NULLCHAR;
3020                     started = STARTED_COMMENT;
3021                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022                         chattingPartner = savingComment - 3; // kludge to remember the box
3023                 } else {
3024                     started = STARTED_CHATTER;
3025                 }
3026                 continue;
3027             }
3028
3029             if (looking_at(buf, &i, "Black Strength :") ||
3030                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031                 looking_at(buf, &i, "<10>") ||
3032                 looking_at(buf, &i, "#@#")) {
3033                 /* Wrong board style */
3034                 loggedOn = TRUE;
3035                 SendToICS(ics_prefix);
3036                 SendToICS("set style 12\n");
3037                 SendToICS(ics_prefix);
3038                 SendToICS("refresh\n");
3039                 continue;
3040             }
3041
3042             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3043                 ICSInitScript();
3044                 have_sent_ICS_logon = 1;
3045                 continue;
3046             }
3047
3048             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049                 (looking_at(buf, &i, "\n<12> ") ||
3050                  looking_at(buf, &i, "<12> "))) {
3051                 loggedOn = TRUE;
3052                 if (oldi > next_out) {
3053                     SendToPlayer(&buf[next_out], oldi - next_out);
3054                 }
3055                 next_out = i;
3056                 started = STARTED_BOARD;
3057                 parse_pos = 0;
3058                 continue;
3059             }
3060
3061             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062                 looking_at(buf, &i, "<b1> ")) {
3063                 if (oldi > next_out) {
3064                     SendToPlayer(&buf[next_out], oldi - next_out);
3065                 }
3066                 next_out = i;
3067                 started = STARTED_HOLDINGS;
3068                 parse_pos = 0;
3069                 continue;
3070             }
3071
3072             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3073                 loggedOn = TRUE;
3074                 /* Header for a move list -- first line */
3075
3076                 switch (ics_getting_history) {
3077                   case H_FALSE:
3078                     switch (gameMode) {
3079                       case IcsIdle:
3080                       case BeginningOfGame:
3081                         /* User typed "moves" or "oldmoves" while we
3082                            were idle.  Pretend we asked for these
3083                            moves and soak them up so user can step
3084                            through them and/or save them.
3085                            */
3086                         Reset(FALSE, TRUE);
3087                         gameMode = IcsObserving;
3088                         ModeHighlight();
3089                         ics_gamenum = -1;
3090                         ics_getting_history = H_GOT_UNREQ_HEADER;
3091                         break;
3092                       case EditGame: /*?*/
3093                       case EditPosition: /*?*/
3094                         /* Should above feature work in these modes too? */
3095                         /* For now it doesn't */
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                       default:
3099                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3100                         break;
3101                     }
3102                     break;
3103                   case H_REQUESTED:
3104                     /* Is this the right one? */
3105                     if (gameInfo.white && gameInfo.black &&
3106                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3107                         strcmp(gameInfo.black, star_match[2]) == 0) {
3108                         /* All is well */
3109                         ics_getting_history = H_GOT_REQ_HEADER;
3110                     }
3111                     break;
3112                   case H_GOT_REQ_HEADER:
3113                   case H_GOT_UNREQ_HEADER:
3114                   case H_GOT_UNWANTED_HEADER:
3115                   case H_GETTING_MOVES:
3116                     /* Should not happen */
3117                     DisplayError(_("Error gathering move list: two headers"), 0);
3118                     ics_getting_history = H_FALSE;
3119                     break;
3120                 }
3121
3122                 /* Save player ratings into gameInfo if needed */
3123                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125                     (gameInfo.whiteRating == -1 ||
3126                      gameInfo.blackRating == -1)) {
3127
3128                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3129                     gameInfo.blackRating = string_to_rating(star_match[3]);
3130                     if (appData.debugMode)
3131                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132                               gameInfo.whiteRating, gameInfo.blackRating);
3133                 }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i,
3138               "* * match, initial time: * minute*, increment: * second")) {
3139                 /* Header for a move list -- second line */
3140                 /* Initial board will follow if this is a wild game */
3141                 if (gameInfo.event != NULL) free(gameInfo.event);
3142                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143                 gameInfo.event = StrSave(str);
3144                 /* [HGM] we switched variant. Translate boards if needed. */
3145                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3146                 continue;
3147             }
3148
3149             if (looking_at(buf, &i, "Move  ")) {
3150                 /* Beginning of a move list */
3151                 switch (ics_getting_history) {
3152                   case H_FALSE:
3153                     /* Normally should not happen */
3154                     /* Maybe user hit reset while we were parsing */
3155                     break;
3156                   case H_REQUESTED:
3157                     /* Happens if we are ignoring a move list that is not
3158                      * the one we just requested.  Common if the user
3159                      * tries to observe two games without turning off
3160                      * getMoveList */
3161                     break;
3162                   case H_GETTING_MOVES:
3163                     /* Should not happen */
3164                     DisplayError(_("Error gathering move list: nested"), 0);
3165                     ics_getting_history = H_FALSE;
3166                     break;
3167                   case H_GOT_REQ_HEADER:
3168                     ics_getting_history = H_GETTING_MOVES;
3169                     started = STARTED_MOVES;
3170                     parse_pos = 0;
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                     }
3174                     break;
3175                   case H_GOT_UNREQ_HEADER:
3176                     ics_getting_history = H_GETTING_MOVES;
3177                     started = STARTED_MOVES_NOHIDE;
3178                     parse_pos = 0;
3179                     break;
3180                   case H_GOT_UNWANTED_HEADER:
3181                     ics_getting_history = H_FALSE;
3182                     break;
3183                 }
3184                 continue;
3185             }
3186
3187             if (looking_at(buf, &i, "% ") ||
3188                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191                     soughtPending = FALSE;
3192                     seekGraphUp = TRUE;
3193                     DrawSeekGraph();
3194                 }
3195                 if(suppressKibitz) next_out = i;
3196                 savingComment = FALSE;
3197                 suppressKibitz = 0;
3198                 switch (started) {
3199                   case STARTED_MOVES:
3200                   case STARTED_MOVES_NOHIDE:
3201                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202                     parse[parse_pos + i - oldi] = NULLCHAR;
3203                     ParseGameHistory(parse);
3204 #if ZIPPY
3205                     if (appData.zippyPlay && first.initDone) {
3206                         FeedMovesToProgram(&first, forwardMostMove);
3207                         if (gameMode == IcsPlayingWhite) {
3208                             if (WhiteOnMove(forwardMostMove)) {
3209                                 if (first.sendTime) {
3210                                   if (first.useColors) {
3211                                     SendToProgram("black\n", &first);
3212                                   }
3213                                   SendTimeRemaining(&first, TRUE);
3214                                 }
3215                                 if (first.useColors) {
3216                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3217                                 }
3218                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219                                 first.maybeThinking = TRUE;
3220                             } else {
3221                                 if (first.usePlayother) {
3222                                   if (first.sendTime) {
3223                                     SendTimeRemaining(&first, TRUE);
3224                                   }
3225                                   SendToProgram("playother\n", &first);
3226                                   firstMove = FALSE;
3227                                 } else {
3228                                   firstMove = TRUE;
3229                                 }
3230                             }
3231                         } else if (gameMode == IcsPlayingBlack) {
3232                             if (!WhiteOnMove(forwardMostMove)) {
3233                                 if (first.sendTime) {
3234                                   if (first.useColors) {
3235                                     SendToProgram("white\n", &first);
3236                                   }
3237                                   SendTimeRemaining(&first, FALSE);
3238                                 }
3239                                 if (first.useColors) {
3240                                   SendToProgram("black\n", &first);
3241                                 }
3242                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243                                 first.maybeThinking = TRUE;
3244                             } else {
3245                                 if (first.usePlayother) {
3246                                   if (first.sendTime) {
3247                                     SendTimeRemaining(&first, FALSE);
3248                                   }
3249                                   SendToProgram("playother\n", &first);
3250                                   firstMove = FALSE;
3251                                 } else {
3252                                   firstMove = TRUE;
3253                                 }
3254                             }
3255                         }
3256                     }
3257 #endif
3258                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3259                         /* Moves came from oldmoves or moves command
3260                            while we weren't doing anything else.
3261                            */
3262                         currentMove = forwardMostMove;
3263                         ClearHighlights();/*!!could figure this out*/
3264                         flipView = appData.flipView;
3265                         DrawPosition(TRUE, boards[currentMove]);
3266                         DisplayBothClocks();
3267                         sprintf(str, "%s vs. %s",
3268                                 gameInfo.white, gameInfo.black);
3269                         DisplayTitle(str);
3270                         gameMode = IcsIdle;
3271                     } else {
3272                         /* Moves were history of an active game */
3273                         if (gameInfo.resultDetails != NULL) {
3274                             free(gameInfo.resultDetails);
3275                             gameInfo.resultDetails = NULL;
3276                         }
3277                     }
3278                     HistorySet(parseList, backwardMostMove,
3279                                forwardMostMove, currentMove-1);
3280                     DisplayMove(currentMove - 1);
3281                     if (started == STARTED_MOVES) next_out = i;
3282                     started = STARTED_NONE;
3283                     ics_getting_history = H_FALSE;
3284                     break;
3285
3286                   case STARTED_OBSERVE:
3287                     started = STARTED_NONE;
3288                     SendToICS(ics_prefix);
3289                     SendToICS("refresh\n");
3290                     break;
3291
3292                   default:
3293                     break;
3294                 }
3295                 if(bookHit) { // [HGM] book: simulate book reply
3296                     static char bookMove[MSG_SIZ]; // a bit generous?
3297
3298                     programStats.nodes = programStats.depth = programStats.time =
3299                     programStats.score = programStats.got_only_move = 0;
3300                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3301
3302                     strcpy(bookMove, "move ");
3303                     strcat(bookMove, bookHit);
3304                     HandleMachineMove(bookMove, &first);
3305                 }
3306                 continue;
3307             }
3308
3309             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310                  started == STARTED_HOLDINGS ||
3311                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312                 /* Accumulate characters in move list or board */
3313                 parse[parse_pos++] = buf[i];
3314             }
3315
3316             /* Start of game messages.  Mostly we detect start of game
3317                when the first board image arrives.  On some versions
3318                of the ICS, though, we need to do a "refresh" after starting
3319                to observe in order to get the current board right away. */
3320             if (looking_at(buf, &i, "Adding game * to observation list")) {
3321                 started = STARTED_OBSERVE;
3322                 continue;
3323             }
3324
3325             /* Handle auto-observe */
3326             if (appData.autoObserve &&
3327                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3329                 char *player;
3330                 /* Choose the player that was highlighted, if any. */
3331                 if (star_match[0][0] == '\033' ||
3332                     star_match[1][0] != '\033') {
3333                     player = star_match[0];
3334                 } else {
3335                     player = star_match[2];
3336                 }
3337                 sprintf(str, "%sobserve %s\n",
3338                         ics_prefix, StripHighlightAndTitle(player));
3339                 SendToICS(str);
3340
3341                 /* Save ratings from notify string */
3342                 strcpy(player1Name, star_match[0]);
3343                 player1Rating = string_to_rating(star_match[1]);
3344                 strcpy(player2Name, star_match[2]);
3345                 player2Rating = string_to_rating(star_match[3]);
3346
3347                 if (appData.debugMode)
3348                   fprintf(debugFP,
3349                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3350                           player1Name, player1Rating,
3351                           player2Name, player2Rating);
3352
3353                 continue;
3354             }
3355
3356             /* Deal with automatic examine mode after a game,
3357                and with IcsObserving -> IcsExamining transition */
3358             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359                 looking_at(buf, &i, "has made you an examiner of game *")) {
3360
3361                 int gamenum = atoi(star_match[0]);
3362                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363                     gamenum == ics_gamenum) {
3364                     /* We were already playing or observing this game;
3365                        no need to refetch history */
3366                     gameMode = IcsExamining;
3367                     if (pausing) {
3368                         pauseExamForwardMostMove = forwardMostMove;
3369                     } else if (currentMove < forwardMostMove) {
3370                         ForwardInner(forwardMostMove);
3371                     }
3372                 } else {
3373                     /* I don't think this case really can happen */
3374                     SendToICS(ics_prefix);
3375                     SendToICS("refresh\n");
3376                 }
3377                 continue;
3378             }
3379
3380             /* Error messages */
3381 //          if (ics_user_moved) {
3382             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383                 if (looking_at(buf, &i, "Illegal move") ||
3384                     looking_at(buf, &i, "Not a legal move") ||
3385                     looking_at(buf, &i, "Your king is in check") ||
3386                     looking_at(buf, &i, "It isn't your turn") ||
3387                     looking_at(buf, &i, "It is not your move")) {
3388                     /* Illegal move */
3389                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390                         currentMove = --forwardMostMove;
3391                         DisplayMove(currentMove - 1); /* before DMError */
3392                         DrawPosition(FALSE, boards[currentMove]);
3393                         SwitchClocks();
3394                         DisplayBothClocks();
3395                     }
3396                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3397                     ics_user_moved = 0;
3398                     continue;
3399                 }
3400             }
3401
3402             if (looking_at(buf, &i, "still have time") ||
3403                 looking_at(buf, &i, "not out of time") ||
3404                 looking_at(buf, &i, "either player is out of time") ||
3405                 looking_at(buf, &i, "has timeseal; checking")) {
3406                 /* We must have called his flag a little too soon */
3407                 whiteFlag = blackFlag = FALSE;
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "added * seconds to") ||
3412                 looking_at(buf, &i, "seconds were added to")) {
3413                 /* Update the clocks */
3414                 SendToICS(ics_prefix);
3415                 SendToICS("refresh\n");
3416                 continue;
3417             }
3418
3419             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420                 ics_clock_paused = TRUE;
3421                 StopClocks();
3422                 continue;
3423             }
3424
3425             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426                 ics_clock_paused = FALSE;
3427                 StartClocks();
3428                 continue;
3429             }
3430
3431             /* Grab player ratings from the Creating: message.
3432                Note we have to check for the special case when
3433                the ICS inserts things like [white] or [black]. */
3434             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3436                 /* star_matches:
3437                    0    player 1 name (not necessarily white)
3438                    1    player 1 rating
3439                    2    empty, white, or black (IGNORED)
3440                    3    player 2 name (not necessarily black)
3441                    4    player 2 rating
3442
3443                    The names/ratings are sorted out when the game
3444                    actually starts (below).
3445                 */
3446                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447                 player1Rating = string_to_rating(star_match[1]);
3448                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449                 player2Rating = string_to_rating(star_match[4]);
3450
3451                 if (appData.debugMode)
3452                   fprintf(debugFP,
3453                           "Ratings from 'Creating:' %s %d, %s %d\n",
3454                           player1Name, player1Rating,
3455                           player2Name, player2Rating);
3456
3457                 continue;
3458             }
3459
3460             /* Improved generic start/end-of-game messages */
3461             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463                 /* If tkind == 0: */
3464                 /* star_match[0] is the game number */
3465                 /*           [1] is the white player's name */
3466                 /*           [2] is the black player's name */
3467                 /* For end-of-game: */
3468                 /*           [3] is the reason for the game end */
3469                 /*           [4] is a PGN end game-token, preceded by " " */
3470                 /* For start-of-game: */
3471                 /*           [3] begins with "Creating" or "Continuing" */
3472                 /*           [4] is " *" or empty (don't care). */
3473                 int gamenum = atoi(star_match[0]);
3474                 char *whitename, *blackname, *why, *endtoken;
3475                 ChessMove endtype = (ChessMove) 0;
3476
3477                 if (tkind == 0) {
3478                   whitename = star_match[1];
3479                   blackname = star_match[2];
3480                   why = star_match[3];
3481                   endtoken = star_match[4];
3482                 } else {
3483                   whitename = star_match[1];
3484                   blackname = star_match[3];
3485                   why = star_match[5];
3486                   endtoken = star_match[6];
3487                 }
3488
3489                 /* Game start messages */
3490                 if (strncmp(why, "Creating ", 9) == 0 ||
3491                     strncmp(why, "Continuing ", 11) == 0) {
3492                     gs_gamenum = gamenum;
3493                     strcpy(gs_kind, strchr(why, ' ') + 1);
3494                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3495 #if ZIPPY
3496                     if (appData.zippyPlay) {
3497                         ZippyGameStart(whitename, blackname);
3498                     }
3499 #endif /*ZIPPY*/
3500                     continue;
3501                 }
3502
3503                 /* Game end messages */
3504                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505                     ics_gamenum != gamenum) {
3506                     continue;
3507                 }
3508                 while (endtoken[0] == ' ') endtoken++;
3509                 switch (endtoken[0]) {
3510                   case '*':
3511                   default:
3512                     endtype = GameUnfinished;
3513                     break;
3514                   case '0':
3515                     endtype = BlackWins;
3516                     break;
3517                   case '1':
3518                     if (endtoken[1] == '/')
3519                       endtype = GameIsDrawn;
3520                     else
3521                       endtype = WhiteWins;
3522                     break;
3523                 }
3524                 GameEnds(endtype, why, GE_ICS);
3525 #if ZIPPY
3526                 if (appData.zippyPlay && first.initDone) {
3527                     ZippyGameEnd(endtype, why);
3528                     if (first.pr == NULL) {
3529                       /* Start the next process early so that we'll
3530                          be ready for the next challenge */
3531                       StartChessProgram(&first);
3532                     }
3533                     /* Send "new" early, in case this command takes
3534                        a long time to finish, so that we'll be ready
3535                        for the next challenge. */
3536                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3537                     Reset(TRUE, TRUE);
3538                 }
3539 #endif /*ZIPPY*/
3540                 continue;
3541             }
3542
3543             if (looking_at(buf, &i, "Removing game * from observation") ||
3544                 looking_at(buf, &i, "no longer observing game *") ||
3545                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546                 if (gameMode == IcsObserving &&
3547                     atoi(star_match[0]) == ics_gamenum)
3548                   {
3549                       /* icsEngineAnalyze */
3550                       if (appData.icsEngineAnalyze) {
3551                             ExitAnalyzeMode();
3552                             ModeHighlight();
3553                       }
3554                       StopClocks();
3555                       gameMode = IcsIdle;
3556                       ics_gamenum = -1;
3557                       ics_user_moved = FALSE;
3558                   }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "no longer examining game *")) {
3563                 if (gameMode == IcsExamining &&
3564                     atoi(star_match[0]) == ics_gamenum)
3565                   {
3566                       gameMode = IcsIdle;
3567                       ics_gamenum = -1;
3568                       ics_user_moved = FALSE;
3569                   }
3570                 continue;
3571             }
3572
3573             /* Advance leftover_start past any newlines we find,
3574                so only partial lines can get reparsed */
3575             if (looking_at(buf, &i, "\n")) {
3576                 prevColor = curColor;
3577                 if (curColor != ColorNormal) {
3578                     if (oldi > next_out) {
3579                         SendToPlayer(&buf[next_out], oldi - next_out);
3580                         next_out = oldi;
3581                     }
3582                     Colorize(ColorNormal, FALSE);
3583                     curColor = ColorNormal;
3584                 }
3585                 if (started == STARTED_BOARD) {
3586                     started = STARTED_NONE;
3587                     parse[parse_pos] = NULLCHAR;
3588                     ParseBoard12(parse);
3589                     ics_user_moved = 0;
3590
3591                     /* Send premove here */
3592                     if (appData.premove) {
3593                       char str[MSG_SIZ];
3594                       if (currentMove == 0 &&
3595                           gameMode == IcsPlayingWhite &&
3596                           appData.premoveWhite) {
3597                         sprintf(str, "%s\n", appData.premoveWhiteText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (currentMove == 1 &&
3602                                  gameMode == IcsPlayingBlack &&
3603                                  appData.premoveBlack) {
3604                         sprintf(str, "%s\n", appData.premoveBlackText);
3605                         if (appData.debugMode)
3606                           fprintf(debugFP, "Sending premove:\n");
3607                         SendToICS(str);
3608                       } else if (gotPremove) {
3609                         gotPremove = 0;
3610                         ClearPremoveHighlights();
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                           UserMoveEvent(premoveFromX, premoveFromY,
3614                                         premoveToX, premoveToY,
3615                                         premovePromoChar);
3616                       }
3617                     }
3618
3619                     /* Usually suppress following prompt */
3620                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622                         if (looking_at(buf, &i, "*% ")) {
3623                             savingComment = FALSE;
3624                             suppressKibitz = 0;
3625                         }
3626                     }
3627                     next_out = i;
3628                 } else if (started == STARTED_HOLDINGS) {
3629                     int gamenum;
3630                     char new_piece[MSG_SIZ];
3631                     started = STARTED_NONE;
3632                     parse[parse_pos] = NULLCHAR;
3633                     if (appData.debugMode)
3634                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635                                                         parse, currentMove);
3636                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637                         gamenum == ics_gamenum) {
3638                         if (gameInfo.variant == VariantNormal) {
3639                           /* [HGM] We seem to switch variant during a game!
3640                            * Presumably no holdings were displayed, so we have
3641                            * to move the position two files to the right to
3642                            * create room for them!
3643                            */
3644                           VariantClass newVariant;
3645                           switch(gameInfo.boardWidth) { // base guess on board width
3646                                 case 9:  newVariant = VariantShogi; break;
3647                                 case 10: newVariant = VariantGreat; break;
3648                                 default: newVariant = VariantCrazyhouse; break;
3649                           }
3650                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651                           /* Get a move list just to see the header, which
3652                              will tell us whether this is really bug or zh */
3653                           if (ics_getting_history == H_FALSE) {
3654                             ics_getting_history = H_REQUESTED;
3655                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656                             SendToICS(str);
3657                           }
3658                         }
3659                         new_piece[0] = NULLCHAR;
3660                         sscanf(parse, "game %d white [%s black [%s <- %s",
3661                                &gamenum, white_holding, black_holding,
3662                                new_piece);
3663                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3664                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3665                         /* [HGM] copy holdings to board holdings area */
3666                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3669 #if ZIPPY
3670                         if (appData.zippyPlay && first.initDone) {
3671                             ZippyHoldings(white_holding, black_holding,
3672                                           new_piece);
3673                         }
3674 #endif /*ZIPPY*/
3675                         if (tinyLayout || smallLayout) {
3676                             char wh[16], bh[16];
3677                             PackHolding(wh, white_holding);
3678                             PackHolding(bh, black_holding);
3679                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680                                     gameInfo.white, gameInfo.black);
3681                         } else {
3682                             sprintf(str, "%s [%s] vs. %s [%s]",
3683                                     gameInfo.white, white_holding,
3684                                     gameInfo.black, black_holding);
3685                         }
3686
3687                         DrawPosition(FALSE, boards[currentMove]);
3688                         DisplayTitle(str);
3689                     }
3690                     /* Suppress following prompt */
3691                     if (looking_at(buf, &i, "*% ")) {
3692                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693                         savingComment = FALSE;
3694                         suppressKibitz = 0;
3695                     }
3696                     next_out = i;
3697                 }
3698                 continue;
3699             }
3700
3701             i++;                /* skip unparsed character and loop back */
3702         }
3703         
3704         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 //          SendToPlayer(&buf[next_out], i - next_out);
3707             started != STARTED_HOLDINGS && leftover_start > next_out) {
3708             SendToPlayer(&buf[next_out], leftover_start - next_out);
3709             next_out = i;
3710         }
3711
3712         leftover_len = buf_len - leftover_start;
3713         /* if buffer ends with something we couldn't parse,
3714            reparse it after appending the next read */
3715
3716     } else if (count == 0) {
3717         RemoveInputSource(isr);
3718         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3719     } else {
3720         DisplayFatalError(_("Error reading from ICS"), error, 1);
3721     }
3722 }
3723
3724
3725 /* Board style 12 looks like this:
3726
3727    <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
3728
3729  * The "<12> " is stripped before it gets to this routine.  The two
3730  * trailing 0's (flip state and clock ticking) are later addition, and
3731  * some chess servers may not have them, or may have only the first.
3732  * Additional trailing fields may be added in the future.
3733  */
3734
3735 #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"
3736
3737 #define RELATION_OBSERVING_PLAYED    0
3738 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE      1
3740 #define RELATION_PLAYING_NOTMYMOVE  -1
3741 #define RELATION_EXAMINING           2
3742 #define RELATION_ISOLATED_BOARD     -3
3743 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3744
3745 void
3746 ParseBoard12(string)
3747      char *string;
3748 {
3749     GameMode newGameMode;
3750     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753     char to_play, board_chars[200];
3754     char move_str[500], str[500], elapsed_time[500];
3755     char black[32], white[32];
3756     Board board;
3757     int prevMove = currentMove;
3758     int ticking = 2;
3759     ChessMove moveType;
3760     int fromX, fromY, toX, toY;
3761     char promoChar;
3762     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763     char *bookHit = NULL; // [HGM] book
3764     Boolean weird = FALSE, reqFlag = FALSE;
3765
3766     fromX = fromY = toX = toY = -1;
3767
3768     newGame = FALSE;
3769
3770     if (appData.debugMode)
3771       fprintf(debugFP, _("Parsing board: %s\n"), string);
3772
3773     move_str[0] = NULLCHAR;
3774     elapsed_time[0] = NULLCHAR;
3775     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3776         int  i = 0, j;
3777         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778             if(string[i] == ' ') { ranks++; files = 0; }
3779             else files++;
3780             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3781             i++;
3782         }
3783         for(j = 0; j <i; j++) board_chars[j] = string[j];
3784         board_chars[i] = '\0';
3785         string += i + 1;
3786     }
3787     n = sscanf(string, PATTERN, &to_play, &double_push,
3788                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789                &gamenum, white, black, &relation, &basetime, &increment,
3790                &white_stren, &black_stren, &white_time, &black_time,
3791                &moveNum, str, elapsed_time, move_str, &ics_flip,
3792                &ticking);
3793
3794     if (n < 21) {
3795         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796         DisplayError(str, 0);
3797         return;
3798     }
3799
3800     /* Convert the move number to internal form */
3801     moveNum = (moveNum - 1) * 2;
3802     if (to_play == 'B') moveNum++;
3803     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3805                         0, 1);
3806       return;
3807     }
3808
3809     switch (relation) {
3810       case RELATION_OBSERVING_PLAYED:
3811       case RELATION_OBSERVING_STATIC:
3812         if (gamenum == -1) {
3813             /* Old ICC buglet */
3814             relation = RELATION_OBSERVING_STATIC;
3815         }
3816         newGameMode = IcsObserving;
3817         break;
3818       case RELATION_PLAYING_MYMOVE:
3819       case RELATION_PLAYING_NOTMYMOVE:
3820         newGameMode =
3821           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822             IcsPlayingWhite : IcsPlayingBlack;
3823         break;
3824       case RELATION_EXAMINING:
3825         newGameMode = IcsExamining;
3826         break;
3827       case RELATION_ISOLATED_BOARD:
3828       default:
3829         /* Just display this board.  If user was doing something else,
3830            we will forget about it until the next board comes. */
3831         newGameMode = IcsIdle;
3832         break;
3833       case RELATION_STARTING_POSITION:
3834         newGameMode = gameMode;
3835         break;
3836     }
3837
3838     /* Modify behavior for initial board display on move listing
3839        of wild games.
3840        */
3841     switch (ics_getting_history) {
3842       case H_FALSE:
3843       case H_REQUESTED:
3844         break;
3845       case H_GOT_REQ_HEADER:
3846       case H_GOT_UNREQ_HEADER:
3847         /* This is the initial position of the current game */
3848         gamenum = ics_gamenum;
3849         moveNum = 0;            /* old ICS bug workaround */
3850         if (to_play == 'B') {
3851           startedFromSetupPosition = TRUE;
3852           blackPlaysFirst = TRUE;
3853           moveNum = 1;
3854           if (forwardMostMove == 0) forwardMostMove = 1;
3855           if (backwardMostMove == 0) backwardMostMove = 1;
3856           if (currentMove == 0) currentMove = 1;
3857         }
3858         newGameMode = gameMode;
3859         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3860         break;
3861       case H_GOT_UNWANTED_HEADER:
3862         /* This is an initial board that we don't want */
3863         return;
3864       case H_GETTING_MOVES:
3865         /* Should not happen */
3866         DisplayError(_("Error gathering move list: extra board"), 0);
3867         ics_getting_history = H_FALSE;
3868         return;
3869     }
3870
3871    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3872                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873      /* [HGM] We seem to have switched variant unexpectedly
3874       * Try to guess new variant from board size
3875       */
3876           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3881           if(!weird) newVariant = VariantNormal;
3882           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883           /* Get a move list just to see the header, which
3884              will tell us whether this is really bug or zh */
3885           if (ics_getting_history == H_FALSE) {
3886             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3888             SendToICS(str);
3889           }
3890     }
3891     
3892     /* Take action if this is the first board of a new game, or of a
3893        different game than is currently being displayed.  */
3894     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895         relation == RELATION_ISOLATED_BOARD) {
3896
3897         /* Forget the old game and get the history (if any) of the new one */
3898         if (gameMode != BeginningOfGame) {
3899           Reset(TRUE, TRUE);
3900         }
3901         newGame = TRUE;
3902         if (appData.autoRaiseBoard) BoardToTop();
3903         prevMove = -3;
3904         if (gamenum == -1) {
3905             newGameMode = IcsIdle;
3906         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907                    appData.getMoveList && !reqFlag) {
3908             /* Need to get game history */
3909             ics_getting_history = H_REQUESTED;
3910             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3911             SendToICS(str);
3912         }
3913
3914         /* Initially flip the board to have black on the bottom if playing
3915            black or if the ICS flip flag is set, but let the user change
3916            it with the Flip View button. */
3917         flipView = appData.autoFlipView ?
3918           (newGameMode == IcsPlayingBlack) || ics_flip :
3919           appData.flipView;
3920
3921         /* Done with values from previous mode; copy in new ones */
3922         gameMode = newGameMode;
3923         ModeHighlight();
3924         ics_gamenum = gamenum;
3925         if (gamenum == gs_gamenum) {
3926             int klen = strlen(gs_kind);
3927             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928             sprintf(str, "ICS %s", gs_kind);
3929             gameInfo.event = StrSave(str);
3930         } else {
3931             gameInfo.event = StrSave("ICS game");
3932         }
3933         gameInfo.site = StrSave(appData.icsHost);
3934         gameInfo.date = PGNDate();
3935         gameInfo.round = StrSave("-");
3936         gameInfo.white = StrSave(white);
3937         gameInfo.black = StrSave(black);
3938         timeControl = basetime * 60 * 1000;
3939         timeControl_2 = 0;
3940         timeIncrement = increment * 1000;
3941         movesPerSession = 0;
3942         gameInfo.timeControl = TimeControlTagValue();
3943         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944   if (appData.debugMode) {
3945     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947     setbuf(debugFP, NULL);
3948   }
3949
3950         gameInfo.outOfBook = NULL;
3951
3952         /* Do we have the ratings? */
3953         if (strcmp(player1Name, white) == 0 &&
3954             strcmp(player2Name, black) == 0) {
3955             if (appData.debugMode)
3956               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957                       player1Rating, player2Rating);
3958             gameInfo.whiteRating = player1Rating;
3959             gameInfo.blackRating = player2Rating;
3960         } else if (strcmp(player2Name, white) == 0 &&
3961                    strcmp(player1Name, black) == 0) {
3962             if (appData.debugMode)
3963               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964                       player2Rating, player1Rating);
3965             gameInfo.whiteRating = player2Rating;
3966             gameInfo.blackRating = player1Rating;
3967         }
3968         player1Name[0] = player2Name[0] = NULLCHAR;
3969
3970         /* Silence shouts if requested */
3971         if (appData.quietPlay &&
3972             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973             SendToICS(ics_prefix);
3974             SendToICS("set shout 0\n");
3975         }
3976     }
3977
3978     /* Deal with midgame name changes */
3979     if (!newGame) {
3980         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981             if (gameInfo.white) free(gameInfo.white);
3982             gameInfo.white = StrSave(white);
3983         }
3984         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985             if (gameInfo.black) free(gameInfo.black);
3986             gameInfo.black = StrSave(black);
3987         }
3988     }
3989
3990     /* Throw away game result if anything actually changes in examine mode */
3991     if (gameMode == IcsExamining && !newGame) {
3992         gameInfo.result = GameUnfinished;
3993         if (gameInfo.resultDetails != NULL) {
3994             free(gameInfo.resultDetails);
3995             gameInfo.resultDetails = NULL;
3996         }
3997     }
3998
3999     /* In pausing && IcsExamining mode, we ignore boards coming
4000        in if they are in a different variation than we are. */
4001     if (pauseExamInvalid) return;
4002     if (pausing && gameMode == IcsExamining) {
4003         if (moveNum <= pauseExamForwardMostMove) {
4004             pauseExamInvalid = TRUE;
4005             forwardMostMove = pauseExamForwardMostMove;
4006             return;
4007         }
4008     }
4009
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4012   }
4013     /* Parse the board */
4014     for (k = 0; k < ranks; k++) {
4015       for (j = 0; j < files; j++)
4016         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017       if(gameInfo.holdingsWidth > 1) {
4018            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4020       }
4021     }
4022     CopyBoard(boards[moveNum], board);
4023     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4024     if (moveNum == 0) {
4025         startedFromSetupPosition =
4026           !CompareBoards(board, initialPosition);
4027         if(startedFromSetupPosition)
4028             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4029     }
4030
4031     /* [HGM] Set castling rights. Take the outermost Rooks,
4032        to make it also work for FRC opening positions. Note that board12
4033        is really defective for later FRC positions, as it has no way to
4034        indicate which Rook can castle if they are on the same side of King.
4035        For the initial position we grant rights to the outermost Rooks,
4036        and remember thos rights, and we then copy them on positions
4037        later in an FRC game. This means WB might not recognize castlings with
4038        Rooks that have moved back to their original position as illegal,
4039        but in ICS mode that is not its job anyway.
4040     */
4041     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4043
4044         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045             if(board[0][i] == WhiteRook) j = i;
4046         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048             if(board[0][i] == WhiteRook) j = i;
4049         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056
4057         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061             if(board[BOARD_HEIGHT-1][k] == bKing)
4062                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063         if(gameInfo.variant == VariantTwoKings) {
4064             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4067         }
4068     } else { int r;
4069         r = boards[moveNum][CASTLING][0] = initialRights[0];
4070         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071         r = boards[moveNum][CASTLING][1] = initialRights[1];
4072         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073         r = boards[moveNum][CASTLING][3] = initialRights[3];
4074         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075         r = boards[moveNum][CASTLING][4] = initialRights[4];
4076         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077         /* wildcastle kludge: always assume King has rights */
4078         r = boards[moveNum][CASTLING][2] = initialRights[2];
4079         r = boards[moveNum][CASTLING][5] = initialRights[5];
4080     }
4081     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4083
4084
4085     if (ics_getting_history == H_GOT_REQ_HEADER ||
4086         ics_getting_history == H_GOT_UNREQ_HEADER) {
4087         /* This was an initial position from a move list, not
4088            the current position */
4089         return;
4090     }
4091
4092     /* Update currentMove and known move number limits */
4093     newMove = newGame || moveNum > forwardMostMove;
4094
4095     if (newGame) {
4096         forwardMostMove = backwardMostMove = currentMove = moveNum;
4097         if (gameMode == IcsExamining && moveNum == 0) {
4098           /* Workaround for ICS limitation: we are not told the wild
4099              type when starting to examine a game.  But if we ask for
4100              the move list, the move list header will tell us */
4101             ics_getting_history = H_REQUESTED;
4102             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4103             SendToICS(str);
4104         }
4105     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4107 #if ZIPPY
4108         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109         /* [HGM] applied this also to an engine that is silently watching        */
4110         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112             gameInfo.variant == currentlyInitializedVariant) {
4113           takeback = forwardMostMove - moveNum;
4114           for (i = 0; i < takeback; i++) {
4115             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116             SendToProgram("undo\n", &first);
4117           }
4118         }
4119 #endif
4120
4121         forwardMostMove = moveNum;
4122         if (!pausing || currentMove > forwardMostMove)
4123           currentMove = forwardMostMove;
4124     } else {
4125         /* New part of history that is not contiguous with old part */
4126         if (pausing && gameMode == IcsExamining) {
4127             pauseExamInvalid = TRUE;
4128             forwardMostMove = pauseExamForwardMostMove;
4129             return;
4130         }
4131         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4132 #if ZIPPY
4133             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134                 // [HGM] when we will receive the move list we now request, it will be
4135                 // fed to the engine from the first move on. So if the engine is not
4136                 // in the initial position now, bring it there.
4137                 InitChessProgram(&first, 0);
4138             }
4139 #endif
4140             ics_getting_history = H_REQUESTED;
4141             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4142             SendToICS(str);
4143         }
4144         forwardMostMove = backwardMostMove = currentMove = moveNum;
4145     }
4146
4147     /* Update the clocks */
4148     if (strchr(elapsed_time, '.')) {
4149       /* Time is in ms */
4150       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4152     } else {
4153       /* Time is in seconds */
4154       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4156     }
4157
4158
4159 #if ZIPPY
4160     if (appData.zippyPlay && newGame &&
4161         gameMode != IcsObserving && gameMode != IcsIdle &&
4162         gameMode != IcsExamining)
4163       ZippyFirstBoard(moveNum, basetime, increment);
4164 #endif
4165
4166     /* Put the move on the move list, first converting
4167        to canonical algebraic form. */
4168     if (moveNum > 0) {
4169   if (appData.debugMode) {
4170     if (appData.debugMode) { int f = forwardMostMove;
4171         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4174     }
4175     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176     fprintf(debugFP, "moveNum = %d\n", moveNum);
4177     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178     setbuf(debugFP, NULL);
4179   }
4180         if (moveNum <= backwardMostMove) {
4181             /* We don't know what the board looked like before
4182                this move.  Punt. */
4183             strcpy(parseList[moveNum - 1], move_str);
4184             strcat(parseList[moveNum - 1], " ");
4185             strcat(parseList[moveNum - 1], elapsed_time);
4186             moveList[moveNum - 1][0] = NULLCHAR;
4187         } else if (strcmp(move_str, "none") == 0) {
4188             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189             /* Again, we don't know what the board looked like;
4190                this is really the start of the game. */
4191             parseList[moveNum - 1][0] = NULLCHAR;
4192             moveList[moveNum - 1][0] = NULLCHAR;
4193             backwardMostMove = moveNum;
4194             startedFromSetupPosition = TRUE;
4195             fromX = fromY = toX = toY = -1;
4196         } else {
4197           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198           //                 So we parse the long-algebraic move string in stead of the SAN move
4199           int valid; char buf[MSG_SIZ], *prom;
4200
4201           // str looks something like "Q/a1-a2"; kill the slash
4202           if(str[1] == '/')
4203                 sprintf(buf, "%c%s", str[0], str+2);
4204           else  strcpy(buf, str); // might be castling
4205           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206                 strcat(buf, prom); // long move lacks promo specification!
4207           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208                 if(appData.debugMode)
4209                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210                 strcpy(move_str, buf);
4211           }
4212           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213                                 &fromX, &fromY, &toX, &toY, &promoChar)
4214                || ParseOneMove(buf, moveNum - 1, &moveType,
4215                                 &fromX, &fromY, &toX, &toY, &promoChar);
4216           // end of long SAN patch
4217           if (valid) {
4218             (void) CoordsToAlgebraic(boards[moveNum - 1],
4219                                      PosFlags(moveNum - 1),
4220                                      fromY, fromX, toY, toX, promoChar,
4221                                      parseList[moveNum-1]);
4222             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4223               case MT_NONE:
4224               case MT_STALEMATE:
4225               default:
4226                 break;
4227               case MT_CHECK:
4228                 if(gameInfo.variant != VariantShogi)
4229                     strcat(parseList[moveNum - 1], "+");
4230                 break;
4231               case MT_CHECKMATE:
4232               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233                 strcat(parseList[moveNum - 1], "#");
4234                 break;
4235             }
4236             strcat(parseList[moveNum - 1], " ");
4237             strcat(parseList[moveNum - 1], elapsed_time);
4238             /* currentMoveString is set as a side-effect of ParseOneMove */
4239             strcpy(moveList[moveNum - 1], currentMoveString);
4240             strcat(moveList[moveNum - 1], "\n");
4241           } else {
4242             /* Move from ICS was illegal!?  Punt. */
4243   if (appData.debugMode) {
4244     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4246   }
4247             strcpy(parseList[moveNum - 1], move_str);
4248             strcat(parseList[moveNum - 1], " ");
4249             strcat(parseList[moveNum - 1], elapsed_time);
4250             moveList[moveNum - 1][0] = NULLCHAR;
4251             fromX = fromY = toX = toY = -1;
4252           }
4253         }
4254   if (appData.debugMode) {
4255     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256     setbuf(debugFP, NULL);
4257   }
4258
4259 #if ZIPPY
4260         /* Send move to chess program (BEFORE animating it). */
4261         if (appData.zippyPlay && !newGame && newMove &&
4262            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4263
4264             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4268                             move_str);
4269                     DisplayError(str, 0);
4270                 } else {
4271                     if (first.sendTime) {
4272                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4273                     }
4274                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275                     if (firstMove && !bookHit) {
4276                         firstMove = FALSE;
4277                         if (first.useColors) {
4278                           SendToProgram(gameMode == IcsPlayingWhite ?
4279                                         "white\ngo\n" :
4280                                         "black\ngo\n", &first);
4281                         } else {
4282                           SendToProgram("go\n", &first);
4283                         }
4284                         first.maybeThinking = TRUE;
4285                     }
4286                 }
4287             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288               if (moveList[moveNum - 1][0] == NULLCHAR) {
4289                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290                 DisplayError(str, 0);
4291               } else {
4292                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293                 SendMoveToProgram(moveNum - 1, &first);
4294               }
4295             }
4296         }
4297 #endif
4298     }
4299
4300     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301         /* If move comes from a remote source, animate it.  If it
4302            isn't remote, it will have already been animated. */
4303         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4305         }
4306         if (!pausing && appData.highlightLastMove) {
4307             SetHighlights(fromX, fromY, toX, toY);
4308         }
4309     }
4310
4311     /* Start the clocks */
4312     whiteFlag = blackFlag = FALSE;
4313     appData.clockMode = !(basetime == 0 && increment == 0);
4314     if (ticking == 0) {
4315       ics_clock_paused = TRUE;
4316       StopClocks();
4317     } else if (ticking == 1) {
4318       ics_clock_paused = FALSE;
4319     }
4320     if (gameMode == IcsIdle ||
4321         relation == RELATION_OBSERVING_STATIC ||
4322         relation == RELATION_EXAMINING ||
4323         ics_clock_paused)
4324       DisplayBothClocks();
4325     else
4326       StartClocks();
4327
4328     /* Display opponents and material strengths */
4329     if (gameInfo.variant != VariantBughouse &&
4330         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331         if (tinyLayout || smallLayout) {
4332             if(gameInfo.variant == VariantNormal)
4333                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4335                     basetime, increment);
4336             else
4337                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment, (int) gameInfo.variant);
4340         } else {
4341             if(gameInfo.variant == VariantNormal)
4342                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment);
4345             else
4346                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4348                     basetime, increment, VariantName(gameInfo.variant));
4349         }
4350         DisplayTitle(str);
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4353   }
4354     }
4355
4356
4357     /* Display the board */
4358     if (!pausing && !appData.noGUI) {
4359       if (appData.premove)
4360           if (!gotPremove ||
4361              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363               ClearPremoveHighlights();
4364
4365       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366       DrawPosition(j, boards[currentMove]);
4367
4368       DisplayMove(moveNum - 1);
4369       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4372         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4373       }
4374     }
4375
4376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4377 #if ZIPPY
4378     if(bookHit) { // [HGM] book: simulate book reply
4379         static char bookMove[MSG_SIZ]; // a bit generous?
4380
4381         programStats.nodes = programStats.depth = programStats.time =
4382         programStats.score = programStats.got_only_move = 0;
4383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4384
4385         strcpy(bookMove, "move ");
4386         strcat(bookMove, bookHit);
4387         HandleMachineMove(bookMove, &first);
4388     }
4389 #endif
4390 }
4391
4392 void
4393 GetMoveListEvent()
4394 {
4395     char buf[MSG_SIZ];
4396     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397         ics_getting_history = H_REQUESTED;
4398         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4399         SendToICS(buf);
4400     }
4401 }
4402
4403 void
4404 AnalysisPeriodicEvent(force)
4405      int force;
4406 {
4407     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408          && !force) || !appData.periodicUpdates)
4409       return;
4410
4411     /* Send . command to Crafty to collect stats */
4412     SendToProgram(".\n", &first);
4413
4414     /* Don't send another until we get a response (this makes
4415        us stop sending to old Crafty's which don't understand
4416        the "." command (sending illegal cmds resets node count & time,
4417        which looks bad)) */
4418     programStats.ok_to_send = 0;
4419 }
4420
4421 void ics_update_width(new_width)
4422         int new_width;
4423 {
4424         ics_printf("set width %d\n", new_width);
4425 }
4426
4427 void
4428 SendMoveToProgram(moveNum, cps)
4429      int moveNum;
4430      ChessProgramState *cps;
4431 {
4432     char buf[MSG_SIZ];
4433
4434     if (cps->useUsermove) {
4435       SendToProgram("usermove ", cps);
4436     }
4437     if (cps->useSAN) {
4438       char *space;
4439       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440         int len = space - parseList[moveNum];
4441         memcpy(buf, parseList[moveNum], len);
4442         buf[len++] = '\n';
4443         buf[len] = NULLCHAR;
4444       } else {
4445         sprintf(buf, "%s\n", parseList[moveNum]);
4446       }
4447       SendToProgram(buf, cps);
4448     } else {
4449       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450         AlphaRank(moveList[moveNum], 4);
4451         SendToProgram(moveList[moveNum], cps);
4452         AlphaRank(moveList[moveNum], 4); // and back
4453       } else
4454       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455        * the engine. It would be nice to have a better way to identify castle
4456        * moves here. */
4457       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458                                                                          && cps->useOOCastle) {
4459         int fromX = moveList[moveNum][0] - AAA;
4460         int fromY = moveList[moveNum][1] - ONE;
4461         int toX = moveList[moveNum][2] - AAA;
4462         int toY = moveList[moveNum][3] - ONE;
4463         if((boards[moveNum][fromY][fromX] == WhiteKing
4464             && boards[moveNum][toY][toX] == WhiteRook)
4465            || (boards[moveNum][fromY][fromX] == BlackKing
4466                && boards[moveNum][toY][toX] == BlackRook)) {
4467           if(toX > fromX) SendToProgram("O-O\n", cps);
4468           else SendToProgram("O-O-O\n", cps);
4469         }
4470         else SendToProgram(moveList[moveNum], cps);
4471       }
4472       else SendToProgram(moveList[moveNum], cps);
4473       /* End of additions by Tord */
4474     }
4475
4476     /* [HGM] setting up the opening has brought engine in force mode! */
4477     /*       Send 'go' if we are in a mode where machine should play. */
4478     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479         (gameMode == TwoMachinesPlay   ||
4480 #ifdef ZIPPY
4481          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4482 #endif
4483          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484         SendToProgram("go\n", cps);
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "(extra)\n");
4487   }
4488     }
4489     setboardSpoiledMachineBlack = 0;
4490 }
4491
4492 void
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4494      ChessMove moveType;
4495      int fromX, fromY, toX, toY;
4496 {
4497     char user_move[MSG_SIZ];
4498
4499     switch (moveType) {
4500       default:
4501         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502                 (int)moveType, fromX, fromY, toX, toY);
4503         DisplayError(user_move + strlen("say "), 0);
4504         break;
4505       case WhiteKingSideCastle:
4506       case BlackKingSideCastle:
4507       case WhiteQueenSideCastleWild:
4508       case BlackQueenSideCastleWild:
4509       /* PUSH Fabien */
4510       case WhiteHSideCastleFR:
4511       case BlackHSideCastleFR:
4512       /* POP Fabien */
4513         sprintf(user_move, "o-o\n");
4514         break;
4515       case WhiteQueenSideCastle:
4516       case BlackQueenSideCastle:
4517       case WhiteKingSideCastleWild:
4518       case BlackKingSideCastleWild:
4519       /* PUSH Fabien */
4520       case WhiteASideCastleFR:
4521       case BlackASideCastleFR:
4522       /* POP Fabien */
4523         sprintf(user_move, "o-o-o\n");
4524         break;
4525       case WhitePromotionQueen:
4526       case BlackPromotionQueen:
4527       case WhitePromotionRook:
4528       case BlackPromotionRook:
4529       case WhitePromotionBishop:
4530       case BlackPromotionBishop:
4531       case WhitePromotionKnight:
4532       case BlackPromotionKnight:
4533       case WhitePromotionKing:
4534       case BlackPromotionKing:
4535       case WhitePromotionChancellor:
4536       case BlackPromotionChancellor:
4537       case WhitePromotionArchbishop:
4538       case BlackPromotionArchbishop:
4539         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteFerz));
4543         else if(gameInfo.variant == VariantGreat)
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(WhiteMan));
4547         else
4548             sprintf(user_move, "%c%c%c%c=%c\n",
4549                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550                 PieceToChar(PromoPiece(moveType)));
4551         break;
4552       case WhiteDrop:
4553       case BlackDrop:
4554         sprintf(user_move, "%c@%c%c\n",
4555                 ToUpper(PieceToChar((ChessSquare) fromX)),
4556                 AAA + toX, ONE + toY);
4557         break;
4558       case NormalMove:
4559       case WhiteCapturesEnPassant:
4560       case BlackCapturesEnPassant:
4561       case IllegalMove:  /* could be a variant we don't quite understand */
4562         sprintf(user_move, "%c%c%c%c\n",
4563                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4564         break;
4565     }
4566     SendToICS(user_move);
4567     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4569 }
4570
4571 void
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4573      int rf, ff, rt, ft;
4574      char promoChar;
4575      char move[7];
4576 {
4577     if (rf == DROP_RANK) {
4578         sprintf(move, "%c@%c%c\n",
4579                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4580     } else {
4581         if (promoChar == 'x' || promoChar == NULLCHAR) {
4582             sprintf(move, "%c%c%c%c\n",
4583                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4584         } else {
4585             sprintf(move, "%c%c%c%c%c\n",
4586                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4587         }
4588     }
4589 }
4590
4591 void
4592 ProcessICSInitScript(f)
4593      FILE *f;
4594 {
4595     char buf[MSG_SIZ];
4596
4597     while (fgets(buf, MSG_SIZ, f)) {
4598         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4599     }
4600
4601     fclose(f);
4602 }
4603
4604
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4606 void
4607 AlphaRank(char *move, int n)
4608 {
4609 //    char *p = move, c; int x, y;
4610
4611     if (appData.debugMode) {
4612         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4613     }
4614
4615     if(move[1]=='*' &&
4616        move[2]>='0' && move[2]<='9' &&
4617        move[3]>='a' && move[3]<='x'    ) {
4618         move[1] = '@';
4619         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4620         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4621     } else
4622     if(move[0]>='0' && move[0]<='9' &&
4623        move[1]>='a' && move[1]<='x' &&
4624        move[2]>='0' && move[2]<='9' &&
4625        move[3]>='a' && move[3]<='x'    ) {
4626         /* input move, Shogi -> normal */
4627         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4628         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4630         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4631     } else
4632     if(move[1]=='@' &&
4633        move[3]>='0' && move[3]<='9' &&
4634        move[2]>='a' && move[2]<='x'    ) {
4635         move[1] = '*';
4636         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4638     } else
4639     if(
4640        move[0]>='a' && move[0]<='x' &&
4641        move[3]>='0' && move[3]<='9' &&
4642        move[2]>='a' && move[2]<='x'    ) {
4643          /* output move, normal -> Shogi */
4644         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4649     }
4650     if (appData.debugMode) {
4651         fprintf(debugFP, "   out = '%s'\n", move);
4652     }
4653 }
4654
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4656 Boolean
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4658      char *move;
4659      int moveNum;
4660      ChessMove *moveType;
4661      int *fromX, *fromY, *toX, *toY;
4662      char *promoChar;
4663 {
4664     if (appData.debugMode) {
4665         fprintf(debugFP, "move to parse: %s\n", move);
4666     }
4667     *moveType = yylexstr(moveNum, move);
4668
4669     switch (*moveType) {
4670       case WhitePromotionChancellor:
4671       case BlackPromotionChancellor:
4672       case WhitePromotionArchbishop:
4673       case BlackPromotionArchbishop:
4674       case WhitePromotionQueen:
4675       case BlackPromotionQueen:
4676       case WhitePromotionRook:
4677       case BlackPromotionRook:
4678       case WhitePromotionBishop:
4679       case BlackPromotionBishop:
4680       case WhitePromotionKnight:
4681       case BlackPromotionKnight:
4682       case WhitePromotionKing:
4683       case BlackPromotionKing:
4684       case NormalMove:
4685       case WhiteCapturesEnPassant:
4686       case BlackCapturesEnPassant:
4687       case WhiteKingSideCastle:
4688       case WhiteQueenSideCastle:
4689       case BlackKingSideCastle:
4690       case BlackQueenSideCastle:
4691       case WhiteKingSideCastleWild:
4692       case WhiteQueenSideCastleWild:
4693       case BlackKingSideCastleWild:
4694       case BlackQueenSideCastleWild:
4695       /* Code added by Tord: */
4696       case WhiteHSideCastleFR:
4697       case WhiteASideCastleFR:
4698       case BlackHSideCastleFR:
4699       case BlackASideCastleFR:
4700       /* End of code added by Tord */
4701       case IllegalMove:         /* bug or odd chess variant */
4702         *fromX = currentMoveString[0] - AAA;
4703         *fromY = currentMoveString[1] - ONE;
4704         *toX = currentMoveString[2] - AAA;
4705         *toY = currentMoveString[3] - ONE;
4706         *promoChar = currentMoveString[4];
4707         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709     if (appData.debugMode) {
4710         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4711     }
4712             *fromX = *fromY = *toX = *toY = 0;
4713             return FALSE;
4714         }
4715         if (appData.testLegality) {
4716           return (*moveType != IllegalMove);
4717         } else {
4718           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4719                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4720         }
4721
4722       case WhiteDrop:
4723       case BlackDrop:
4724         *fromX = *moveType == WhiteDrop ?
4725           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726           (int) CharToPiece(ToLower(currentMoveString[0]));
4727         *fromY = DROP_RANK;
4728         *toX = currentMoveString[2] - AAA;
4729         *toY = currentMoveString[3] - ONE;
4730         *promoChar = NULLCHAR;
4731         return TRUE;
4732
4733       case AmbiguousMove:
4734       case ImpossibleMove:
4735       case (ChessMove) 0:       /* end of file */
4736       case ElapsedTime:
4737       case Comment:
4738       case PGNTag:
4739       case NAG:
4740       case WhiteWins:
4741       case BlackWins:
4742       case GameIsDrawn:
4743       default:
4744     if (appData.debugMode) {
4745         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4746     }
4747         /* bug? */
4748         *fromX = *fromY = *toX = *toY = 0;
4749         *promoChar = NULLCHAR;
4750         return FALSE;
4751     }
4752 }
4753
4754
4755 void
4756 ParsePV(char *pv)
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758   int fromX, fromY, toX, toY; char promoChar;
4759   ChessMove moveType;
4760   Boolean valid;
4761   int nr = 0;
4762
4763   endPV = forwardMostMove;
4764   do {
4765     while(*pv == ' ') pv++;
4766     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4770 }
4771     if(!valid && nr == 0 &&
4772        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4773         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4774     }
4775     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4777     nr++;
4778     if(endPV+1 > framePtr) break; // no space, truncate
4779     if(!valid) break;
4780     endPV++;
4781     CopyBoard(boards[endPV], boards[endPV-1]);
4782     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783     moveList[endPV-1][0] = fromX + AAA;
4784     moveList[endPV-1][1] = fromY + ONE;
4785     moveList[endPV-1][2] = toX + AAA;
4786     moveList[endPV-1][3] = toY + ONE;
4787     parseList[endPV-1][0] = NULLCHAR;
4788   } while(valid);
4789   currentMove = endPV;
4790   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793   DrawPosition(TRUE, boards[currentMove]);
4794 }
4795
4796 static int lastX, lastY;
4797
4798 Boolean
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4800 {
4801         int startPV;
4802
4803         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804         lastX = x; lastY = y;
4805         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4806         startPV = index;
4807       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4808       index = startPV;
4809         while(buf[index] && buf[index] != '\n') index++;
4810         buf[index] = 0;
4811         ParsePV(buf+startPV);
4812         *start = startPV; *end = index-1;
4813         return TRUE;
4814 }
4815
4816 Boolean
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820   lastX = x; lastY = y;
4821   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4822   return TRUE;
4823 }
4824
4825 void
4826 UnLoadPV()
4827 {
4828   if(endPV < 0) return;
4829   endPV = -1;
4830   currentMove = forwardMostMove;
4831   ClearPremoveHighlights();
4832   DrawPosition(TRUE, boards[currentMove]);
4833 }
4834
4835 void
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838   int margin = h>>3, step = 0;
4839
4840   if(endPV < 0) return;
4841   // we must somehow check if right button is still down (might be released off board!)
4842   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4845   if(!step) return;
4846   lastX = x; lastY = y;
4847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848   currentMove += step;
4849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852   DrawPosition(FALSE, boards[currentMove]);
4853 }
4854
4855
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4859 #define DARK 1
4860 #define LITE 2
4861 #define ANY 3
4862
4863 int squaresLeft[4];
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4866
4867 void GetPositionNumber()
4868 {       // sets global variable seed
4869         int i;
4870
4871         seed = appData.defaultFrcPosition;
4872         if(seed < 0) { // randomize based on time for negative FRC position numbers
4873                 for(i=0; i<50; i++) seed += random();
4874                 seed = random() ^ random() >> 8 ^ random() << 8;
4875                 if(seed<0) seed = -seed;
4876         }
4877 }
4878
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4881 {
4882         int i;
4883
4884         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886                         board[rank][i] = (ChessSquare) pieceType;
4887                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4888                         squaresLeft[ANY]--;
4889                         piecesLeft[pieceType]--;
4890                         return i;
4891                 }
4892         }
4893         return -1;
4894 }
4895
4896
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4899 {
4900         int i;
4901
4902         i = seed % squaresLeft[shade];
4903         nrOfShuffles *= squaresLeft[shade];
4904         seed /= squaresLeft[shade];
4905         put(board, pieceType, rank, i, shade);
4906 }
4907
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4910 {
4911         int i, n=squaresLeft[ANY], j=n-1, k;
4912
4913         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914         i = seed % k;  // pick one
4915         nrOfShuffles *= k;
4916         seed /= k;
4917         while(i >= j) i -= j--;
4918         j = n - 1 - j; i += j;
4919         put(board, pieceType, rank, j, ANY);
4920         put(board, pieceType, rank, i, ANY);
4921 }
4922
4923 void SetUpShuffle(Board board, int number)
4924 {
4925         int i, p, first=1;
4926
4927         GetPositionNumber(); nrOfShuffles = 1;
4928
4929         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4931         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4932
4933         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4934
4935         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936             p = (int) board[0][i];
4937             if(p < (int) BlackPawn) piecesLeft[p] ++;
4938             board[0][i] = EmptySquare;
4939         }
4940
4941         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942             // shuffles restricted to allow normal castling put KRR first
4943             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950                 put(board, WhiteRook, 0, 0, ANY);
4951             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4952         }
4953
4954         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958                 while(piecesLeft[p] >= 2) {
4959                     AddOnePiece(board, p, 0, LITE);
4960                     AddOnePiece(board, p, 0, DARK);
4961                 }
4962                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4963             }
4964
4965         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967             // but we leave King and Rooks for last, to possibly obey FRC restriction
4968             if(p == (int)WhiteRook) continue;
4969             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4971         }
4972
4973         // now everything is placed, except perhaps King (Unicorn) and Rooks
4974
4975         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976             // Last King gets castling rights
4977             while(piecesLeft[(int)WhiteUnicorn]) {
4978                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4980             }
4981
4982             while(piecesLeft[(int)WhiteKing]) {
4983                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4985             }
4986
4987
4988         } else {
4989             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4990             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4991         }
4992
4993         // Only Rooks can be left; simply place them all
4994         while(piecesLeft[(int)WhiteRook]) {
4995                 i = put(board, WhiteRook, 0, 0, ANY);
4996                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4997                         if(first) {
4998                                 first=0;
4999                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5000                         }
5001                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5002                 }
5003         }
5004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5006         }
5007
5008         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5009 }
5010
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /*       Basically a safe strcpy that uses the last character as King */
5014 {
5015     int result = FALSE; int NrPieces;
5016
5017     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018                     && NrPieces >= 12 && !(NrPieces&1)) {
5019         int i; /* [HGM] Accept even length from 12 to 34 */
5020
5021         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022         for( i=0; i<NrPieces/2-1; i++ ) {
5023             table[i] = map[i];
5024             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5025         }
5026         table[(int) WhiteKing]  = map[NrPieces/2-1];
5027         table[(int) BlackKing]  = map[NrPieces-1];
5028
5029         result = TRUE;
5030     }
5031
5032     return result;
5033 }
5034
5035 void Prelude(Board board)
5036 {       // [HGM] superchess: random selection of exo-pieces
5037         int i, j, k; ChessSquare p;
5038         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5039
5040         GetPositionNumber(); // use FRC position number
5041
5042         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043             SetCharTable(pieceToChar, appData.pieceToCharTable);
5044             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5046         }
5047
5048         j = seed%4;                 seed /= 4;
5049         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5051         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5052         j = seed%3 + (seed%3 >= j); seed /= 3;
5053         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5055         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5056         j = seed%3;                 seed /= 3;
5057         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5059         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5060         j = seed%2 + (seed%2 >= j); seed /= 2;
5061         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5063         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5064         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5065         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5066         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067         put(board, exoPieces[0],    0, 0, ANY);
5068         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5069 }
5070
5071 void
5072 InitPosition(redraw)
5073      int redraw;
5074 {
5075     ChessSquare (* pieces)[BOARD_FILES];
5076     int i, j, pawnRow, overrule,
5077     oldx = gameInfo.boardWidth,
5078     oldy = gameInfo.boardHeight,
5079     oldh = gameInfo.holdingsWidth,
5080     oldv = gameInfo.variant;
5081
5082     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5083
5084     /* [AS] Initialize pv info list [HGM] and game status */
5085     {
5086         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087             pvInfoList[i].depth = 0;
5088             boards[i][EP_STATUS] = EP_NONE;
5089             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5090         }
5091
5092         initialRulePlies = 0; /* 50-move counter start */
5093
5094         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5096     }
5097
5098
5099     /* [HGM] logic here is completely changed. In stead of full positions */
5100     /* the initialized data only consist of the two backranks. The switch */
5101     /* selects which one we will use, which is than copied to the Board   */
5102     /* initialPosition, which for the rest is initialized by Pawns and    */
5103     /* empty squares. This initial position is then copied to boards[0],  */
5104     /* possibly after shuffling, so that it remains available.            */
5105
5106     gameInfo.holdingsWidth = 0; /* default board sizes */
5107     gameInfo.boardWidth    = 8;
5108     gameInfo.boardHeight   = 8;
5109     gameInfo.holdingsSize  = 0;
5110     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111     for(i=0; i<BOARD_FILES-2; i++)
5112       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113     initialPosition[EP_STATUS] = EP_NONE;
5114     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5115
5116     switch (gameInfo.variant) {
5117     case VariantFischeRandom:
5118       shuffleOpenings = TRUE;
5119     default:
5120       pieces = FIDEArray;
5121       break;
5122     case VariantShatranj:
5123       pieces = ShatranjArray;
5124       nrCastlingRights = 0;
5125       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5126       break;
5127     case VariantMakruk:
5128       pieces = makrukArray;
5129       nrCastlingRights = 0;
5130       startedFromSetupPosition = TRUE;
5131       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5132       break;
5133     case VariantTwoKings:
5134       pieces = twoKingsArray;
5135       break;
5136     case VariantCapaRandom:
5137       shuffleOpenings = TRUE;
5138     case VariantCapablanca:
5139       pieces = CapablancaArray;
5140       gameInfo.boardWidth = 10;
5141       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5142       break;
5143     case VariantGothic:
5144       pieces = GothicArray;
5145       gameInfo.boardWidth = 10;
5146       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5147       break;
5148     case VariantJanus:
5149       pieces = JanusArray;
5150       gameInfo.boardWidth = 10;
5151       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152       nrCastlingRights = 6;
5153         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5159       break;
5160     case VariantFalcon:
5161       pieces = FalconArray;
5162       gameInfo.boardWidth = 10;
5163       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5164       break;
5165     case VariantXiangqi:
5166       pieces = XiangqiArray;
5167       gameInfo.boardWidth  = 9;
5168       gameInfo.boardHeight = 10;
5169       nrCastlingRights = 0;
5170       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5171       break;
5172     case VariantShogi:
5173       pieces = ShogiArray;
5174       gameInfo.boardWidth  = 9;
5175       gameInfo.boardHeight = 9;
5176       gameInfo.holdingsSize = 7;
5177       nrCastlingRights = 0;
5178       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5179       break;
5180     case VariantCourier:
5181       pieces = CourierArray;
5182       gameInfo.boardWidth  = 12;
5183       nrCastlingRights = 0;
5184       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5185       break;
5186     case VariantKnightmate:
5187       pieces = KnightmateArray;
5188       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5189       break;
5190     case VariantFairy:
5191       pieces = fairyArray;
5192       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5193       break;
5194     case VariantGreat:
5195       pieces = GreatArray;
5196       gameInfo.boardWidth = 10;
5197       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198       gameInfo.holdingsSize = 8;
5199       break;
5200     case VariantSuper:
5201       pieces = FIDEArray;
5202       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203       gameInfo.holdingsSize = 8;
5204       startedFromSetupPosition = TRUE;
5205       break;
5206     case VariantCrazyhouse:
5207     case VariantBughouse:
5208       pieces = FIDEArray;
5209       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210       gameInfo.holdingsSize = 5;
5211       break;
5212     case VariantWildCastle:
5213       pieces = FIDEArray;
5214       /* !!?shuffle with kings guaranteed to be on d or e file */
5215       shuffleOpenings = 1;
5216       break;
5217     case VariantNoCastle:
5218       pieces = FIDEArray;
5219       nrCastlingRights = 0;
5220       /* !!?unconstrained back-rank shuffle */
5221       shuffleOpenings = 1;
5222       break;
5223     }
5224
5225     overrule = 0;
5226     if(appData.NrFiles >= 0) {
5227         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228         gameInfo.boardWidth = appData.NrFiles;
5229     }
5230     if(appData.NrRanks >= 0) {
5231         gameInfo.boardHeight = appData.NrRanks;
5232     }
5233     if(appData.holdingsSize >= 0) {
5234         i = appData.holdingsSize;
5235         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236         gameInfo.holdingsSize = i;
5237     }
5238     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5241
5242     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243     if(pawnRow < 1) pawnRow = 1;
5244     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5245
5246     /* User pieceToChar list overrules defaults */
5247     if(appData.pieceToCharTable != NULL)
5248         SetCharTable(pieceToChar, appData.pieceToCharTable);
5249
5250     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5251
5252         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253             s = (ChessSquare) 0; /* account holding counts in guard band */
5254         for( i=0; i<BOARD_HEIGHT; i++ )
5255             initialPosition[i][j] = s;
5256
5257         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259         initialPosition[pawnRow][j] = WhitePawn;
5260         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261         if(gameInfo.variant == VariantXiangqi) {
5262             if(j&1) {
5263                 initialPosition[pawnRow][j] =
5264                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266                    initialPosition[2][j] = WhiteCannon;
5267                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5268                 }
5269             }
5270         }
5271         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5272     }
5273     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5274
5275             j=BOARD_LEFT+1;
5276             initialPosition[1][j] = WhiteBishop;
5277             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5278             j=BOARD_RGHT-2;
5279             initialPosition[1][j] = WhiteRook;
5280             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5281     }
5282
5283     if( nrCastlingRights == -1) {
5284         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285         /*       This sets default castling rights from none to normal corners   */
5286         /* Variants with other castling rights must set them themselves above    */
5287         nrCastlingRights = 6;
5288         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5294      }
5295
5296      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297      if(gameInfo.variant == VariantGreat) { // promotion commoners
5298         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5302      }
5303   if (appData.debugMode) {
5304     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5305   }
5306     if(shuffleOpenings) {
5307         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308         startedFromSetupPosition = TRUE;
5309     }
5310     if(startedFromPositionFile) {
5311       /* [HGM] loadPos: use PositionFile for every new game */
5312       CopyBoard(initialPosition, filePosition);
5313       for(i=0; i<nrCastlingRights; i++)
5314           initialRights[i] = filePosition[CASTLING][i];
5315       startedFromSetupPosition = TRUE;
5316     }
5317
5318     CopyBoard(boards[0], initialPosition);
5319     if(oldx != gameInfo.boardWidth ||
5320        oldy != gameInfo.boardHeight ||
5321        oldh != gameInfo.holdingsWidth
5322 #ifdef GOTHIC
5323        || oldv == VariantGothic ||        // For licensing popups
5324        gameInfo.variant == VariantGothic
5325 #endif
5326 #ifdef FALCON
5327        || oldv == VariantFalcon ||
5328        gameInfo.variant == VariantFalcon
5329 #endif
5330                                          )
5331       {
5332             InitDrawingSizes(-2 ,0);
5333       }
5334
5335     if (redraw)
5336       DrawPosition(TRUE, boards[currentMove]);
5337
5338 }
5339
5340 void
5341 SendBoard(cps, moveNum)
5342      ChessProgramState *cps;
5343      int moveNum;
5344 {
5345     char message[MSG_SIZ];
5346
5347     if (cps->useSetboard) {
5348       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349       sprintf(message, "setboard %s\n", fen);
5350       SendToProgram(message, cps);
5351       free(fen);
5352
5353     } else {
5354       ChessSquare *bp;
5355       int i, j;
5356       /* Kludge to set black to move, avoiding the troublesome and now
5357        * deprecated "black" command.
5358        */
5359       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5360
5361       SendToProgram("edit\n", cps);
5362       SendToProgram("#\n", cps);
5363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364         bp = &boards[moveNum][i][BOARD_LEFT];
5365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366           if ((int) *bp < (int) BlackPawn) {
5367             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5368                     AAA + j, ONE + i);
5369             if(message[0] == '+' || message[0] == '~') {
5370                 sprintf(message, "%c%c%c+\n",
5371                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5372                         AAA + j, ONE + i);
5373             }
5374             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375                 message[1] = BOARD_RGHT   - 1 - j + '1';
5376                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5377             }
5378             SendToProgram(message, cps);
5379           }
5380         }
5381       }
5382
5383       SendToProgram("c\n", cps);
5384       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385         bp = &boards[moveNum][i][BOARD_LEFT];
5386         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387           if (((int) *bp != (int) EmptySquare)
5388               && ((int) *bp >= (int) BlackPawn)) {
5389             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5390                     AAA + j, ONE + i);
5391             if(message[0] == '+' || message[0] == '~') {
5392                 sprintf(message, "%c%c%c+\n",
5393                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5394                         AAA + j, ONE + i);
5395             }
5396             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397                 message[1] = BOARD_RGHT   - 1 - j + '1';
5398                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5399             }
5400             SendToProgram(message, cps);
5401           }
5402         }
5403       }
5404
5405       SendToProgram(".\n", cps);
5406     }
5407     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5408 }
5409
5410 int
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5412 {
5413     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414     /* [HGM] add Shogi promotions */
5415     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5416     ChessSquare piece;
5417     ChessMove moveType;
5418     Boolean premove;
5419
5420     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5422
5423     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5425         return FALSE;
5426
5427     piece = boards[currentMove][fromY][fromX];
5428     if(gameInfo.variant == VariantShogi) {
5429         promotionZoneSize = 3;
5430         highestPromotingPiece = (int)WhiteFerz;
5431     } else if(gameInfo.variant == VariantMakruk) {
5432         promotionZoneSize = 3;
5433     }
5434
5435     // next weed out all moves that do not touch the promotion zone at all
5436     if((int)piece >= BlackPawn) {
5437         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5438              return FALSE;
5439         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5440     } else {
5441         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5442            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5443     }
5444
5445     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5446
5447     // weed out mandatory Shogi promotions
5448     if(gameInfo.variant == VariantShogi) {
5449         if(piece >= BlackPawn) {
5450             if(toY == 0 && piece == BlackPawn ||
5451                toY == 0 && piece == BlackQueen ||
5452                toY <= 1 && piece == BlackKnight) {
5453                 *promoChoice = '+';
5454                 return FALSE;
5455             }
5456         } else {
5457             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5460                 *promoChoice = '+';
5461                 return FALSE;
5462             }
5463         }
5464     }
5465
5466     // weed out obviously illegal Pawn moves
5467     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5468         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472         // note we are not allowed to test for valid (non-)capture, due to premove
5473     }
5474
5475     // we either have a choice what to promote to, or (in Shogi) whether to promote
5476     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477         *promoChoice = PieceToChar(BlackFerz);  // no choice
5478         return FALSE;
5479     }
5480     if(appData.alwaysPromoteToQueen) { // predetermined
5481         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483         else *promoChoice = PieceToChar(BlackQueen);
5484         return FALSE;
5485     }
5486
5487     // suppress promotion popup on illegal moves that are not premoves
5488     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5490     if(appData.testLegality && !premove) {
5491         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492                         fromY, fromX, toY, toX, NULLCHAR);
5493         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5494            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5495             return FALSE;
5496     }
5497
5498     return TRUE;
5499 }
5500
5501 int
5502 InPalace(row, column)
5503      int row, column;
5504 {   /* [HGM] for Xiangqi */
5505     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506          column < (BOARD_WIDTH + 4)/2 &&
5507          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5508     return FALSE;
5509 }
5510
5511 int
5512 PieceForSquare (x, y)
5513      int x;
5514      int y;
5515 {
5516   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5517      return -1;
5518   else
5519      return boards[currentMove][y][x];
5520 }
5521
5522 int
5523 OKToStartUserMove(x, y)
5524      int x, y;
5525 {
5526     ChessSquare from_piece;
5527     int white_piece;
5528
5529     if (matchMode) return FALSE;
5530     if (gameMode == EditPosition) return TRUE;
5531
5532     if (x >= 0 && y >= 0)
5533       from_piece = boards[currentMove][y][x];
5534     else
5535       from_piece = EmptySquare;
5536
5537     if (from_piece == EmptySquare) return FALSE;
5538
5539     white_piece = (int)from_piece >= (int)WhitePawn &&
5540       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5541
5542     switch (gameMode) {
5543       case PlayFromGameFile:
5544       case AnalyzeFile:
5545       case TwoMachinesPlay:
5546       case EndOfGame:
5547         return FALSE;
5548
5549       case IcsObserving:
5550       case IcsIdle:
5551         return FALSE;
5552
5553       case MachinePlaysWhite:
5554       case IcsPlayingBlack:
5555         if (appData.zippyPlay) return FALSE;
5556         if (white_piece) {
5557             DisplayMoveError(_("You are playing Black"));
5558             return FALSE;
5559         }
5560         break;
5561
5562       case MachinePlaysBlack:
5563       case IcsPlayingWhite:
5564         if (appData.zippyPlay) return FALSE;
5565         if (!white_piece) {
5566             DisplayMoveError(_("You are playing White"));
5567             return FALSE;
5568         }
5569         break;
5570
5571       case EditGame:
5572         if (!white_piece && WhiteOnMove(currentMove)) {
5573             DisplayMoveError(_("It is White's turn"));
5574             return FALSE;
5575         }
5576         if (white_piece && !WhiteOnMove(currentMove)) {
5577             DisplayMoveError(_("It is Black's turn"));
5578             return FALSE;
5579         }
5580         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581             /* Editing correspondence game history */
5582             /* Could disallow this or prompt for confirmation */
5583             cmailOldMove = -1;
5584         }
5585         break;
5586
5587       case BeginningOfGame:
5588         if (appData.icsActive) return FALSE;
5589         if (!appData.noChessProgram) {
5590             if (!white_piece) {
5591                 DisplayMoveError(_("You are playing White"));
5592                 return FALSE;
5593             }
5594         }
5595         break;
5596
5597       case Training:
5598         if (!white_piece && WhiteOnMove(currentMove)) {
5599             DisplayMoveError(_("It is White's turn"));
5600             return FALSE;
5601         }
5602         if (white_piece && !WhiteOnMove(currentMove)) {
5603             DisplayMoveError(_("It is Black's turn"));
5604             return FALSE;
5605         }
5606         break;
5607
5608       default:
5609       case IcsExamining:
5610         break;
5611     }
5612     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614         && gameMode != AnalyzeFile && gameMode != Training) {
5615         DisplayMoveError(_("Displayed position is not current"));
5616         return FALSE;
5617     }
5618     return TRUE;
5619 }
5620
5621 Boolean
5622 OnlyMove(int *x, int *y) {
5623     DisambiguateClosure cl;
5624     if (appData.zippyPlay) return FALSE;
5625     switch(gameMode) {
5626       case MachinePlaysBlack:
5627       case IcsPlayingWhite:
5628       case BeginningOfGame:
5629         if(!WhiteOnMove(currentMove)) return FALSE;
5630         break;
5631       case MachinePlaysWhite:
5632       case IcsPlayingBlack:
5633         if(WhiteOnMove(currentMove)) return FALSE;
5634         break;
5635       default:
5636         return FALSE;
5637     }
5638     cl.pieceIn = EmptySquare; 
5639     cl.rfIn = *y;
5640     cl.ffIn = *x;
5641     cl.rtIn = -1;
5642     cl.ftIn = -1;
5643     cl.promoCharIn = NULLCHAR;
5644     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645     if(cl.kind == NormalMove) {
5646       fromX = cl.ff;
5647       fromY = cl.rf;
5648       *x = cl.ft;
5649       *y = cl.rt;
5650       return TRUE;
5651     }
5652     if(cl.kind != ImpossibleMove) return FALSE;
5653     cl.pieceIn = EmptySquare;
5654     cl.rfIn = -1;
5655     cl.ffIn = -1;
5656     cl.rtIn = *y;
5657     cl.ftIn = *x;
5658     cl.promoCharIn = NULLCHAR;
5659     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660     if(cl.kind == NormalMove) {
5661       fromX = cl.ff;
5662       fromY = cl.rf;
5663       *x = cl.ft;
5664       *y = cl.rt;
5665       return TRUE;
5666     }
5667     return FALSE;
5668 }
5669
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5675
5676 ChessMove
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678      int fromX, fromY, toX, toY;
5679      int promoChar;
5680      Boolean captureOwn;
5681 {
5682     ChessMove moveType;
5683     ChessSquare pdown, pup;
5684
5685     /* Check if the user is playing in turn.  This is complicated because we
5686        let the user "pick up" a piece before it is his turn.  So the piece he
5687        tried to pick up may have been captured by the time he puts it down!
5688        Therefore we use the color the user is supposed to be playing in this
5689        test, not the color of the piece that is currently on the starting
5690        square---except in EditGame mode, where the user is playing both
5691        sides; fortunately there the capture race can't happen.  (It can
5692        now happen in IcsExamining mode, but that's just too bad.  The user
5693        will get a somewhat confusing message in that case.)
5694        */
5695
5696     switch (gameMode) {
5697       case PlayFromGameFile:
5698       case AnalyzeFile:
5699       case TwoMachinesPlay:
5700       case EndOfGame:
5701       case IcsObserving:
5702       case IcsIdle:
5703         /* We switched into a game mode where moves are not accepted,
5704            perhaps while the mouse button was down. */
5705         return ImpossibleMove;
5706
5707       case MachinePlaysWhite:
5708         /* User is moving for Black */
5709         if (WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return ImpossibleMove;
5712         }
5713         break;
5714
5715       case MachinePlaysBlack:
5716         /* User is moving for White */
5717         if (!WhiteOnMove(currentMove)) {
5718             DisplayMoveError(_("It is Black's turn"));
5719             return ImpossibleMove;
5720         }
5721         break;
5722
5723       case EditGame:
5724       case IcsExamining:
5725       case BeginningOfGame:
5726       case AnalyzeMode:
5727       case Training:
5728         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730             /* User is moving for Black */
5731             if (WhiteOnMove(currentMove)) {
5732                 DisplayMoveError(_("It is White's turn"));
5733                 return ImpossibleMove;
5734             }
5735         } else {
5736             /* User is moving for White */
5737             if (!WhiteOnMove(currentMove)) {
5738                 DisplayMoveError(_("It is Black's turn"));
5739                 return ImpossibleMove;
5740             }
5741         }
5742         break;
5743
5744       case IcsPlayingBlack:
5745         /* User is moving for Black */
5746         if (WhiteOnMove(currentMove)) {
5747             if (!appData.premove) {
5748                 DisplayMoveError(_("It is White's turn"));
5749             } else if (toX >= 0 && toY >= 0) {
5750                 premoveToX = toX;
5751                 premoveToY = toY;
5752                 premoveFromX = fromX;
5753                 premoveFromY = fromY;
5754                 premovePromoChar = promoChar;
5755                 gotPremove = 1;
5756                 if (appData.debugMode)
5757                     fprintf(debugFP, "Got premove: fromX %d,"
5758                             "fromY %d, toX %d, toY %d\n",
5759                             fromX, fromY, toX, toY);
5760             }
5761             return ImpossibleMove;
5762         }
5763         break;
5764
5765       case IcsPlayingWhite:
5766         /* User is moving for White */
5767         if (!WhiteOnMove(currentMove)) {
5768             if (!appData.premove) {
5769                 DisplayMoveError(_("It is Black's turn"));
5770             } else if (toX >= 0 && toY >= 0) {
5771                 premoveToX = toX;
5772                 premoveToY = toY;
5773                 premoveFromX = fromX;
5774                 premoveFromY = fromY;
5775                 premovePromoChar = promoChar;
5776                 gotPremove = 1;
5777                 if (appData.debugMode)
5778                     fprintf(debugFP, "Got premove: fromX %d,"
5779                             "fromY %d, toX %d, toY %d\n",
5780                             fromX, fromY, toX, toY);
5781             }
5782             return ImpossibleMove;
5783         }
5784         break;
5785
5786       default:
5787         break;
5788
5789       case EditPosition:
5790         /* EditPosition, empty square, or different color piece;
5791            click-click move is possible */
5792         if (toX == -2 || toY == -2) {
5793             boards[0][fromY][fromX] = EmptySquare;
5794             return AmbiguousMove;
5795         } else if (toX >= 0 && toY >= 0) {
5796             boards[0][toY][toX] = boards[0][fromY][fromX];
5797             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798                 if(boards[0][fromY][0] != EmptySquare) {
5799                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5801                 }
5802             } else
5803             if(fromX == BOARD_RGHT+1) {
5804                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5807                 }
5808             } else
5809             boards[0][fromY][fromX] = EmptySquare;
5810             return AmbiguousMove;
5811         }
5812         return ImpossibleMove;
5813     }
5814
5815     if(toX < 0 || toY < 0) return ImpossibleMove;
5816     pdown = boards[currentMove][fromY][fromX];
5817     pup = boards[currentMove][toY][toX];
5818
5819     /* [HGM] If move started in holdings, it means a drop */
5820     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821          if( pup != EmptySquare ) return ImpossibleMove;
5822          if(appData.testLegality) {
5823              /* it would be more logical if LegalityTest() also figured out
5824               * which drops are legal. For now we forbid pawns on back rank.
5825               * Shogi is on its own here...
5826               */
5827              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5830          }
5831          return WhiteDrop; /* Not needed to specify white or black yet */
5832     }
5833
5834
5835     /* [HGM] always test for legality, to get promotion info */
5836     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837                                          fromY, fromX, toY, toX, promoChar);
5838     /* [HGM] but possibly ignore an IllegalMove result */
5839     if (appData.testLegality) {
5840         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841             DisplayMoveError(_("Illegal move"));
5842             return ImpossibleMove;
5843         }
5844     }
5845
5846     return moveType;
5847     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848        function is made into one that returns an OK move type if FinishMove
5849        should be called. This to give the calling driver routine the
5850        opportunity to finish the userMove input with a promotion popup,
5851        without bothering the user with this for invalid or illegal moves */
5852
5853 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5854 }
5855
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5857 int
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5859      ChessMove moveType;
5860      int fromX, fromY, toX, toY;
5861      /*char*/int promoChar;
5862 {
5863   char *bookHit = 0;
5864
5865   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5866     {
5867       // [HGM] superchess: suppress promotions to non-available piece
5868       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869       if(WhiteOnMove(currentMove))
5870         {
5871           if(!boards[currentMove][k][BOARD_WIDTH-2])
5872             return 0;
5873         }
5874       else
5875         {
5876           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5877             return 0;
5878         }
5879     }
5880   
5881   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882      move type in caller when we know the move is a legal promotion */
5883   if(moveType == NormalMove && promoChar)
5884     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5885   
5886   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887      move type in caller when we know the move is a legal promotion */
5888   if(moveType == NormalMove && promoChar)
5889     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5890   
5891   /* [HGM] convert drag-and-drop piece drops to standard form */
5892   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5893     {
5894       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5896                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5901       fromY = DROP_RANK;
5902     }
5903   
5904   /* [HGM] <popupFix> The following if has been moved here from
5905      UserMoveEvent(). Because it seemed to belong here (why not allow
5906      piece drops in training games?), and because it can only be
5907      performed after it is known to what we promote. */
5908   if (gameMode == Training) 
5909     {
5910       /* compare the move played on the board to the next move in the
5911        * game. If they match, display the move and the opponent's response.
5912        * If they don't match, display an error message.
5913        */
5914       int saveAnimate;
5915       Board testBoard;
5916       CopyBoard(testBoard, boards[currentMove]);
5917       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5918
5919       if (CompareBoards(testBoard, boards[currentMove+1]))
5920         {
5921           ForwardInner(currentMove+1);
5922
5923           /* Autoplay the opponent's response.
5924            * if appData.animate was TRUE when Training mode was entered,
5925            * the response will be animated.
5926            */
5927           saveAnimate = appData.animate;
5928           appData.animate = animateTraining;
5929           ForwardInner(currentMove+1);
5930           appData.animate = saveAnimate;
5931
5932           /* check for the end of the game */
5933           if (currentMove >= forwardMostMove)
5934             {
5935               gameMode = PlayFromGameFile;
5936               ModeHighlight();
5937               SetTrainingModeOff();
5938               DisplayInformation(_("End of game"));
5939             }
5940         }
5941       else
5942         {
5943           DisplayError(_("Incorrect move"), 0);
5944         }
5945       return 1;
5946     }
5947
5948   /* Ok, now we know that the move is good, so we can kill
5949      the previous line in Analysis Mode */
5950   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5951                                 && currentMove < forwardMostMove) {
5952     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5953   }
5954
5955   /* If we need the chess program but it's dead, restart it */
5956   ResurrectChessProgram();
5957
5958   /* A user move restarts a paused game*/
5959   if (pausing)
5960     PauseEvent();
5961
5962   thinkOutput[0] = NULLCHAR;
5963
5964   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5965
5966
5967  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5968
5969 if (gameMode == BeginningOfGame)
5970     {
5971       if (appData.noChessProgram)
5972         {
5973           gameMode = EditGame;
5974           SetGameInfo();
5975         }
5976       else
5977         {
5978           char buf[MSG_SIZ];
5979           gameMode = MachinePlaysBlack;
5980           StartClocks();
5981           SetGameInfo();
5982           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5983           DisplayTitle(buf);
5984           if (first.sendName)
5985             {
5986               sprintf(buf, "name %s\n", gameInfo.white);
5987               SendToProgram(buf, &first);
5988             }
5989           StartClocks();
5990         }
5991       ModeHighlight();
5992
5993     }
5994
5995   /* Relay move to ICS or chess engine */
5996
5997   if (appData.icsActive) {
5998     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999         gameMode == IcsExamining) {
6000       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6002         SendToICS("draw ");
6003         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6004       }
6005       // also send plain move, in case ICS does not understand atomic claims
6006       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6007       ics_user_moved = 1;
6008     }
6009   } else {
6010     if (first.sendTime && (gameMode == BeginningOfGame ||
6011                            gameMode == MachinePlaysWhite ||
6012                            gameMode == MachinePlaysBlack)) {
6013       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6014     }
6015     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016          // [HGM] book: if program might be playing, let it use book
6017         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018         first.maybeThinking = TRUE;
6019     } else SendMoveToProgram(forwardMostMove-1, &first);
6020     if (currentMove == cmailOldMove + 1) {
6021       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6022     }
6023     }
6024
6025   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027   switch (gameMode) 
6028     {
6029     case EditGame:
6030       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
6031         {
6032         case MT_NONE:
6033         case MT_CHECK:
6034           break;
6035         case MT_CHECKMATE:
6036         case MT_STAINMATE:
6037           if (WhiteOnMove(currentMove)) {
6038             GameEnds(BlackWins, "Black mates", GE_PLAYER);
6039           } else {
6040             GameEnds(WhiteWins, "White mates", GE_PLAYER);
6041           }
6042           break;
6043         case MT_STALEMATE:
6044           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6045           break;
6046         }
6047       break;
6048       
6049     case MachinePlaysBlack:
6050     case MachinePlaysWhite:
6051       /* disable certain menu options while machine is thinking */
6052       SetMachineThinkingEnables();
6053       break;
6054       
6055     default:
6056       break;
6057     }
6058   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6059         
6060   if(bookHit)
6061     { // [HGM] book: simulate book reply
6062         static char bookMove[MSG_SIZ]; // a bit generous?
6063
6064
6065       programStats.nodes = programStats.depth = programStats.time =
6066         programStats.score = programStats.got_only_move = 0;
6067       sprintf(programStats.movelist, "%s (xbook)", bookHit);
6068
6069       strcpy(bookMove, "move ");
6070       strcat(bookMove, bookHit);
6071       HandleMachineMove(bookMove, &first);
6072     }
6073
6074   return 1;
6075 }
6076
6077 void
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079      int fromX, fromY, toX, toY;
6080      int promoChar;
6081 {
6082     /* [HGM] This routine was added to allow calling of its two logical
6083        parts from other modules in the old way. Before, UserMoveEvent()
6084        automatically called FinishMove() if the move was OK, and returned
6085        otherwise. I separated the two, in order to make it possible to
6086        slip a promotion popup in between. But that it always needs two
6087        calls, to the first part, (now called UserMoveTest() ), and to
6088        FinishMove if the first part succeeded. Calls that do not need
6089        to do anything in between, can call this routine the old way.
6090     */
6091   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093   if(moveType == AmbiguousMove)
6094     DrawPosition(FALSE, boards[currentMove]);
6095   else if(moveType != ImpossibleMove && moveType != Comment)
6096     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6097 }
6098
6099 void
6100 PromoDialog(int h, int w, Board board, Boolean clearBoard)
6101 {       // dummy routine to mimic with pseudo-popup what front-end should do:
6102         // display a popup with h x w mini-board, and divert any mouse clicks
6103         // on it to the back-end routines RightClick and LeftClick, just
6104         // like the mouse event hadler of the board widget does now.
6105         // (Note it would have to off-set x if holdings are displayed!)
6106         DisplayMessage("Click on your piece of choice", "");
6107         DrawPosition(TRUE, board);
6108 }
6109
6110 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 };
6111 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 };
6112 Board promoBoard;
6113 int promotionChoice = 0;
6114
6115 void
6116 PiecePopUp()
6117 {
6118     int i, j, h, w, nWhite=0, nBlack=0;
6119     ChessSquare list[EmptySquare];
6120     for(i=0; i<EmptySquare/2; i++) {
6121         if(PieceToChar(i) != '.') list[nWhite++] = i;
6122         if(PieceToChar(i+EmptySquare/2) != '.') list[EmptySquare - ++nBlack] = i + EmptySquare/2;
6123     }
6124     CopyBoard(promoBoard, boards[currentMove]);
6125     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6126     j = nWhite + nBlack + 1;
6127     h = sqrt((j+1)/2 + 1.); w = (j+h-1)/h;
6128     if(w>BOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; }
6129     for(i=0; i<nWhite; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[nWhite-1-i];
6130     if(h==2 && nWhite == nBlack)
6131         for(i=0; i<nWhite; i++) promoBoard[1][BOARD_LEFT+i%w] = list[EmptySquare-nBlack+i];
6132     else
6133         for(i=0; i<nBlack; i++) promoBoard[h-1-i/w][BOARD_LEFT+w-1-i%w] = list[EmptySquare-nBlack+i];
6134     promotionChoice = 3;
6135     ClearHighlights();
6136     PromoDialog(h, w, promoBoard, TRUE);
6137 }
6138
6139 void
6140 PromoPopUp(ChessSquare piece)
6141 {   // determine the layout of the piece-choice dialog
6142     int w, h, i, j, nr;
6143     ChessSquare list[EmptySquare];
6144
6145     if(gameInfo.variant == VariantShogi) {
6146         // non-Pawn promotes; must be shogi
6147         h = 1; w = 1; list[0] = piece;
6148         if(PieceToChar(PROMOTED piece) != '.') {
6149             // promoted version is enabled
6150             w = 2; list[1] = PROMOTED piece;
6151         }
6152     } else {
6153         // Pawn, promotes to any enabled other piece
6154         h = 1; w = nr = 0;
6155         for(i=1; i<EmptySquare/2; i++) {
6156             if(PieceToChar(piece+i) != '.' 
6157            && PieceToChar(PROMOTED piece + i) != '~' // suppress bughouse true pieces
6158                                                         ) {
6159                 list[w++] = piece+i; nr++;
6160             }
6161         }
6162         if(appData.testLegality && gameInfo.variant != VariantSuicide) nr--,w--; // remove King
6163         h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6164         for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6165         for(i=0; i < nr; i++) promoBoard[BOARD_LEFT+i/w][i%w] = list[i]; // layout
6166     }
6167     promotionChoice = 2; // wait for click on board
6168     PromoDialog(h, w, promoBoard, FALSE);
6169 }
6170
6171 void
6172 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6173      Board board;
6174      int flags;
6175      ChessMove kind;
6176      int rf, ff, rt, ft;
6177      VOIDSTAR closure;
6178 {
6179     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6180     Markers *m = (Markers *) closure;
6181     if(rf == fromY && ff == fromX)
6182         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6183                          || kind == WhiteCapturesEnPassant
6184                          || kind == BlackCapturesEnPassant);
6185     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6186 }
6187
6188 void
6189 MarkTargetSquares(int clear)
6190 {
6191   int x, y;
6192   if(!appData.markers || !appData.highlightDragging || 
6193      !appData.testLegality || gameMode == EditPosition) return;
6194   if(clear) {
6195     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6196   } else {
6197     int capt = 0;
6198     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6199     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6200       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6201       if(capt)
6202       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6203     }
6204   }
6205   DrawPosition(TRUE, NULL);
6206 }
6207
6208 void LeftClick(ClickType clickType, int xPix, int yPix)
6209 {
6210     int x, y;
6211     Boolean saveAnimate;
6212     static int second = 0;
6213     char promoChoice = NULLCHAR;
6214
6215     if(appData.seekGraph && appData.icsActive && loggedOn &&
6216         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6217         SeekGraphClick(clickType, xPix, yPix, 0);
6218         return;
6219     }
6220
6221     if (clickType == Press) ErrorPopDown();
6222     MarkTargetSquares(1);
6223
6224     x = EventToSquare(xPix, BOARD_WIDTH);
6225     y = EventToSquare(yPix, BOARD_HEIGHT);
6226     if (!flipView && y >= 0) {
6227         y = BOARD_HEIGHT - 1 - y;
6228     }
6229     if (flipView && x >= 0) {
6230         x = BOARD_WIDTH - 1 - x;
6231     }
6232
6233     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6234         ChessSquare p = EmptySquare; Boolean inHoldings;
6235         if(promotionChoice == 3) {
6236             if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6237             else if(clickType == Release) promotionChoice = 0;
6238             fromX = fromY = -1;
6239             return;
6240         }
6241         if(clickType == Release) return; // ignore upclick of click-click destination
6242         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6243         inHoldings = gameInfo.holdingsWidth &&
6244                 (WhiteOnMove(currentMove) 
6245                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6246                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6247             // click in right holdings, for determining promotion piece
6248         if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6249             p = promoBoard[y][x];
6250             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6251             if(p != EmptySquare) {
6252                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6253                 fromX = fromY = -1;
6254                 promotionChoice = 0;
6255                 return;
6256             }
6257         }
6258         promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6259         DrawPosition(FALSE, boards[currentMove]);
6260         return;
6261     }
6262
6263     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6264     if(clickType == Press
6265             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6266               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6267               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6268         return;
6269
6270     if (fromX == -1) {
6271       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6272         if (clickType == Press) {
6273             /* First square */
6274             if (OKToStartUserMove(x, y)) {
6275                 fromX = x;
6276                 fromY = y;
6277                 second = 0;
6278                 MarkTargetSquares(0);
6279                 DragPieceBegin(xPix, yPix);
6280                 if (appData.highlightDragging) {
6281                     SetHighlights(x, y, -1, -1);
6282                 }
6283             }
6284         }
6285         return;
6286       }
6287     }
6288
6289     /* fromX != -1 */
6290     if (clickType == Press && gameMode != EditPosition) {
6291         ChessSquare fromP;
6292         ChessSquare toP;
6293         int frc;
6294
6295         // ignore off-board to clicks
6296         if(y < 0 || x < 0) return;
6297
6298         /* Check if clicking again on the same color piece */
6299         fromP = boards[currentMove][fromY][fromX];
6300         toP = boards[currentMove][y][x];
6301         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6302         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6303              WhitePawn <= toP && toP <= WhiteKing &&
6304              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6305              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6306             (BlackPawn <= fromP && fromP <= BlackKing && 
6307              BlackPawn <= toP && toP <= BlackKing &&
6308              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6309              !(fromP == BlackKing && toP == BlackRook && frc))) {
6310             /* Clicked again on same color piece -- changed his mind */
6311             second = (x == fromX && y == fromY);
6312             if (appData.highlightDragging) {
6313                 SetHighlights(x, y, -1, -1);
6314             } else {
6315                 ClearHighlights();
6316             }
6317             if (OKToStartUserMove(x, y)) {
6318                 fromX = x;
6319                 fromY = y;
6320                 MarkTargetSquares(0);
6321                 DragPieceBegin(xPix, yPix);
6322             }
6323             return;
6324         }
6325         // ignore clicks on holdings
6326         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6327     }
6328
6329     if (clickType == Release && x == fromX && y == fromY) {
6330         DragPieceEnd(xPix, yPix);
6331         if (appData.animateDragging) {
6332             /* Undo animation damage if any */
6333             DrawPosition(FALSE, NULL);
6334         }
6335         if (second) {
6336             /* Second up/down in same square; just abort move */
6337             second = 0;
6338             fromX = fromY = -1;
6339             ClearHighlights();
6340             gotPremove = 0;
6341             ClearPremoveHighlights();
6342         } else {
6343             /* First upclick in same square; start click-click mode */
6344             SetHighlights(x, y, -1, -1);
6345         }
6346         return;
6347     }
6348
6349     /* we now have a different from- and (possibly off-board) to-square */
6350     /* Completed move */
6351     toX = x;
6352     toY = y;
6353     saveAnimate = appData.animate;
6354     if (clickType == Press) {
6355         /* Finish clickclick move */
6356         if (appData.animate || appData.highlightLastMove) {
6357             SetHighlights(fromX, fromY, toX, toY);
6358         } else {
6359             ClearHighlights();
6360         }
6361     } else {
6362         /* Finish drag move */
6363         if (appData.highlightLastMove) {
6364             SetHighlights(fromX, fromY, toX, toY);
6365         } else {
6366             ClearHighlights();
6367         }
6368         DragPieceEnd(xPix, yPix);
6369         /* Don't animate move and drag both */
6370         appData.animate = FALSE;
6371     }
6372
6373     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6374     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6375         ChessSquare piece = boards[currentMove][fromY][fromX];
6376         if(gameMode == EditPosition && piece != EmptySquare &&
6377            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6378             int n;
6379              
6380             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6381                 n = PieceToNumber(piece - (int)BlackPawn);
6382                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6383                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6384                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6385             } else
6386             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6387                 n = PieceToNumber(piece);
6388                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6389                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6390                 boards[currentMove][n][BOARD_WIDTH-2]++;
6391             }
6392             boards[currentMove][fromY][fromX] = EmptySquare;
6393         }
6394         ClearHighlights();
6395         fromX = fromY = -1;
6396         DrawPosition(TRUE, boards[currentMove]);
6397         return;
6398     }
6399
6400     // off-board moves should not be highlighted
6401     if(x < 0 || x < 0) ClearHighlights();
6402
6403     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6404         SetHighlights(fromX, fromY, toX, toY);
6405         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6406             // [HGM] super: promotion to captured piece selected from holdings
6407             ChessSquare p = boards[currentMove][fromY][fromX];
6408             promotionChoice = 1;
6409             CopyBoard(promoBoard, boards[currentMove]);
6410             // kludge follows to temporarily execute move on display, without promoting yet
6411             promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6412             promoBoard[toY][toX] = p;
6413             DrawPosition(FALSE, promoBoard);
6414             DisplayMessage("Click in holdings to choose piece", "");
6415             return;
6416         }
6417         CopyBoard(promoBoard, boards[currentMove]);
6418         PromoPopUp(boards[currentMove][fromY][fromX]);
6419     } else {
6420         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6421         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6422         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6423         fromX = fromY = -1;
6424     }
6425     appData.animate = saveAnimate;
6426     if (appData.animate || appData.animateDragging) {
6427         /* Undo animation damage if needed */
6428         DrawPosition(FALSE, NULL);
6429     }
6430 }
6431
6432 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6433 {   // front-end-free part taken out of PieceMenuPopup
6434     int whichMenu; int xSqr, ySqr;
6435
6436     if(seekGraphUp) { // [HGM] seekgraph
6437         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6438         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6439         return -2;
6440     }
6441
6442     xSqr = EventToSquare(x, BOARD_WIDTH);
6443     ySqr = EventToSquare(y, BOARD_HEIGHT);
6444     if (flipView)
6445       xSqr = BOARD_WIDTH - 1 - xSqr;
6446     else
6447       ySqr = BOARD_HEIGHT - 1 - ySqr;
6448     if(promotionChoice == 3 && action == Release
6449          && promoBoard[ySqr][xSqr] != EmptySquare && (xSqr != fromX || ySqr != fromY) // not needed if separate window
6450                                                 ) {
6451         EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6452         fromX = fromY = -1;
6453         promotionChoice = 0;
6454         return -1;
6455     }
6456     if (action == Release) UnLoadPV(); // [HGM] pv
6457     if (action != Press) return -2; // return code to be ignored
6458     switch (gameMode) {
6459       case IcsExamining:
6460         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6461       case EditPosition:
6462         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6463         if (xSqr < 0 || ySqr < 0) return -1;
6464         whichMenu = 0; // edit-position menu
6465         break;
6466       case IcsObserving:
6467         if(!appData.icsEngineAnalyze) return -1;
6468       case IcsPlayingWhite:
6469       case IcsPlayingBlack:
6470         if(!appData.zippyPlay) goto noZip;
6471       case AnalyzeMode:
6472       case AnalyzeFile:
6473       case MachinePlaysWhite:
6474       case MachinePlaysBlack:
6475       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6476         if (!appData.dropMenu) {
6477           LoadPV(x, y);
6478           return 2; // flag front-end to grab mouse events
6479         }
6480         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6481            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6482       case EditGame:
6483       noZip:
6484         if (xSqr < 0 || ySqr < 0) return -1;
6485         if (!appData.dropMenu || appData.testLegality &&
6486             gameInfo.variant != VariantBughouse &&
6487             gameInfo.variant != VariantCrazyhouse) return -1;
6488         whichMenu = 1; // drop menu
6489         break;
6490       default:
6491         return -1;
6492     }
6493
6494     if (((*xx = xSqr) < 0) ||
6495         ((*yy = ySqr) < 0)) {
6496         *xx = *yy = -1;
6497         return -1;
6498     }
6499
6500     fromX = *xx; fromY = *yy;
6501     if(whichMenu == 0) { PiecePopUp(); return -1; } // suppress EditPosition menu
6502
6503     return whichMenu;
6504 }
6505
6506 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6507 {
6508 //    char * hint = lastHint;
6509     FrontEndProgramStats stats;
6510
6511     stats.which = cps == &first ? 0 : 1;
6512     stats.depth = cpstats->depth;
6513     stats.nodes = cpstats->nodes;
6514     stats.score = cpstats->score;
6515     stats.time = cpstats->time;
6516     stats.pv = cpstats->movelist;
6517     stats.hint = lastHint;
6518     stats.an_move_index = 0;
6519     stats.an_move_count = 0;
6520
6521     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6522         stats.hint = cpstats->move_name;
6523         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6524         stats.an_move_count = cpstats->nr_moves;
6525     }
6526
6527     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6528
6529     SetProgramStats( &stats );
6530 }
6531
6532 int
6533 Adjudicate(ChessProgramState *cps)
6534 {       // [HGM] some adjudications useful with buggy engines
6535         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6536         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6537         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6538         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6539         int k, count = 0; static int bare = 1;
6540         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6541         Boolean canAdjudicate = !appData.icsActive;
6542
6543         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6544         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6545             if( appData.testLegality )
6546             {   /* [HGM] Some more adjudications for obstinate engines */
6547                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6548                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6549                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6550                 static int moveCount = 6;
6551                 ChessMove result;
6552                 char *reason = NULL;
6553
6554
6555                 /* Count what is on board. */
6556                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6557                 {   ChessSquare p = boards[forwardMostMove][i][j];
6558                     int m=i;
6559
6560                     switch((int) p)
6561                     {   /* count B,N,R and other of each side */
6562                         case WhiteKing:
6563                         case BlackKing:
6564                              NrK++; break; // [HGM] atomic: count Kings
6565                         case WhiteKnight:
6566                              NrWN++; break;
6567                         case WhiteBishop:
6568                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6569                              bishopsColor |= 1 << ((i^j)&1);
6570                              NrWB++; break;
6571                         case BlackKnight:
6572                              NrBN++; break;
6573                         case BlackBishop:
6574                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6575                              bishopsColor |= 1 << ((i^j)&1);
6576                              NrBB++; break;
6577                         case WhiteRook:
6578                              NrWR++; break;
6579                         case BlackRook:
6580                              NrBR++; break;
6581                         case WhiteQueen:
6582                              NrWQ++; break;
6583                         case BlackQueen:
6584                              NrBQ++; break;
6585                         case EmptySquare: 
6586                              break;
6587                         case BlackPawn:
6588                              m = 7-i;
6589                         case WhitePawn:
6590                              PawnAdvance += m; NrPawns++;
6591                     }
6592                     NrPieces += (p != EmptySquare);
6593                     NrW += ((int)p < (int)BlackPawn);
6594                     if(gameInfo.variant == VariantXiangqi && 
6595                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6596                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6597                         NrW -= ((int)p < (int)BlackPawn);
6598                     }
6599                 }
6600
6601                 /* Some material-based adjudications that have to be made before stalemate test */
6602                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6603                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6604                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6605                      if(canAdjudicate && appData.checkMates) {
6606                          if(engineOpponent)
6607                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6608                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6609                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6610                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6611                          return 1;
6612                      }
6613                 }
6614
6615                 /* Bare King in Shatranj (loses) or Losers (wins) */
6616                 if( NrW == 1 || NrPieces - NrW == 1) {
6617                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6618                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6619                      if(canAdjudicate && appData.checkMates) {
6620                          if(engineOpponent)
6621                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6622                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6623                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6624                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6625                          return 1;
6626                      }
6627                   } else
6628                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6629                   {    /* bare King */
6630                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6631                         if(canAdjudicate && appData.checkMates) {
6632                             /* but only adjudicate if adjudication enabled */
6633                             if(engineOpponent)
6634                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6635                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6637                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6638                             return 1;
6639                         }
6640                   }
6641                 } else bare = 1;
6642
6643
6644             // don't wait for engine to announce game end if we can judge ourselves
6645             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6646               case MT_CHECK:
6647                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6648                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6649                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6650                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6651                             checkCnt++;
6652                         if(checkCnt >= 2) {
6653                             reason = "Xboard adjudication: 3rd check";
6654                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6655                             break;
6656                         }
6657                     }
6658                 }
6659               case MT_NONE:
6660               default:
6661                 break;
6662               case MT_STALEMATE:
6663               case MT_STAINMATE:
6664                 reason = "Xboard adjudication: Stalemate";
6665                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6666                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6667                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6668                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6669                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6670                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6671                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6672                                                                         EP_CHECKMATE : EP_WINS);
6673                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6674                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6675                 }
6676                 break;
6677               case MT_CHECKMATE:
6678                 reason = "Xboard adjudication: Checkmate";
6679                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6680                 break;
6681             }
6682
6683                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6684                     case EP_STALEMATE:
6685                         result = GameIsDrawn; break;
6686                     case EP_CHECKMATE:
6687                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6688                     case EP_WINS:
6689                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6690                     default:
6691                         result = (ChessMove) 0;
6692                 }
6693                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6694                     if(engineOpponent)
6695                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6696                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697                     GameEnds( result, reason, GE_XBOARD );
6698                     return 1;
6699                 }
6700
6701                 /* Next absolutely insufficient mating material. */
6702                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6703                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6704                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6705                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6706                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6707
6708                      /* always flag draws, for judging claims */
6709                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6710
6711                      if(canAdjudicate && appData.materialDraws) {
6712                          /* but only adjudicate them if adjudication enabled */
6713                          if(engineOpponent) {
6714                            SendToProgram("force\n", engineOpponent); // suppress reply
6715                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6716                          }
6717                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6718                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6719                          return 1;
6720                      }
6721                 }
6722
6723                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6724                 if(NrPieces == 4 && 
6725                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6726                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6727                    || NrWN==2 || NrBN==2     /* KNNK */
6728                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6729                   ) ) {
6730                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6731                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6732                           if(engineOpponent) {
6733                             SendToProgram("force\n", engineOpponent); // suppress reply
6734                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6735                           }
6736                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6737                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6738                           return 1;
6739                      }
6740                 } else moveCount = 6;
6741             }
6742         }
6743           
6744         if (appData.debugMode) { int i;
6745             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6746                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6747                     appData.drawRepeats);
6748             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6749               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6750             
6751         }
6752
6753         // Repetition draws and 50-move rule can be applied independently of legality testing
6754
6755                 /* Check for rep-draws */
6756                 count = 0;
6757                 for(k = forwardMostMove-2;
6758                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6759                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6760                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6761                     k-=2)
6762                 {   int rights=0;
6763                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6764                         /* compare castling rights */
6765                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6766                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6767                                 rights++; /* King lost rights, while rook still had them */
6768                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6769                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6770                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6771                                    rights++; /* but at least one rook lost them */
6772                         }
6773                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6774                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6775                                 rights++; 
6776                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6777                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6778                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6779                                    rights++;
6780                         }
6781                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6782                             && appData.drawRepeats > 1) {
6783                              /* adjudicate after user-specified nr of repeats */
6784                              if(engineOpponent) {
6785                                SendToProgram("force\n", engineOpponent); // suppress reply
6786                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6787                              }
6788                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6790                                 // [HGM] xiangqi: check for forbidden perpetuals
6791                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6792                                 for(m=forwardMostMove; m>k; m-=2) {
6793                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6794                                         ourPerpetual = 0; // the current mover did not always check
6795                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6796                                         hisPerpetual = 0; // the opponent did not always check
6797                                 }
6798                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6799                                                                         ourPerpetual, hisPerpetual);
6800                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6801                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6802                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6803                                     return 1;
6804                                 }
6805                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6806                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6807                                 // Now check for perpetual chases
6808                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6809                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6810                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6811                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6812                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6813                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6814                                         return 1;
6815                                     }
6816                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6817                                         break; // Abort repetition-checking loop.
6818                                 }
6819                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6820                              }
6821                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6822                              return 1;
6823                         }
6824                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6825                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6826                     }
6827                 }
6828
6829                 /* Now we test for 50-move draws. Determine ply count */
6830                 count = forwardMostMove;
6831                 /* look for last irreversble move */
6832                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6833                     count--;
6834                 /* if we hit starting position, add initial plies */
6835                 if( count == backwardMostMove )
6836                     count -= initialRulePlies;
6837                 count = forwardMostMove - count; 
6838                 if( count >= 100)
6839                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6840                          /* this is used to judge if draw claims are legal */
6841                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6842                          if(engineOpponent) {
6843                            SendToProgram("force\n", engineOpponent); // suppress reply
6844                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6845                          }
6846                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6848                          return 1;
6849                 }
6850
6851                 /* if draw offer is pending, treat it as a draw claim
6852                  * when draw condition present, to allow engines a way to
6853                  * claim draws before making their move to avoid a race
6854                  * condition occurring after their move
6855                  */
6856                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6857                          char *p = NULL;
6858                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6859                              p = "Draw claim: 50-move rule";
6860                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6861                              p = "Draw claim: 3-fold repetition";
6862                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6863                              p = "Draw claim: insufficient mating material";
6864                          if( p != NULL && canAdjudicate) {
6865                              if(engineOpponent) {
6866                                SendToProgram("force\n", engineOpponent); // suppress reply
6867                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6868                              }
6869                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6870                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6871                              return 1;
6872                          }
6873                 }
6874
6875                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6876                     if(engineOpponent) {
6877                       SendToProgram("force\n", engineOpponent); // suppress reply
6878                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6879                     }
6880                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6881                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6882                     return 1;
6883                 }
6884         return 0;
6885 }
6886
6887 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6888 {   // [HGM] book: this routine intercepts moves to simulate book replies
6889     char *bookHit = NULL;
6890
6891     //first determine if the incoming move brings opponent into his book
6892     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6893         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6894     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6895     if(bookHit != NULL && !cps->bookSuspend) {
6896         // make sure opponent is not going to reply after receiving move to book position
6897         SendToProgram("force\n", cps);
6898         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6899     }
6900     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6901     // now arrange restart after book miss
6902     if(bookHit) {
6903         // after a book hit we never send 'go', and the code after the call to this routine
6904         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6905         char buf[MSG_SIZ];
6906         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6907         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6908         SendToProgram(buf, cps);
6909         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6910     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6911         SendToProgram("go\n", cps);
6912         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6913     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6914         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6915             SendToProgram("go\n", cps); 
6916         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6917     }
6918     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6919 }
6920
6921 char *savedMessage;
6922 ChessProgramState *savedState;
6923 void DeferredBookMove(void)
6924 {
6925         if(savedState->lastPing != savedState->lastPong)
6926                     ScheduleDelayedEvent(DeferredBookMove, 10);
6927         else
6928         HandleMachineMove(savedMessage, savedState);
6929 }
6930
6931 void
6932 HandleMachineMove(message, cps)
6933      char *message;
6934      ChessProgramState *cps;
6935 {
6936     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6937     char realname[MSG_SIZ];
6938     int fromX, fromY, toX, toY;
6939     ChessMove moveType;
6940     char promoChar;
6941     char *p;
6942     int machineWhite;
6943     char *bookHit;
6944
6945     cps->userError = 0;
6946
6947 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6948     /*
6949      * Kludge to ignore BEL characters
6950      */
6951     while (*message == '\007') message++;
6952
6953     /*
6954      * [HGM] engine debug message: ignore lines starting with '#' character
6955      */
6956     if(cps->debug && *message == '#') return;
6957
6958     /*
6959      * Look for book output
6960      */
6961     if (cps == &first && bookRequested) {
6962         if (message[0] == '\t' || message[0] == ' ') {
6963             /* Part of the book output is here; append it */
6964             strcat(bookOutput, message);
6965             strcat(bookOutput, "  \n");
6966             return;
6967         } else if (bookOutput[0] != NULLCHAR) {
6968             /* All of book output has arrived; display it */
6969             char *p = bookOutput;
6970             while (*p != NULLCHAR) {
6971                 if (*p == '\t') *p = ' ';
6972                 p++;
6973             }
6974             DisplayInformation(bookOutput);
6975             bookRequested = FALSE;
6976             /* Fall through to parse the current output */
6977         }
6978     }
6979
6980     /*
6981      * Look for machine move.
6982      */
6983     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6984         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6985     {
6986         /* This method is only useful on engines that support ping */
6987         if (cps->lastPing != cps->lastPong) {
6988           if (gameMode == BeginningOfGame) {
6989             /* Extra move from before last new; ignore */
6990             if (appData.debugMode) {
6991                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6992             }
6993           } else {
6994             if (appData.debugMode) {
6995                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6996                         cps->which, gameMode);
6997             }
6998
6999             SendToProgram("undo\n", cps);
7000           }
7001           return;
7002         }
7003
7004         switch (gameMode) {
7005           case BeginningOfGame:
7006             /* Extra move from before last reset; ignore */
7007             if (appData.debugMode) {
7008                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7009             }
7010             return;
7011
7012           case EndOfGame:
7013           case IcsIdle:
7014           default:
7015             /* Extra move after we tried to stop.  The mode test is
7016                not a reliable way of detecting this problem, but it's
7017                the best we can do on engines that don't support ping.
7018             */
7019             if (appData.debugMode) {
7020                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7021                         cps->which, gameMode);
7022             }
7023             SendToProgram("undo\n", cps);
7024             return;
7025
7026           case MachinePlaysWhite:
7027           case IcsPlayingWhite:
7028             machineWhite = TRUE;
7029             break;
7030
7031           case MachinePlaysBlack:
7032           case IcsPlayingBlack:
7033             machineWhite = FALSE;
7034             break;
7035
7036           case TwoMachinesPlay:
7037             machineWhite = (cps->twoMachinesColor[0] == 'w');
7038             break;
7039         }
7040         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7041             if (appData.debugMode) {
7042                 fprintf(debugFP,
7043                         "Ignoring move out of turn by %s, gameMode %d"
7044                         ", forwardMost %d\n",
7045                         cps->which, gameMode, forwardMostMove);
7046             }
7047             return;
7048         }
7049
7050     if (appData.debugMode) { int f = forwardMostMove;
7051         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7052                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7053                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7054     }
7055         if(cps->alphaRank) AlphaRank(machineMove, 4);
7056         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7057                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7058             /* Machine move could not be parsed; ignore it. */
7059             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7060                     machineMove, cps->which);
7061             DisplayError(buf1, 0);
7062             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7063                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7064             if (gameMode == TwoMachinesPlay) {
7065               GameEnds(machineWhite ? BlackWins : WhiteWins,
7066                        buf1, GE_XBOARD);
7067             }
7068             return;
7069         }
7070
7071         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7072         /* So we have to redo legality test with true e.p. status here,  */
7073         /* to make sure an illegal e.p. capture does not slip through,   */
7074         /* to cause a forfeit on a justified illegal-move complaint      */
7075         /* of the opponent.                                              */
7076         if( gameMode==TwoMachinesPlay && appData.testLegality
7077             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7078                                                               ) {
7079            ChessMove moveType;
7080            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7081                              fromY, fromX, toY, toX, promoChar);
7082             if (appData.debugMode) {
7083                 int i;
7084                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7085                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7086                 fprintf(debugFP, "castling rights\n");
7087             }
7088             if(moveType == IllegalMove) {
7089                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7090                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7091                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7092                            buf1, GE_XBOARD);
7093                 return;
7094            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7095            /* [HGM] Kludge to handle engines that send FRC-style castling
7096               when they shouldn't (like TSCP-Gothic) */
7097            switch(moveType) {
7098              case WhiteASideCastleFR:
7099              case BlackASideCastleFR:
7100                toX+=2;
7101                currentMoveString[2]++;
7102                break;
7103              case WhiteHSideCastleFR:
7104              case BlackHSideCastleFR:
7105                toX--;
7106                currentMoveString[2]--;
7107                break;
7108              default: ; // nothing to do, but suppresses warning of pedantic compilers
7109            }
7110         }
7111         hintRequested = FALSE;
7112         lastHint[0] = NULLCHAR;
7113         bookRequested = FALSE;
7114         /* Program may be pondering now */
7115         cps->maybeThinking = TRUE;
7116         if (cps->sendTime == 2) cps->sendTime = 1;
7117         if (cps->offeredDraw) cps->offeredDraw--;
7118
7119         /* currentMoveString is set as a side-effect of ParseOneMove */
7120         strcpy(machineMove, currentMoveString);
7121         strcat(machineMove, "\n");
7122         strcpy(moveList[forwardMostMove], machineMove);
7123
7124         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7125
7126         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7127         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7128             int count = 0;
7129
7130             while( count < adjudicateLossPlies ) {
7131                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7132
7133                 if( count & 1 ) {
7134                     score = -score; /* Flip score for winning side */
7135                 }
7136
7137                 if( score > adjudicateLossThreshold ) {
7138                     break;
7139                 }
7140
7141                 count++;
7142             }
7143
7144             if( count >= adjudicateLossPlies ) {
7145                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7146
7147                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7148                     "Xboard adjudication", 
7149                     GE_XBOARD );
7150
7151                 return;
7152             }
7153         }
7154
7155         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7156
7157 #if ZIPPY
7158         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7159             first.initDone) {
7160           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7161                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7162                 SendToICS("draw ");
7163                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7164           }
7165           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7166           ics_user_moved = 1;
7167           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7168                 char buf[3*MSG_SIZ];
7169
7170                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7171                         programStats.score / 100.,
7172                         programStats.depth,
7173                         programStats.time / 100.,
7174                         (unsigned int)programStats.nodes,
7175                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7176                         programStats.movelist);
7177                 SendToICS(buf);
7178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7179           }
7180         }
7181 #endif
7182
7183         /* [AS] Save move info and clear stats for next move */
7184         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7185         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7186         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7187         ClearProgramStats();
7188         thinkOutput[0] = NULLCHAR;
7189         hiddenThinkOutputState = 0;
7190
7191         bookHit = NULL;
7192         if (gameMode == TwoMachinesPlay) {
7193             /* [HGM] relaying draw offers moved to after reception of move */
7194             /* and interpreting offer as claim if it brings draw condition */
7195             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7196                 SendToProgram("draw\n", cps->other);
7197             }
7198             if (cps->other->sendTime) {
7199                 SendTimeRemaining(cps->other,
7200                                   cps->other->twoMachinesColor[0] == 'w');
7201             }
7202             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7203             if (firstMove && !bookHit) {
7204                 firstMove = FALSE;
7205                 if (cps->other->useColors) {
7206                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7207                 }
7208                 SendToProgram("go\n", cps->other);
7209             }
7210             cps->other->maybeThinking = TRUE;
7211         }
7212
7213         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7214
7215         if (!pausing && appData.ringBellAfterMoves) {
7216             RingBell();
7217         }
7218
7219         /*
7220          * Reenable menu items that were disabled while
7221          * machine was thinking
7222          */
7223         if (gameMode != TwoMachinesPlay)
7224             SetUserThinkingEnables();
7225
7226         // [HGM] book: after book hit opponent has received move and is now in force mode
7227         // force the book reply into it, and then fake that it outputted this move by jumping
7228         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7229         if(bookHit) {
7230                 static char bookMove[MSG_SIZ]; // a bit generous?
7231
7232                 strcpy(bookMove, "move ");
7233                 strcat(bookMove, bookHit);
7234                 message = bookMove;
7235                 cps = cps->other;
7236                 programStats.nodes = programStats.depth = programStats.time =
7237                 programStats.score = programStats.got_only_move = 0;
7238                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7239
7240                 if(cps->lastPing != cps->lastPong) {
7241                     savedMessage = message; // args for deferred call
7242                     savedState = cps;
7243                     ScheduleDelayedEvent(DeferredBookMove, 10);
7244                     return;
7245                 }
7246                 goto FakeBookMove;
7247         }
7248
7249         return;
7250     }
7251
7252     /* Set special modes for chess engines.  Later something general
7253      *  could be added here; for now there is just one kludge feature,
7254      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7255      *  when "xboard" is given as an interactive command.
7256      */
7257     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7258         cps->useSigint = FALSE;
7259         cps->useSigterm = FALSE;
7260     }
7261     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7262       ParseFeatures(message+8, cps);
7263       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7264     }
7265
7266     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7267      * want this, I was asked to put it in, and obliged.
7268      */
7269     if (!strncmp(message, "setboard ", 9)) {
7270         Board initial_position;
7271
7272         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7273
7274         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7275             DisplayError(_("Bad FEN received from engine"), 0);
7276             return ;
7277         } else {
7278            Reset(TRUE, FALSE);
7279            CopyBoard(boards[0], initial_position);
7280            initialRulePlies = FENrulePlies;
7281            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7282            else gameMode = MachinePlaysBlack;
7283            DrawPosition(FALSE, boards[currentMove]);
7284         }
7285         return;
7286     }
7287
7288     /*
7289      * Look for communication commands
7290      */
7291     if (!strncmp(message, "telluser ", 9)) {
7292         DisplayNote(message + 9);
7293         return;
7294     }
7295     if (!strncmp(message, "tellusererror ", 14)) {
7296         cps->userError = 1;
7297         DisplayError(message + 14, 0);
7298         return;
7299     }
7300     if (!strncmp(message, "tellopponent ", 13)) {
7301       if (appData.icsActive) {
7302         if (loggedOn) {
7303           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7304           SendToICS(buf1);
7305         }
7306       } else {
7307         DisplayNote(message + 13);
7308       }
7309       return;
7310     }
7311     if (!strncmp(message, "tellothers ", 11)) {
7312       if (appData.icsActive) {
7313         if (loggedOn) {
7314           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7315           SendToICS(buf1);
7316         }
7317       }
7318       return;
7319     }
7320     if (!strncmp(message, "tellall ", 8)) {
7321       if (appData.icsActive) {
7322         if (loggedOn) {
7323           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7324           SendToICS(buf1);
7325         }
7326       } else {
7327         DisplayNote(message + 8);
7328       }
7329       return;
7330     }
7331     if (strncmp(message, "warning", 7) == 0) {
7332         /* Undocumented feature, use tellusererror in new code */
7333         DisplayError(message, 0);
7334         return;
7335     }
7336     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7337         strcpy(realname, cps->tidy);
7338         strcat(realname, " query");
7339         AskQuestion(realname, buf2, buf1, cps->pr);
7340         return;
7341     }
7342     /* Commands from the engine directly to ICS.  We don't allow these to be
7343      *  sent until we are logged on. Crafty kibitzes have been known to
7344      *  interfere with the login process.
7345      */
7346     if (loggedOn) {
7347         if (!strncmp(message, "tellics ", 8)) {
7348             SendToICS(message + 8);
7349             SendToICS("\n");
7350             return;
7351         }
7352         if (!strncmp(message, "tellicsnoalias ", 15)) {
7353             SendToICS(ics_prefix);
7354             SendToICS(message + 15);
7355             SendToICS("\n");
7356             return;
7357         }
7358         /* The following are for backward compatibility only */
7359         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7360             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7361             SendToICS(ics_prefix);
7362             SendToICS(message);
7363             SendToICS("\n");
7364             return;
7365         }
7366     }
7367     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7368         return;
7369     }
7370     /*
7371      * If the move is illegal, cancel it and redraw the board.
7372      * Also deal with other error cases.  Matching is rather loose
7373      * here to accommodate engines written before the spec.
7374      */
7375     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7376         strncmp(message, "Error", 5) == 0) {
7377         if (StrStr(message, "name") ||
7378             StrStr(message, "rating") || StrStr(message, "?") ||
7379             StrStr(message, "result") || StrStr(message, "board") ||
7380             StrStr(message, "bk") || StrStr(message, "computer") ||
7381             StrStr(message, "variant") || StrStr(message, "hint") ||
7382             StrStr(message, "random") || StrStr(message, "depth") ||
7383             StrStr(message, "accepted")) {
7384             return;
7385         }
7386         if (StrStr(message, "protover")) {
7387           /* Program is responding to input, so it's apparently done
7388              initializing, and this error message indicates it is
7389              protocol version 1.  So we don't need to wait any longer
7390              for it to initialize and send feature commands. */
7391           FeatureDone(cps, 1);
7392           cps->protocolVersion = 1;
7393           return;
7394         }
7395         cps->maybeThinking = FALSE;
7396
7397         if (StrStr(message, "draw")) {
7398             /* Program doesn't have "draw" command */
7399             cps->sendDrawOffers = 0;
7400             return;
7401         }
7402         if (cps->sendTime != 1 &&
7403             (StrStr(message, "time") || StrStr(message, "otim"))) {
7404           /* Program apparently doesn't have "time" or "otim" command */
7405           cps->sendTime = 0;
7406           return;
7407         }
7408         if (StrStr(message, "analyze")) {
7409             cps->analysisSupport = FALSE;
7410             cps->analyzing = FALSE;
7411             Reset(FALSE, TRUE);
7412             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7413             DisplayError(buf2, 0);
7414             return;
7415         }
7416         if (StrStr(message, "(no matching move)st")) {
7417           /* Special kludge for GNU Chess 4 only */
7418           cps->stKludge = TRUE;
7419           SendTimeControl(cps, movesPerSession, timeControl,
7420                           timeIncrement, appData.searchDepth,
7421                           searchTime);
7422           return;
7423         }
7424         if (StrStr(message, "(no matching move)sd")) {
7425           /* Special kludge for GNU Chess 4 only */
7426           cps->sdKludge = TRUE;
7427           SendTimeControl(cps, movesPerSession, timeControl,
7428                           timeIncrement, appData.searchDepth,
7429                           searchTime);
7430           return;
7431         }
7432         if (!StrStr(message, "llegal")) {
7433             return;
7434         }
7435         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7436             gameMode == IcsIdle) return;
7437         if (forwardMostMove <= backwardMostMove) return;
7438         if (pausing) PauseEvent();
7439       if(appData.forceIllegal) {
7440             // [HGM] illegal: machine refused move; force position after move into it
7441           SendToProgram("force\n", cps);
7442           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7443                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7444                 // when black is to move, while there might be nothing on a2 or black
7445                 // might already have the move. So send the board as if white has the move.
7446                 // But first we must change the stm of the engine, as it refused the last move
7447                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7448                 if(WhiteOnMove(forwardMostMove)) {
7449                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7450                     SendBoard(cps, forwardMostMove); // kludgeless board
7451                 } else {
7452                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7453                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7454                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7455                 }
7456           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7457             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7458                  gameMode == TwoMachinesPlay)
7459               SendToProgram("go\n", cps);
7460             return;
7461       } else
7462         if (gameMode == PlayFromGameFile) {
7463             /* Stop reading this game file */
7464             gameMode = EditGame;
7465             ModeHighlight();
7466         }
7467         currentMove = --forwardMostMove;
7468         DisplayMove(currentMove-1); /* before DisplayMoveError */
7469         SwitchClocks();
7470         DisplayBothClocks();
7471         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7472                 parseList[currentMove], cps->which);
7473         DisplayMoveError(buf1);
7474         DrawPosition(FALSE, boards[currentMove]);
7475
7476         /* [HGM] illegal-move claim should forfeit game when Xboard */
7477         /* only passes fully legal moves                            */
7478         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7479             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7480                                 "False illegal-move claim", GE_XBOARD );
7481         }
7482         return;
7483     }
7484     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7485         /* Program has a broken "time" command that
7486            outputs a string not ending in newline.
7487            Don't use it. */
7488         cps->sendTime = 0;
7489     }
7490
7491     /*
7492      * If chess program startup fails, exit with an error message.
7493      * Attempts to recover here are futile.
7494      */
7495     if ((StrStr(message, "unknown host") != NULL)
7496         || (StrStr(message, "No remote directory") != NULL)
7497         || (StrStr(message, "not found") != NULL)
7498         || (StrStr(message, "No such file") != NULL)
7499         || (StrStr(message, "can't alloc") != NULL)
7500         || (StrStr(message, "Permission denied") != NULL)) {
7501
7502         cps->maybeThinking = FALSE;
7503         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7504                 cps->which, cps->program, cps->host, message);
7505         RemoveInputSource(cps->isr);
7506         DisplayFatalError(buf1, 0, 1);
7507         return;
7508     }
7509
7510     /*
7511      * Look for hint output
7512      */
7513     if (sscanf(message, "Hint: %s", buf1) == 1) {
7514         if (cps == &first && hintRequested) {
7515             hintRequested = FALSE;
7516             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7517                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7518                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7519                                     PosFlags(forwardMostMove),
7520                                     fromY, fromX, toY, toX, promoChar, buf1);
7521                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7522                 DisplayInformation(buf2);
7523             } else {
7524                 /* Hint move could not be parsed!? */
7525               snprintf(buf2, sizeof(buf2),
7526                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7527                         buf1, cps->which);
7528                 DisplayError(buf2, 0);
7529             }
7530         } else {
7531             strcpy(lastHint, buf1);
7532         }
7533         return;
7534     }
7535
7536     /*
7537      * Ignore other messages if game is not in progress
7538      */
7539     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7540         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7541
7542     /*
7543      * look for win, lose, draw, or draw offer
7544      */
7545     if (strncmp(message, "1-0", 3) == 0) {
7546         char *p, *q, *r = "";
7547         p = strchr(message, '{');
7548         if (p) {
7549             q = strchr(p, '}');
7550             if (q) {
7551                 *q = NULLCHAR;
7552                 r = p + 1;
7553             }
7554         }
7555         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7556         return;
7557     } else if (strncmp(message, "0-1", 3) == 0) {
7558         char *p, *q, *r = "";
7559         p = strchr(message, '{');
7560         if (p) {
7561             q = strchr(p, '}');
7562             if (q) {
7563                 *q = NULLCHAR;
7564                 r = p + 1;
7565             }
7566         }
7567         /* Kludge for Arasan 4.1 bug */
7568         if (strcmp(r, "Black resigns") == 0) {
7569             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7570             return;
7571         }
7572         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7573         return;
7574     } else if (strncmp(message, "1/2", 3) == 0) {
7575         char *p, *q, *r = "";
7576         p = strchr(message, '{');
7577         if (p) {
7578             q = strchr(p, '}');
7579             if (q) {
7580                 *q = NULLCHAR;
7581                 r = p + 1;
7582             }
7583         }
7584
7585         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7586         return;
7587
7588     } else if (strncmp(message, "White resign", 12) == 0) {
7589         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7590         return;
7591     } else if (strncmp(message, "Black resign", 12) == 0) {
7592         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7593         return;
7594     } else if (strncmp(message, "White matches", 13) == 0 ||
7595                strncmp(message, "Black matches", 13) == 0   ) {
7596         /* [HGM] ignore GNUShogi noises */
7597         return;
7598     } else if (strncmp(message, "White", 5) == 0 &&
7599                message[5] != '(' &&
7600                StrStr(message, "Black") == NULL) {
7601         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7602         return;
7603     } else if (strncmp(message, "Black", 5) == 0 &&
7604                message[5] != '(') {
7605         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7606         return;
7607     } else if (strcmp(message, "resign") == 0 ||
7608                strcmp(message, "computer resigns") == 0) {
7609         switch (gameMode) {
7610           case MachinePlaysBlack:
7611           case IcsPlayingBlack:
7612             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7613             break;
7614           case MachinePlaysWhite:
7615           case IcsPlayingWhite:
7616             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7617             break;
7618           case TwoMachinesPlay:
7619             if (cps->twoMachinesColor[0] == 'w')
7620               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7621             else
7622               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7623             break;
7624           default:
7625             /* can't happen */
7626             break;
7627         }
7628         return;
7629     } else if (strncmp(message, "opponent mates", 14) == 0) {
7630         switch (gameMode) {
7631           case MachinePlaysBlack:
7632           case IcsPlayingBlack:
7633             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7634             break;
7635           case MachinePlaysWhite:
7636           case IcsPlayingWhite:
7637             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7638             break;
7639           case TwoMachinesPlay:
7640             if (cps->twoMachinesColor[0] == 'w')
7641               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7642             else
7643               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7644             break;
7645           default:
7646             /* can't happen */
7647             break;
7648         }
7649         return;
7650     } else if (strncmp(message, "computer mates", 14) == 0) {
7651         switch (gameMode) {
7652           case MachinePlaysBlack:
7653           case IcsPlayingBlack:
7654             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7655             break;
7656           case MachinePlaysWhite:
7657           case IcsPlayingWhite:
7658             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7659             break;
7660           case TwoMachinesPlay:
7661             if (cps->twoMachinesColor[0] == 'w')
7662               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7663             else
7664               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7665             break;
7666           default:
7667             /* can't happen */
7668             break;
7669         }
7670         return;
7671     } else if (strncmp(message, "checkmate", 9) == 0) {
7672         if (WhiteOnMove(forwardMostMove)) {
7673             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7674         } else {
7675             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7676         }
7677         return;
7678     } else if (strstr(message, "Draw") != NULL ||
7679                strstr(message, "game is a draw") != NULL) {
7680         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7681         return;
7682     } else if (strstr(message, "offer") != NULL &&
7683                strstr(message, "draw") != NULL) {
7684 #if ZIPPY
7685         if (appData.zippyPlay && first.initDone) {
7686             /* Relay offer to ICS */
7687             SendToICS(ics_prefix);
7688             SendToICS("draw\n");
7689         }
7690 #endif
7691         cps->offeredDraw = 2; /* valid until this engine moves twice */
7692         if (gameMode == TwoMachinesPlay) {
7693             if (cps->other->offeredDraw) {
7694                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7695             /* [HGM] in two-machine mode we delay relaying draw offer      */
7696             /* until after we also have move, to see if it is really claim */
7697             }
7698         } else if (gameMode == MachinePlaysWhite ||
7699                    gameMode == MachinePlaysBlack) {
7700           if (userOfferedDraw) {
7701             DisplayInformation(_("Machine accepts your draw offer"));
7702             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7703           } else {
7704             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7705           }
7706         }
7707     }
7708
7709
7710     /*
7711      * Look for thinking output
7712      */
7713     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7714           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7715                                 ) {
7716         int plylev, mvleft, mvtot, curscore, time;
7717         char mvname[MOVE_LEN];
7718         u64 nodes; // [DM]
7719         char plyext;
7720         int ignore = FALSE;
7721         int prefixHint = FALSE;
7722         mvname[0] = NULLCHAR;
7723
7724         switch (gameMode) {
7725           case MachinePlaysBlack:
7726           case IcsPlayingBlack:
7727             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7728             break;
7729           case MachinePlaysWhite:
7730           case IcsPlayingWhite:
7731             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7732             break;
7733           case AnalyzeMode:
7734           case AnalyzeFile:
7735             break;
7736           case IcsObserving: /* [DM] icsEngineAnalyze */
7737             if (!appData.icsEngineAnalyze) ignore = TRUE;
7738             break;
7739           case TwoMachinesPlay:
7740             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7741                 ignore = TRUE;
7742             }
7743             break;
7744           default:
7745             ignore = TRUE;
7746             break;
7747         }
7748
7749         if (!ignore) {
7750             buf1[0] = NULLCHAR;
7751             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7752                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7753
7754                 if (plyext != ' ' && plyext != '\t') {
7755                     time *= 100;
7756                 }
7757
7758                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7759                 if( cps->scoreIsAbsolute && 
7760                     ( gameMode == MachinePlaysBlack ||
7761                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7762                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7763                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7764                      !WhiteOnMove(currentMove)
7765                     ) )
7766                 {
7767                     curscore = -curscore;
7768                 }
7769
7770
7771                 programStats.depth = plylev;
7772                 programStats.nodes = nodes;
7773                 programStats.time = time;
7774                 programStats.score = curscore;
7775                 programStats.got_only_move = 0;
7776
7777                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7778                         int ticklen;
7779
7780                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7781                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7782                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7783                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7784                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7785                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7786                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7787                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7788                 }
7789
7790                 /* Buffer overflow protection */
7791                 if (buf1[0] != NULLCHAR) {
7792                     if (strlen(buf1) >= sizeof(programStats.movelist)
7793                         && appData.debugMode) {
7794                         fprintf(debugFP,
7795                                 "PV is too long; using the first %u bytes.\n",
7796                                 (unsigned) sizeof(programStats.movelist) - 1);
7797                     }
7798
7799                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7800                 } else {
7801                     sprintf(programStats.movelist, " no PV\n");
7802                 }
7803
7804                 if (programStats.seen_stat) {
7805                     programStats.ok_to_send = 1;
7806                 }
7807
7808                 if (strchr(programStats.movelist, '(') != NULL) {
7809                     programStats.line_is_book = 1;
7810                     programStats.nr_moves = 0;
7811                     programStats.moves_left = 0;
7812                 } else {
7813                     programStats.line_is_book = 0;
7814                 }
7815
7816                 SendProgramStatsToFrontend( cps, &programStats );
7817
7818                 /*
7819                     [AS] Protect the thinkOutput buffer from overflow... this
7820                     is only useful if buf1 hasn't overflowed first!
7821                 */
7822                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7823                         plylev,
7824                         (gameMode == TwoMachinesPlay ?
7825                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7826                         ((double) curscore) / 100.0,
7827                         prefixHint ? lastHint : "",
7828                         prefixHint ? " " : "" );
7829
7830                 if( buf1[0] != NULLCHAR ) {
7831                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7832
7833                     if( strlen(buf1) > max_len ) {
7834                         if( appData.debugMode) {
7835                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7836                         }
7837                         buf1[max_len+1] = '\0';
7838                     }
7839
7840                     strcat( thinkOutput, buf1 );
7841                 }
7842
7843                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7844                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7845                     DisplayMove(currentMove - 1);
7846                 }
7847                 return;
7848
7849             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7850                 /* crafty (9.25+) says "(only move) <move>"
7851                  * if there is only 1 legal move
7852                  */
7853                 sscanf(p, "(only move) %s", buf1);
7854                 sprintf(thinkOutput, "%s (only move)", buf1);
7855                 sprintf(programStats.movelist, "%s (only move)", buf1);
7856                 programStats.depth = 1;
7857                 programStats.nr_moves = 1;
7858                 programStats.moves_left = 1;
7859                 programStats.nodes = 1;
7860                 programStats.time = 1;
7861                 programStats.got_only_move = 1;
7862
7863                 /* Not really, but we also use this member to
7864                    mean "line isn't going to change" (Crafty
7865                    isn't searching, so stats won't change) */
7866                 programStats.line_is_book = 1;
7867
7868                 SendProgramStatsToFrontend( cps, &programStats );
7869
7870                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7871                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7872                     DisplayMove(currentMove - 1);
7873                 }
7874                 return;
7875             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7876                               &time, &nodes, &plylev, &mvleft,
7877                               &mvtot, mvname) >= 5) {
7878                 /* The stat01: line is from Crafty (9.29+) in response
7879                    to the "." command */
7880                 programStats.seen_stat = 1;
7881                 cps->maybeThinking = TRUE;
7882
7883                 if (programStats.got_only_move || !appData.periodicUpdates)
7884                   return;
7885
7886                 programStats.depth = plylev;
7887                 programStats.time = time;
7888                 programStats.nodes = nodes;
7889                 programStats.moves_left = mvleft;
7890                 programStats.nr_moves = mvtot;
7891                 strcpy(programStats.move_name, mvname);
7892                 programStats.ok_to_send = 1;
7893                 programStats.movelist[0] = '\0';
7894
7895                 SendProgramStatsToFrontend( cps, &programStats );
7896
7897                 return;
7898
7899             } else if (strncmp(message,"++",2) == 0) {
7900                 /* Crafty 9.29+ outputs this */
7901                 programStats.got_fail = 2;
7902                 return;
7903
7904             } else if (strncmp(message,"--",2) == 0) {
7905                 /* Crafty 9.29+ outputs this */
7906                 programStats.got_fail = 1;
7907                 return;
7908
7909             } else if (thinkOutput[0] != NULLCHAR &&
7910                        strncmp(message, "    ", 4) == 0) {
7911                 unsigned message_len;
7912
7913                 p = message;
7914                 while (*p && *p == ' ') p++;
7915
7916                 message_len = strlen( p );
7917
7918                 /* [AS] Avoid buffer overflow */
7919                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7920                     strcat(thinkOutput, " ");
7921                     strcat(thinkOutput, p);
7922                 }
7923
7924                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7925                     strcat(programStats.movelist, " ");
7926                     strcat(programStats.movelist, p);
7927                 }
7928
7929                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7930                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7931                     DisplayMove(currentMove - 1);
7932                 }
7933                 return;
7934             }
7935         }
7936         else {
7937             buf1[0] = NULLCHAR;
7938
7939             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7940                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7941             {
7942                 ChessProgramStats cpstats;
7943
7944                 if (plyext != ' ' && plyext != '\t') {
7945                     time *= 100;
7946                 }
7947
7948                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7949                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7950                     curscore = -curscore;
7951                 }
7952
7953                 cpstats.depth = plylev;
7954                 cpstats.nodes = nodes;
7955                 cpstats.time = time;
7956                 cpstats.score = curscore;
7957                 cpstats.got_only_move = 0;
7958                 cpstats.movelist[0] = '\0';
7959
7960                 if (buf1[0] != NULLCHAR) {
7961                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7962                 }
7963
7964                 cpstats.ok_to_send = 0;
7965                 cpstats.line_is_book = 0;
7966                 cpstats.nr_moves = 0;
7967                 cpstats.moves_left = 0;
7968
7969                 SendProgramStatsToFrontend( cps, &cpstats );
7970             }
7971         }
7972     }
7973 }
7974
7975
7976 /* Parse a game score from the character string "game", and
7977    record it as the history of the current game.  The game
7978    score is NOT assumed to start from the standard position.
7979    The display is not updated in any way.
7980    */
7981 void
7982 ParseGameHistory(game)
7983      char *game;
7984 {
7985     ChessMove moveType;
7986     int fromX, fromY, toX, toY, boardIndex;
7987     char promoChar;
7988     char *p, *q;
7989     char buf[MSG_SIZ];
7990
7991     if (appData.debugMode)
7992       fprintf(debugFP, "Parsing game history: %s\n", game);
7993
7994     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7995     gameInfo.site = StrSave(appData.icsHost);
7996     gameInfo.date = PGNDate();
7997     gameInfo.round = StrSave("-");
7998
7999     /* Parse out names of players */
8000     while (*game == ' ') game++;
8001     p = buf;
8002     while (*game != ' ') *p++ = *game++;
8003     *p = NULLCHAR;
8004     gameInfo.white = StrSave(buf);
8005     while (*game == ' ') game++;
8006     p = buf;
8007     while (*game != ' ' && *game != '\n') *p++ = *game++;
8008     *p = NULLCHAR;
8009     gameInfo.black = StrSave(buf);
8010
8011     /* Parse moves */
8012     boardIndex = blackPlaysFirst ? 1 : 0;
8013     yynewstr(game);
8014     for (;;) {
8015         yyboardindex = boardIndex;
8016         moveType = (ChessMove) yylex();
8017         switch (moveType) {
8018           case IllegalMove:             /* maybe suicide chess, etc. */
8019   if (appData.debugMode) {
8020     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8021     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8022     setbuf(debugFP, NULL);
8023   }
8024           case WhitePromotionChancellor:
8025           case BlackPromotionChancellor:
8026           case WhitePromotionArchbishop:
8027           case BlackPromotionArchbishop:
8028           case WhitePromotionQueen:
8029           case BlackPromotionQueen:
8030           case WhitePromotionRook:
8031           case BlackPromotionRook:
8032           case WhitePromotionBishop:
8033           case BlackPromotionBishop:
8034           case WhitePromotionKnight:
8035           case BlackPromotionKnight:
8036           case WhitePromotionKing:
8037           case BlackPromotionKing:
8038           case NormalMove:
8039           case WhiteCapturesEnPassant:
8040           case BlackCapturesEnPassant:
8041           case WhiteKingSideCastle:
8042           case WhiteQueenSideCastle:
8043           case BlackKingSideCastle:
8044           case BlackQueenSideCastle:
8045           case WhiteKingSideCastleWild:
8046           case WhiteQueenSideCastleWild:
8047           case BlackKingSideCastleWild:
8048           case BlackQueenSideCastleWild:
8049           /* PUSH Fabien */
8050           case WhiteHSideCastleFR:
8051           case WhiteASideCastleFR:
8052           case BlackHSideCastleFR:
8053           case BlackASideCastleFR:
8054           /* POP Fabien */
8055             fromX = currentMoveString[0] - AAA;
8056             fromY = currentMoveString[1] - ONE;
8057             toX = currentMoveString[2] - AAA;
8058             toY = currentMoveString[3] - ONE;
8059             promoChar = currentMoveString[4];
8060             break;
8061           case WhiteDrop:
8062           case BlackDrop:
8063             fromX = moveType == WhiteDrop ?
8064               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8065             (int) CharToPiece(ToLower(currentMoveString[0]));
8066             fromY = DROP_RANK;
8067             toX = currentMoveString[2] - AAA;
8068             toY = currentMoveString[3] - ONE;
8069             promoChar = NULLCHAR;
8070             break;
8071           case AmbiguousMove:
8072             /* bug? */
8073             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8074   if (appData.debugMode) {
8075     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8076     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8077     setbuf(debugFP, NULL);
8078   }
8079             DisplayError(buf, 0);
8080             return;
8081           case ImpossibleMove:
8082             /* bug? */
8083             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8084   if (appData.debugMode) {
8085     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8086     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8087     setbuf(debugFP, NULL);
8088   }
8089             DisplayError(buf, 0);
8090             return;
8091           case (ChessMove) 0:   /* end of file */
8092             if (boardIndex < backwardMostMove) {
8093                 /* Oops, gap.  How did that happen? */
8094                 DisplayError(_("Gap in move list"), 0);
8095                 return;
8096             }
8097             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8098             if (boardIndex > forwardMostMove) {
8099                 forwardMostMove = boardIndex;
8100             }
8101             return;
8102           case ElapsedTime:
8103             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8104                 strcat(parseList[boardIndex-1], " ");
8105                 strcat(parseList[boardIndex-1], yy_text);
8106             }
8107             continue;
8108           case Comment:
8109           case PGNTag:
8110           case NAG:
8111           default:
8112             /* ignore */
8113             continue;
8114           case WhiteWins:
8115           case BlackWins:
8116           case GameIsDrawn:
8117           case GameUnfinished:
8118             if (gameMode == IcsExamining) {
8119                 if (boardIndex < backwardMostMove) {
8120                     /* Oops, gap.  How did that happen? */
8121                     return;
8122                 }
8123                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8124                 return;
8125             }
8126             gameInfo.result = moveType;
8127             p = strchr(yy_text, '{');
8128             if (p == NULL) p = strchr(yy_text, '(');
8129             if (p == NULL) {
8130                 p = yy_text;
8131                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8132             } else {
8133                 q = strchr(p, *p == '{' ? '}' : ')');
8134                 if (q != NULL) *q = NULLCHAR;
8135                 p++;
8136             }
8137             gameInfo.resultDetails = StrSave(p);
8138             continue;
8139         }
8140         if (boardIndex >= forwardMostMove &&
8141             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8142             backwardMostMove = blackPlaysFirst ? 1 : 0;
8143             return;
8144         }
8145         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8146                                  fromY, fromX, toY, toX, promoChar,
8147                                  parseList[boardIndex]);
8148         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8149         /* currentMoveString is set as a side-effect of yylex */
8150         strcpy(moveList[boardIndex], currentMoveString);
8151         strcat(moveList[boardIndex], "\n");
8152         boardIndex++;
8153         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8154         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8155           case MT_NONE:
8156           case MT_STALEMATE:
8157           default:
8158             break;
8159           case MT_CHECK:
8160             if(gameInfo.variant != VariantShogi)
8161                 strcat(parseList[boardIndex - 1], "+");
8162             break;
8163           case MT_CHECKMATE:
8164           case MT_STAINMATE:
8165             strcat(parseList[boardIndex - 1], "#");
8166             break;
8167         }
8168     }
8169 }
8170
8171
8172 /* Apply a move to the given board  */
8173 void
8174 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8175      int fromX, fromY, toX, toY;
8176      int promoChar;
8177      Board board;
8178 {
8179   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8180   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8181
8182     /* [HGM] compute & store e.p. status and castling rights for new position */
8183     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8184     { int i;
8185
8186       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8187       oldEP = (signed char)board[EP_STATUS];
8188       board[EP_STATUS] = EP_NONE;
8189
8190       if( board[toY][toX] != EmptySquare ) 
8191            board[EP_STATUS] = EP_CAPTURE;  
8192
8193       if( board[fromY][fromX] == WhitePawn ) {
8194            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8195                board[EP_STATUS] = EP_PAWN_MOVE;
8196            if( toY-fromY==2) {
8197                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8198                         gameInfo.variant != VariantBerolina || toX < fromX)
8199                       board[EP_STATUS] = toX | berolina;
8200                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8201                   gameInfo.variant != VariantBerolina || toX > fromX) 
8202                  board[EP_STATUS] = toX;
8203            }
8204       } else
8205       if( board[fromY][fromX] == BlackPawn ) {
8206            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8207                board[EP_STATUS] = EP_PAWN_MOVE; 
8208            if( toY-fromY== -2) {
8209                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8210                         gameInfo.variant != VariantBerolina || toX < fromX)
8211                       board[EP_STATUS] = toX | berolina;
8212                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8213                         gameInfo.variant != VariantBerolina || toX > fromX) 
8214                       board[EP_STATUS] = toX;
8215            }
8216        }
8217
8218        for(i=0; i<nrCastlingRights; i++) {
8219            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8220               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8221              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8222        }
8223
8224     }
8225
8226   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8227   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8228        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8229
8230   if (fromX == toX && fromY == toY) return;
8231
8232   if (fromY == DROP_RANK) {
8233         /* must be first */
8234         piece = board[toY][toX] = (ChessSquare) fromX;
8235   } else {
8236      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8237      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8238      if(gameInfo.variant == VariantKnightmate)
8239          king += (int) WhiteUnicorn - (int) WhiteKing;
8240
8241     /* Code added by Tord: */
8242     /* FRC castling assumed when king captures friendly rook. */
8243     if (board[fromY][fromX] == WhiteKing &&
8244              board[toY][toX] == WhiteRook) {
8245       board[fromY][fromX] = EmptySquare;
8246       board[toY][toX] = EmptySquare;
8247       if(toX > fromX) {
8248         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8249       } else {
8250         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8251       }
8252     } else if (board[fromY][fromX] == BlackKing &&
8253                board[toY][toX] == BlackRook) {
8254       board[fromY][fromX] = EmptySquare;
8255       board[toY][toX] = EmptySquare;
8256       if(toX > fromX) {
8257         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8258       } else {
8259         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8260       }
8261     /* End of code added by Tord */
8262
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_RGHT-1];
8269         board[fromY][BOARD_RGHT-1] = EmptySquare;
8270     } else if (board[fromY][fromX] == king
8271         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8272                && toY == fromY && toX < fromX-1) {
8273         board[fromY][fromX] = EmptySquare;
8274         board[toY][toX] = king;
8275         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8276         board[fromY][BOARD_LEFT] = EmptySquare;
8277     } else if (board[fromY][fromX] == WhitePawn
8278                && toY >= BOARD_HEIGHT-promoRank
8279                && gameInfo.variant != VariantXiangqi
8280                ) {
8281         /* white pawn promotion */
8282         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8283         if (board[toY][toX] == EmptySquare) {
8284             board[toY][toX] = WhiteQueen;
8285         }
8286         if(gameInfo.variant==VariantBughouse ||
8287            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8288             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8289         board[fromY][fromX] = EmptySquare;
8290     } else if ((fromY == BOARD_HEIGHT-4)
8291                && (toX != fromX)
8292                && gameInfo.variant != VariantXiangqi
8293                && gameInfo.variant != VariantBerolina
8294                && (board[fromY][fromX] == WhitePawn)
8295                && (board[toY][toX] == EmptySquare)) {
8296         board[fromY][fromX] = EmptySquare;
8297         board[toY][toX] = WhitePawn;
8298         captured = board[toY - 1][toX];
8299         board[toY - 1][toX] = EmptySquare;
8300     } else if ((fromY == BOARD_HEIGHT-4)
8301                && (toX == fromX)
8302                && gameInfo.variant == VariantBerolina
8303                && (board[fromY][fromX] == WhitePawn)
8304                && (board[toY][toX] == EmptySquare)) {
8305         board[fromY][fromX] = EmptySquare;
8306         board[toY][toX] = WhitePawn;
8307         if(oldEP & EP_BEROLIN_A) {
8308                 captured = board[fromY][fromX-1];
8309                 board[fromY][fromX-1] = EmptySquare;
8310         }else{  captured = board[fromY][fromX+1];
8311                 board[fromY][fromX+1] = EmptySquare;
8312         }
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_RGHT-1];
8319         board[fromY][BOARD_RGHT-1] = EmptySquare;
8320     } else if (board[fromY][fromX] == king
8321         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8322                && toY == fromY && toX < fromX-1) {
8323         board[fromY][fromX] = EmptySquare;
8324         board[toY][toX] = king;
8325         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8326         board[fromY][BOARD_LEFT] = EmptySquare;
8327     } else if (fromY == 7 && fromX == 3
8328                && board[fromY][fromX] == BlackKing
8329                && toY == 7 && toX == 5) {
8330         board[fromY][fromX] = EmptySquare;
8331         board[toY][toX] = BlackKing;
8332         board[fromY][7] = EmptySquare;
8333         board[toY][4] = BlackRook;
8334     } else if (fromY == 7 && fromX == 3
8335                && board[fromY][fromX] == BlackKing
8336                && toY == 7 && toX == 1) {
8337         board[fromY][fromX] = EmptySquare;
8338         board[toY][toX] = BlackKing;
8339         board[fromY][0] = EmptySquare;
8340         board[toY][2] = BlackRook;
8341     } else if (board[fromY][fromX] == BlackPawn
8342                && toY < promoRank
8343                && gameInfo.variant != VariantXiangqi
8344                ) {
8345         /* black pawn promotion */
8346         board[toY][toX] = CharToPiece(ToLower(promoChar));
8347         if (board[toY][toX] == EmptySquare) {
8348             board[toY][toX] = BlackQueen;
8349         }
8350         if(gameInfo.variant==VariantBughouse ||
8351            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8352             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8353         board[fromY][fromX] = EmptySquare;
8354     } else if ((fromY == 3)
8355                && (toX != fromX)
8356                && gameInfo.variant != VariantXiangqi
8357                && gameInfo.variant != VariantBerolina
8358                && (board[fromY][fromX] == BlackPawn)
8359                && (board[toY][toX] == EmptySquare)) {
8360         board[fromY][fromX] = EmptySquare;
8361         board[toY][toX] = BlackPawn;
8362         captured = board[toY + 1][toX];
8363         board[toY + 1][toX] = EmptySquare;
8364     } else if ((fromY == 3)
8365                && (toX == fromX)
8366                && gameInfo.variant == VariantBerolina
8367                && (board[fromY][fromX] == BlackPawn)
8368                && (board[toY][toX] == EmptySquare)) {
8369         board[fromY][fromX] = EmptySquare;
8370         board[toY][toX] = BlackPawn;
8371         if(oldEP & EP_BEROLIN_A) {
8372                 captured = board[fromY][fromX-1];
8373                 board[fromY][fromX-1] = EmptySquare;
8374         }else{  captured = board[fromY][fromX+1];
8375                 board[fromY][fromX+1] = EmptySquare;
8376         }
8377     } else {
8378         board[toY][toX] = board[fromY][fromX];
8379         board[fromY][fromX] = EmptySquare;
8380     }
8381
8382     /* [HGM] now we promote for Shogi, if needed */
8383     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8384         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8385   }
8386
8387     if (gameInfo.holdingsWidth != 0) {
8388
8389       /* !!A lot more code needs to be written to support holdings  */
8390       /* [HGM] OK, so I have written it. Holdings are stored in the */
8391       /* penultimate board files, so they are automaticlly stored   */
8392       /* in the game history.                                       */
8393       if (fromY == DROP_RANK) {
8394         /* Delete from holdings, by decreasing count */
8395         /* and erasing image if necessary            */
8396         p = (int) fromX;
8397         if(p < (int) BlackPawn) { /* white drop */
8398              p -= (int)WhitePawn;
8399                  p = PieceToNumber((ChessSquare)p);
8400              if(p >= gameInfo.holdingsSize) p = 0;
8401              if(--board[p][BOARD_WIDTH-2] <= 0)
8402                   board[p][BOARD_WIDTH-1] = EmptySquare;
8403              if((int)board[p][BOARD_WIDTH-2] < 0)
8404                         board[p][BOARD_WIDTH-2] = 0;
8405         } else {                  /* black drop */
8406              p -= (int)BlackPawn;
8407                  p = PieceToNumber((ChessSquare)p);
8408              if(p >= gameInfo.holdingsSize) p = 0;
8409              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8410                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8411              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8412                         board[BOARD_HEIGHT-1-p][1] = 0;
8413         }
8414       }
8415       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8416           && gameInfo.variant != VariantBughouse        ) {
8417         /* [HGM] holdings: Add to holdings, if holdings exist */
8418         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8419                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8420                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8421         }
8422         p = (int) captured;
8423         if (p >= (int) BlackPawn) {
8424           p -= (int)BlackPawn;
8425           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8426                   /* in Shogi restore piece to its original  first */
8427                   captured = (ChessSquare) (DEMOTED captured);
8428                   p = DEMOTED p;
8429           }
8430           p = PieceToNumber((ChessSquare)p);
8431           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8432           board[p][BOARD_WIDTH-2]++;
8433           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8434         } else {
8435           p -= (int)WhitePawn;
8436           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8437                   captured = (ChessSquare) (DEMOTED captured);
8438                   p = DEMOTED p;
8439           }
8440           p = PieceToNumber((ChessSquare)p);
8441           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8442           board[BOARD_HEIGHT-1-p][1]++;
8443           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8444         }
8445       }
8446     } else if (gameInfo.variant == VariantAtomic) {
8447       if (captured != EmptySquare) {
8448         int y, x;
8449         for (y = toY-1; y <= toY+1; y++) {
8450           for (x = toX-1; x <= toX+1; x++) {
8451             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8452                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8453               board[y][x] = EmptySquare;
8454             }
8455           }
8456         }
8457         board[toY][toX] = EmptySquare;
8458       }
8459     }
8460     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8461         /* [HGM] Shogi promotions */
8462         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8463     }
8464
8465     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8466                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8467         // [HGM] superchess: take promotion piece out of holdings
8468         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8469         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8470             if(!--board[k][BOARD_WIDTH-2])
8471                 board[k][BOARD_WIDTH-1] = EmptySquare;
8472         } else {
8473             if(!--board[BOARD_HEIGHT-1-k][1])
8474                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8475         }
8476     }
8477
8478 }
8479
8480 /* Updates forwardMostMove */
8481 void
8482 MakeMove(fromX, fromY, toX, toY, promoChar)
8483      int fromX, fromY, toX, toY;
8484      int promoChar;
8485 {
8486 //    forwardMostMove++; // [HGM] bare: moved downstream
8487
8488     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8489         int timeLeft; static int lastLoadFlag=0; int king, piece;
8490         piece = boards[forwardMostMove][fromY][fromX];
8491         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8492         if(gameInfo.variant == VariantKnightmate)
8493             king += (int) WhiteUnicorn - (int) WhiteKing;
8494         if(forwardMostMove == 0) {
8495             if(blackPlaysFirst)
8496                 fprintf(serverMoves, "%s;", second.tidy);
8497             fprintf(serverMoves, "%s;", first.tidy);
8498             if(!blackPlaysFirst)
8499                 fprintf(serverMoves, "%s;", second.tidy);
8500         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8501         lastLoadFlag = loadFlag;
8502         // print base move
8503         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8504         // print castling suffix
8505         if( toY == fromY && piece == king ) {
8506             if(toX-fromX > 1)
8507                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8508             if(fromX-toX >1)
8509                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8510         }
8511         // e.p. suffix
8512         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8513              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8514              boards[forwardMostMove][toY][toX] == EmptySquare
8515              && fromX != toX )
8516                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8517         // promotion suffix
8518         if(promoChar != NULLCHAR)
8519                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8520         if(!loadFlag) {
8521             fprintf(serverMoves, "/%d/%d",
8522                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8523             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8524             else                      timeLeft = blackTimeRemaining/1000;
8525             fprintf(serverMoves, "/%d", timeLeft);
8526         }
8527         fflush(serverMoves);
8528     }
8529
8530     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8531       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8532                         0, 1);
8533       return;
8534     }
8535     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8536     if (commentList[forwardMostMove+1] != NULL) {
8537         free(commentList[forwardMostMove+1]);
8538         commentList[forwardMostMove+1] = NULL;
8539     }
8540     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8541     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8542     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8543     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8544     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8545     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8546     gameInfo.result = GameUnfinished;
8547     if (gameInfo.resultDetails != NULL) {
8548         free(gameInfo.resultDetails);
8549         gameInfo.resultDetails = NULL;
8550     }
8551     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8552                               moveList[forwardMostMove - 1]);
8553     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8554                              PosFlags(forwardMostMove - 1),
8555                              fromY, fromX, toY, toX, promoChar,
8556                              parseList[forwardMostMove - 1]);
8557     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8558       case MT_NONE:
8559       case MT_STALEMATE:
8560       default:
8561         break;
8562       case MT_CHECK:
8563         if(gameInfo.variant != VariantShogi)
8564             strcat(parseList[forwardMostMove - 1], "+");
8565         break;
8566       case MT_CHECKMATE:
8567       case MT_STAINMATE:
8568         strcat(parseList[forwardMostMove - 1], "#");
8569         break;
8570     }
8571     if (appData.debugMode) {
8572         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8573     }
8574
8575 }
8576
8577 /* Updates currentMove if not pausing */
8578 void
8579 ShowMove(fromX, fromY, toX, toY)
8580 {
8581     int instant = (gameMode == PlayFromGameFile) ?
8582         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8583
8584     if(appData.noGUI) return;
8585
8586     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8587       {
8588         if (!instant)
8589           {
8590             if (forwardMostMove == currentMove + 1)
8591               {
8592 //TODO
8593 //              AnimateMove(boards[forwardMostMove - 1],
8594 //                          fromX, fromY, toX, toY);
8595               }
8596             if (appData.highlightLastMove)
8597               {
8598                 SetHighlights(fromX, fromY, toX, toY);
8599               }
8600           }
8601         currentMove = forwardMostMove;
8602     }
8603
8604     if (instant) return;
8605
8606     DisplayMove(currentMove - 1);
8607     DrawPosition(FALSE, boards[currentMove]);
8608     DisplayBothClocks();
8609     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8610
8611     return;
8612 }
8613
8614 void SendEgtPath(ChessProgramState *cps)
8615 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8616         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8617
8618         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8619
8620         while(*p) {
8621             char c, *q = name+1, *r, *s;
8622
8623             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8624             while(*p && *p != ',') *q++ = *p++;
8625             *q++ = ':'; *q = 0;
8626             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8627                 strcmp(name, ",nalimov:") == 0 ) {
8628                 // take nalimov path from the menu-changeable option first, if it is defined
8629                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8630                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8631             } else
8632             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8633                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8634                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8635                 s = r = StrStr(s, ":") + 1; // beginning of path info
8636                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8637                 c = *r; *r = 0;             // temporarily null-terminate path info
8638                     *--q = 0;               // strip of trailig ':' from name
8639                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8640                 *r = c;
8641                 SendToProgram(buf,cps);     // send egtbpath command for this format
8642             }
8643             if(*p == ',') p++; // read away comma to position for next format name
8644         }
8645 }
8646
8647 void
8648 InitChessProgram(cps, setup)
8649      ChessProgramState *cps;
8650      int setup; /* [HGM] needed to setup FRC opening position */
8651 {
8652     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8653     if (appData.noChessProgram) return;
8654     hintRequested = FALSE;
8655     bookRequested = FALSE;
8656
8657     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8658     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8659     if(cps->memSize) { /* [HGM] memory */
8660         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8661         SendToProgram(buf, cps);
8662     }
8663     SendEgtPath(cps); /* [HGM] EGT */
8664     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8665         sprintf(buf, "cores %d\n", appData.smpCores);
8666         SendToProgram(buf, cps);
8667     }
8668
8669     SendToProgram(cps->initString, cps);
8670     if (gameInfo.variant != VariantNormal &&
8671         gameInfo.variant != VariantLoadable
8672         /* [HGM] also send variant if board size non-standard */
8673         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8674                                             ) {
8675       char *v = VariantName(gameInfo.variant);
8676       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8677         /* [HGM] in protocol 1 we have to assume all variants valid */
8678         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8679         DisplayFatalError(buf, 0, 1);
8680         return;
8681       }
8682
8683       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8684       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8685       if( gameInfo.variant == VariantXiangqi )
8686            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8687       if( gameInfo.variant == VariantShogi )
8688            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8689       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8690            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8691       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8692                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8693            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8694       if( gameInfo.variant == VariantCourier )
8695            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8696       if( gameInfo.variant == VariantSuper )
8697            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8698       if( gameInfo.variant == VariantGreat )
8699            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8700
8701       if(overruled) {
8702            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8703                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8704            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8705            if(StrStr(cps->variants, b) == NULL) {
8706                // specific sized variant not known, check if general sizing allowed
8707                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8708                    if(StrStr(cps->variants, "boardsize") == NULL) {
8709                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8710                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8711                        DisplayFatalError(buf, 0, 1);
8712                        return;
8713                    }
8714                    /* [HGM] here we really should compare with the maximum supported board size */
8715                }
8716            }
8717       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8718       sprintf(buf, "variant %s\n", b);
8719       SendToProgram(buf, cps);
8720     }
8721     currentlyInitializedVariant = gameInfo.variant;
8722
8723     /* [HGM] send opening position in FRC to first engine */
8724     if(setup) {
8725           SendToProgram("force\n", cps);
8726           SendBoard(cps, 0);
8727           /* engine is now in force mode! Set flag to wake it up after first move. */
8728           setboardSpoiledMachineBlack = 1;
8729     }
8730
8731     if (cps->sendICS) {
8732       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8733       SendToProgram(buf, cps);
8734     }
8735     cps->maybeThinking = FALSE;
8736     cps->offeredDraw = 0;
8737     if (!appData.icsActive) {
8738         SendTimeControl(cps, movesPerSession, timeControl,
8739                         timeIncrement, appData.searchDepth,
8740                         searchTime);
8741     }
8742     if (appData.showThinking
8743         // [HGM] thinking: four options require thinking output to be sent
8744         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8745                                 ) {
8746         SendToProgram("post\n", cps);
8747     }
8748     SendToProgram("hard\n", cps);
8749     if (!appData.ponderNextMove) {
8750         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8751            it without being sure what state we are in first.  "hard"
8752            is not a toggle, so that one is OK.
8753          */
8754         SendToProgram("easy\n", cps);
8755     }
8756     if (cps->usePing) {
8757       sprintf(buf, "ping %d\n", ++cps->lastPing);
8758       SendToProgram(buf, cps);
8759     }
8760     cps->initDone = TRUE;
8761 }
8762
8763
8764 void
8765 StartChessProgram(cps)
8766      ChessProgramState *cps;
8767 {
8768     char buf[MSG_SIZ];
8769     int err;
8770
8771     if (appData.noChessProgram) return;
8772     cps->initDone = FALSE;
8773
8774     if (strcmp(cps->host, "localhost") == 0) {
8775         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8776     } else if (*appData.remoteShell == NULLCHAR) {
8777         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8778     } else {
8779         if (*appData.remoteUser == NULLCHAR) {
8780           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8781                     cps->program);
8782         } else {
8783           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8784                     cps->host, appData.remoteUser, cps->program);
8785         }
8786         err = StartChildProcess(buf, "", &cps->pr);
8787     }
8788
8789     if (err != 0) {
8790         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8791         DisplayFatalError(buf, err, 1);
8792         cps->pr = NoProc;
8793         cps->isr = NULL;
8794         return;
8795     }
8796
8797     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8798     if (cps->protocolVersion > 1) {
8799       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8800       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8801       cps->comboCnt = 0;  //                and values of combo boxes
8802       SendToProgram(buf, cps);
8803     } else {
8804       SendToProgram("xboard\n", cps);
8805     }
8806 }
8807
8808
8809 void
8810 TwoMachinesEventIfReady P((void))
8811 {
8812   if (first.lastPing != first.lastPong) {
8813     DisplayMessage("", _("Waiting for first chess program"));
8814     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8815     return;
8816   }
8817   if (second.lastPing != second.lastPong) {
8818     DisplayMessage("", _("Waiting for second chess program"));
8819     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8820     return;
8821   }
8822   ThawUI();
8823   TwoMachinesEvent();
8824 }
8825
8826 void
8827 NextMatchGame P((void))
8828 {
8829     int index; /* [HGM] autoinc: step load index during match */
8830     Reset(FALSE, TRUE);
8831     if (*appData.loadGameFile != NULLCHAR) {
8832         index = appData.loadGameIndex;
8833         if(index < 0) { // [HGM] autoinc
8834             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8835             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8836         }
8837         LoadGameFromFile(appData.loadGameFile,
8838                          index,
8839                          appData.loadGameFile, FALSE);
8840     } else if (*appData.loadPositionFile != NULLCHAR) {
8841         index = appData.loadPositionIndex;
8842         if(index < 0) { // [HGM] autoinc
8843             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8844             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8845         }
8846         LoadPositionFromFile(appData.loadPositionFile,
8847                              index,
8848                              appData.loadPositionFile);
8849     }
8850     TwoMachinesEventIfReady();
8851 }
8852
8853 void UserAdjudicationEvent( int result )
8854 {
8855     ChessMove gameResult = GameIsDrawn;
8856
8857     if( result > 0 ) {
8858         gameResult = WhiteWins;
8859     }
8860     else if( result < 0 ) {
8861         gameResult = BlackWins;
8862     }
8863
8864     if( gameMode == TwoMachinesPlay ) {
8865         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8866     }
8867 }
8868
8869
8870 // [HGM] save: calculate checksum of game to make games easily identifiable
8871 int StringCheckSum(char *s)
8872 {
8873         int i = 0;
8874         if(s==NULL) return 0;
8875         while(*s) i = i*259 + *s++;
8876         return i;
8877 }
8878
8879 int GameCheckSum()
8880 {
8881         int i, sum=0;
8882         for(i=backwardMostMove; i<forwardMostMove; i++) {
8883                 sum += pvInfoList[i].depth;
8884                 sum += StringCheckSum(parseList[i]);
8885                 sum += StringCheckSum(commentList[i]);
8886                 sum *= 261;
8887         }
8888         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8889         return sum + StringCheckSum(commentList[i]);
8890 } // end of save patch
8891
8892 void
8893 GameEnds(result, resultDetails, whosays)
8894      ChessMove result;
8895      char *resultDetails;
8896      int whosays;
8897 {
8898     GameMode nextGameMode;
8899     int isIcsGame;
8900     char buf[MSG_SIZ];
8901
8902     if(endingGame) return; /* [HGM] crash: forbid recursion */
8903     endingGame = 1;
8904
8905     if (appData.debugMode) {
8906       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8907               result, resultDetails ? resultDetails : "(null)", whosays);
8908     }
8909
8910     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8911         /* If we are playing on ICS, the server decides when the
8912            game is over, but the engine can offer to draw, claim
8913            a draw, or resign.
8914          */
8915 #if ZIPPY
8916         if (appData.zippyPlay && first.initDone) {
8917             if (result == GameIsDrawn) {
8918                 /* In case draw still needs to be claimed */
8919                 SendToICS(ics_prefix);
8920                 SendToICS("draw\n");
8921             } else if (StrCaseStr(resultDetails, "resign")) {
8922                 SendToICS(ics_prefix);
8923                 SendToICS("resign\n");
8924             }
8925         }
8926 #endif
8927         endingGame = 0; /* [HGM] crash */
8928         return;
8929     }
8930
8931     /* If we're loading the game from a file, stop */
8932     if (whosays == GE_FILE) {
8933       (void) StopLoadGameTimer();
8934       gameFileFP = NULL;
8935     }
8936
8937     /* Cancel draw offers */
8938     first.offeredDraw = second.offeredDraw = 0;
8939
8940     /* If this is an ICS game, only ICS can really say it's done;
8941        if not, anyone can. */
8942     isIcsGame = (gameMode == IcsPlayingWhite ||
8943                  gameMode == IcsPlayingBlack ||
8944                  gameMode == IcsObserving    ||
8945                  gameMode == IcsExamining);
8946
8947     if (!isIcsGame || whosays == GE_ICS) {
8948         /* OK -- not an ICS game, or ICS said it was done */
8949         StopClocks();
8950         if (!isIcsGame && !appData.noChessProgram)
8951           SetUserThinkingEnables();
8952
8953         /* [HGM] if a machine claims the game end we verify this claim */
8954         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8955             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8956                 char claimer;
8957                 ChessMove trueResult = (ChessMove) -1;
8958
8959                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8960                                             first.twoMachinesColor[0] :
8961                                             second.twoMachinesColor[0] ;
8962
8963                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8964                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8965                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8966                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8967                 } else
8968                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8969                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8970                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8971                 } else
8972                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8973                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8974                 }
8975
8976                 // now verify win claims, but not in drop games, as we don't understand those yet
8977                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8978                                                  || gameInfo.variant == VariantGreat) &&
8979                     (result == WhiteWins && claimer == 'w' ||
8980                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8981                       if (appData.debugMode) {
8982                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8983                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8984                       }
8985                       if(result != trueResult) {
8986                               sprintf(buf, "False win claim: '%s'", resultDetails);
8987                               result = claimer == 'w' ? BlackWins : WhiteWins;
8988                               resultDetails = buf;
8989                       }
8990                 } else
8991                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8992                     && (forwardMostMove <= backwardMostMove ||
8993                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8994                         (claimer=='b')==(forwardMostMove&1))
8995                                                                                   ) {
8996                       /* [HGM] verify: draws that were not flagged are false claims */
8997                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8998                       result = claimer == 'w' ? BlackWins : WhiteWins;
8999                       resultDetails = buf;
9000                 }
9001                 /* (Claiming a loss is accepted no questions asked!) */
9002             }
9003
9004             /* [HGM] bare: don't allow bare King to win */
9005             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9006                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9007                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9008                && result != GameIsDrawn)
9009             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9010                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9011                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9012                         if(p >= 0 && p <= (int)WhiteKing) k++;
9013                 }
9014                 if (appData.debugMode) {
9015                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9016                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9017                 }
9018                 if(k <= 1) {
9019                         result = GameIsDrawn;
9020                         sprintf(buf, "%s but bare king", resultDetails);
9021                         resultDetails = buf;
9022                 }
9023             }
9024         }
9025
9026         if(serverMoves != NULL && !loadFlag) { char c = '=';
9027             if(result==WhiteWins) c = '+';
9028             if(result==BlackWins) c = '-';
9029             if(resultDetails != NULL)
9030                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9031         }
9032         if (resultDetails != NULL) {
9033             gameInfo.result = result;
9034             gameInfo.resultDetails = StrSave(resultDetails);
9035
9036             /* display last move only if game was not loaded from file */
9037             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9038                 DisplayMove(currentMove - 1);
9039
9040             if (forwardMostMove != 0) {
9041                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9042                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9043                                                                 ) {
9044                     if (*appData.saveGameFile != NULLCHAR) {
9045                         SaveGameToFile(appData.saveGameFile, TRUE);
9046                     } else if (appData.autoSaveGames) {
9047                         AutoSaveGame();
9048                     }
9049                     if (*appData.savePositionFile != NULLCHAR) {
9050                         SavePositionToFile(appData.savePositionFile);
9051                     }
9052                 }
9053             }
9054
9055             /* Tell program how game ended in case it is learning */
9056             /* [HGM] Moved this to after saving the PGN, just in case */
9057             /* engine died and we got here through time loss. In that */
9058             /* case we will get a fatal error writing the pipe, which */
9059             /* would otherwise lose us the PGN.                       */
9060             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9061             /* output during GameEnds should never be fatal anymore   */
9062             if (gameMode == MachinePlaysWhite ||
9063                 gameMode == MachinePlaysBlack ||
9064                 gameMode == TwoMachinesPlay ||
9065                 gameMode == IcsPlayingWhite ||
9066                 gameMode == IcsPlayingBlack ||
9067                 gameMode == BeginningOfGame) {
9068                 char buf[MSG_SIZ];
9069                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9070                         resultDetails);
9071                 if (first.pr != NoProc) {
9072                     SendToProgram(buf, &first);
9073                 }
9074                 if (second.pr != NoProc &&
9075                     gameMode == TwoMachinesPlay) {
9076                     SendToProgram(buf, &second);
9077                 }
9078             }
9079         }
9080
9081         if (appData.icsActive) {
9082             if (appData.quietPlay &&
9083                 (gameMode == IcsPlayingWhite ||
9084                  gameMode == IcsPlayingBlack)) {
9085                 SendToICS(ics_prefix);
9086                 SendToICS("set shout 1\n");
9087             }
9088             nextGameMode = IcsIdle;
9089             ics_user_moved = FALSE;
9090             /* clean up premove.  It's ugly when the game has ended and the
9091              * premove highlights are still on the board.
9092              */
9093             if (gotPremove) {
9094               gotPremove = FALSE;
9095               ClearPremoveHighlights();
9096               DrawPosition(FALSE, boards[currentMove]);
9097             }
9098             if (whosays == GE_ICS) {
9099                 switch (result) {
9100                 case WhiteWins:
9101                     if (gameMode == IcsPlayingWhite)
9102                         PlayIcsWinSound();
9103                     else if(gameMode == IcsPlayingBlack)
9104                         PlayIcsLossSound();
9105                     break;
9106                 case BlackWins:
9107                     if (gameMode == IcsPlayingBlack)
9108                         PlayIcsWinSound();
9109                     else if(gameMode == IcsPlayingWhite)
9110                         PlayIcsLossSound();
9111                     break;
9112                 case GameIsDrawn:
9113                     PlayIcsDrawSound();
9114                     break;
9115                 default:
9116                     PlayIcsUnfinishedSound();
9117                 }
9118             }
9119         } else if (gameMode == EditGame ||
9120                    gameMode == PlayFromGameFile ||
9121                    gameMode == AnalyzeMode ||
9122                    gameMode == AnalyzeFile) {
9123             nextGameMode = gameMode;
9124         } else {
9125             nextGameMode = EndOfGame;
9126         }
9127         pausing = FALSE;
9128         ModeHighlight();
9129     } else {
9130         nextGameMode = gameMode;
9131     }
9132
9133     if (appData.noChessProgram) {
9134         gameMode = nextGameMode;
9135         ModeHighlight();
9136         endingGame = 0; /* [HGM] crash */
9137         return;
9138     }
9139
9140     if (first.reuse) {
9141         /* Put first chess program into idle state */
9142         if (first.pr != NoProc &&
9143             (gameMode == MachinePlaysWhite ||
9144              gameMode == MachinePlaysBlack ||
9145              gameMode == TwoMachinesPlay ||
9146              gameMode == IcsPlayingWhite ||
9147              gameMode == IcsPlayingBlack ||
9148              gameMode == BeginningOfGame)) {
9149             SendToProgram("force\n", &first);
9150             if (first.usePing) {
9151               char buf[MSG_SIZ];
9152               sprintf(buf, "ping %d\n", ++first.lastPing);
9153               SendToProgram(buf, &first);
9154             }
9155         }
9156     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9157         /* Kill off first chess program */
9158         if (first.isr != NULL)
9159           RemoveInputSource(first.isr);
9160         first.isr = NULL;
9161
9162         if (first.pr != NoProc) {
9163             ExitAnalyzeMode();
9164             DoSleep( appData.delayBeforeQuit );
9165             SendToProgram("quit\n", &first);
9166             DoSleep( appData.delayAfterQuit );
9167             DestroyChildProcess(first.pr, first.useSigterm);
9168         }
9169         first.pr = NoProc;
9170     }
9171     if (second.reuse) {
9172         /* Put second chess program into idle state */
9173         if (second.pr != NoProc &&
9174             gameMode == TwoMachinesPlay) {
9175             SendToProgram("force\n", &second);
9176             if (second.usePing) {
9177               char buf[MSG_SIZ];
9178               sprintf(buf, "ping %d\n", ++second.lastPing);
9179               SendToProgram(buf, &second);
9180             }
9181         }
9182     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9183         /* Kill off second chess program */
9184         if (second.isr != NULL)
9185           RemoveInputSource(second.isr);
9186         second.isr = NULL;
9187
9188         if (second.pr != NoProc) {
9189             DoSleep( appData.delayBeforeQuit );
9190             SendToProgram("quit\n", &second);
9191             DoSleep( appData.delayAfterQuit );
9192             DestroyChildProcess(second.pr, second.useSigterm);
9193         }
9194         second.pr = NoProc;
9195     }
9196
9197     if (matchMode && gameMode == TwoMachinesPlay) {
9198         switch (result) {
9199         case WhiteWins:
9200           if (first.twoMachinesColor[0] == 'w') {
9201             first.matchWins++;
9202           } else {
9203             second.matchWins++;
9204           }
9205           break;
9206         case BlackWins:
9207           if (first.twoMachinesColor[0] == 'b') {
9208             first.matchWins++;
9209           } else {
9210             second.matchWins++;
9211           }
9212           break;
9213         default:
9214           break;
9215         }
9216         if (matchGame < appData.matchGames) {
9217             char *tmp;
9218             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9219                 tmp = first.twoMachinesColor;
9220                 first.twoMachinesColor = second.twoMachinesColor;
9221                 second.twoMachinesColor = tmp;
9222             }
9223             gameMode = nextGameMode;
9224             matchGame++;
9225             if(appData.matchPause>10000 || appData.matchPause<10)
9226                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9227             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9228             endingGame = 0; /* [HGM] crash */
9229             return;
9230         } else {
9231             char buf[MSG_SIZ];
9232             gameMode = nextGameMode;
9233             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9234                     first.tidy, second.tidy,
9235                     first.matchWins, second.matchWins,
9236                     appData.matchGames - (first.matchWins + second.matchWins));
9237             DisplayFatalError(buf, 0, 0);
9238         }
9239     }
9240     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9241         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9242       ExitAnalyzeMode();
9243     gameMode = nextGameMode;
9244     ModeHighlight();
9245     endingGame = 0;  /* [HGM] crash */
9246 }
9247
9248 /* Assumes program was just initialized (initString sent).
9249    Leaves program in force mode. */
9250 void
9251 FeedMovesToProgram(cps, upto)
9252      ChessProgramState *cps;
9253      int upto;
9254 {
9255     int i;
9256
9257     if (appData.debugMode)
9258       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9259               startedFromSetupPosition ? "position and " : "",
9260               backwardMostMove, upto, cps->which);
9261     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9262         // [HGM] variantswitch: make engine aware of new variant
9263         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9264                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9265         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9266         SendToProgram(buf, cps);
9267         currentlyInitializedVariant = gameInfo.variant;
9268     }
9269     SendToProgram("force\n", cps);
9270     if (startedFromSetupPosition) {
9271         SendBoard(cps, backwardMostMove);
9272     if (appData.debugMode) {
9273         fprintf(debugFP, "feedMoves\n");
9274     }
9275     }
9276     for (i = backwardMostMove; i < upto; i++) {
9277         SendMoveToProgram(i, cps);
9278     }
9279 }
9280
9281
9282 void
9283 ResurrectChessProgram()
9284 {
9285      /* The chess program may have exited.
9286         If so, restart it and feed it all the moves made so far. */
9287
9288     if (appData.noChessProgram || first.pr != NoProc) return;
9289
9290     StartChessProgram(&first);
9291     InitChessProgram(&first, FALSE);
9292     FeedMovesToProgram(&first, currentMove);
9293
9294     if (!first.sendTime) {
9295         /* can't tell gnuchess what its clock should read,
9296            so we bow to its notion. */
9297         ResetClocks();
9298         timeRemaining[0][currentMove] = whiteTimeRemaining;
9299         timeRemaining[1][currentMove] = blackTimeRemaining;
9300     }
9301
9302     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9303                 appData.icsEngineAnalyze) && first.analysisSupport) {
9304       SendToProgram("analyze\n", &first);
9305       first.analyzing = TRUE;
9306     }
9307 }
9308
9309 /*
9310  * Button procedures
9311  */
9312 void
9313 Reset(redraw, init)
9314      int redraw, init;
9315 {
9316     int i;
9317
9318     if (appData.debugMode) {
9319         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9320                 redraw, init, gameMode);
9321     }
9322     CleanupTail(); // [HGM] vari: delete any stored variations
9323     pausing = pauseExamInvalid = FALSE;
9324     startedFromSetupPosition = blackPlaysFirst = FALSE;
9325     firstMove = TRUE;
9326     whiteFlag = blackFlag = FALSE;
9327     userOfferedDraw = FALSE;
9328     hintRequested = bookRequested = FALSE;
9329     first.maybeThinking = FALSE;
9330     second.maybeThinking = FALSE;
9331     first.bookSuspend = FALSE; // [HGM] book
9332     second.bookSuspend = FALSE;
9333     thinkOutput[0] = NULLCHAR;
9334     lastHint[0] = NULLCHAR;
9335     ClearGameInfo(&gameInfo);
9336     gameInfo.variant = StringToVariant(appData.variant);
9337     ics_user_moved = ics_clock_paused = FALSE;
9338     ics_getting_history = H_FALSE;
9339     ics_gamenum = -1;
9340     white_holding[0] = black_holding[0] = NULLCHAR;
9341     ClearProgramStats();
9342     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9343
9344     ResetFrontEnd();
9345     ClearHighlights();
9346     flipView = appData.flipView;
9347     ClearPremoveHighlights();
9348     gotPremove = FALSE;
9349     alarmSounded = FALSE;
9350
9351     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9352     if(appData.serverMovesName != NULL) {
9353         /* [HGM] prepare to make moves file for broadcasting */
9354         clock_t t = clock();
9355         if(serverMoves != NULL) fclose(serverMoves);
9356         serverMoves = fopen(appData.serverMovesName, "r");
9357         if(serverMoves != NULL) {
9358             fclose(serverMoves);
9359             /* delay 15 sec before overwriting, so all clients can see end */
9360             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9361         }
9362         serverMoves = fopen(appData.serverMovesName, "w");
9363     }
9364
9365     ExitAnalyzeMode();
9366     gameMode = BeginningOfGame;
9367     ModeHighlight();
9368
9369     if(appData.icsActive) gameInfo.variant = VariantNormal;
9370     currentMove = forwardMostMove = backwardMostMove = 0;
9371     InitPosition(redraw);
9372     for (i = 0; i < MAX_MOVES; i++) {
9373         if (commentList[i] != NULL) {
9374             free(commentList[i]);
9375             commentList[i] = NULL;
9376         }
9377     }
9378
9379     ResetClocks();
9380     timeRemaining[0][0] = whiteTimeRemaining;
9381     timeRemaining[1][0] = blackTimeRemaining;
9382     if (first.pr == NULL) {
9383         StartChessProgram(&first);
9384     }
9385     if (init) {
9386             InitChessProgram(&first, startedFromSetupPosition);
9387     }
9388
9389     DisplayTitle("");
9390     DisplayMessage("", "");
9391     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9392     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9393     return;
9394 }
9395
9396 void
9397 AutoPlayGameLoop()
9398 {
9399     for (;;) {
9400         if (!AutoPlayOneMove())
9401           return;
9402         if (matchMode || appData.timeDelay == 0)
9403           continue;
9404         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9405           return;
9406         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9407         break;
9408     }
9409 }
9410
9411
9412 int
9413 AutoPlayOneMove()
9414 {
9415     int fromX, fromY, toX, toY;
9416
9417     if (appData.debugMode) {
9418       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9419     }
9420
9421     if (gameMode != PlayFromGameFile)
9422       return FALSE;
9423
9424     if (currentMove >= forwardMostMove) {
9425       gameMode = EditGame;
9426       ModeHighlight();
9427
9428       /* [AS] Clear current move marker at the end of a game */
9429       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9430
9431       return FALSE;
9432     }
9433
9434     toX = moveList[currentMove][2] - AAA;
9435     toY = moveList[currentMove][3] - ONE;
9436
9437     if (moveList[currentMove][1] == '@') {
9438         if (appData.highlightLastMove) {
9439             SetHighlights(-1, -1, toX, toY);
9440         }
9441     } else {
9442         fromX = moveList[currentMove][0] - AAA;
9443         fromY = moveList[currentMove][1] - ONE;
9444
9445         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9446
9447         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9448
9449         if (appData.highlightLastMove) {
9450             SetHighlights(fromX, fromY, toX, toY);
9451         }
9452     }
9453     DisplayMove(currentMove);
9454     SendMoveToProgram(currentMove++, &first);
9455     DisplayBothClocks();
9456     DrawPosition(FALSE, boards[currentMove]);
9457     // [HGM] PV info: always display, routine tests if empty
9458     DisplayComment(currentMove - 1, commentList[currentMove]);
9459     return TRUE;
9460 }
9461
9462
9463 int
9464 LoadGameOneMove(readAhead)
9465      ChessMove readAhead;
9466 {
9467     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9468     char promoChar = NULLCHAR;
9469     ChessMove moveType;
9470     char move[MSG_SIZ];
9471     char *p, *q;
9472
9473     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9474         gameMode != AnalyzeMode && gameMode != Training) {
9475         gameFileFP = NULL;
9476         return FALSE;
9477     }
9478
9479     yyboardindex = forwardMostMove;
9480     if (readAhead != (ChessMove)0) {
9481       moveType = readAhead;
9482     } else {
9483       if (gameFileFP == NULL)
9484           return FALSE;
9485       moveType = (ChessMove) yylex();
9486     }
9487
9488     done = FALSE;
9489     switch (moveType) {
9490       case Comment:
9491         if (appData.debugMode)
9492           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9493         p = yy_text;
9494
9495         /* append the comment but don't display it */
9496         AppendComment(currentMove, p, FALSE);
9497         return TRUE;
9498
9499       case WhiteCapturesEnPassant:
9500       case BlackCapturesEnPassant:
9501       case WhitePromotionChancellor:
9502       case BlackPromotionChancellor:
9503       case WhitePromotionArchbishop:
9504       case BlackPromotionArchbishop:
9505       case WhitePromotionCentaur:
9506       case BlackPromotionCentaur:
9507       case WhitePromotionQueen:
9508       case BlackPromotionQueen:
9509       case WhitePromotionRook:
9510       case BlackPromotionRook:
9511       case WhitePromotionBishop:
9512       case BlackPromotionBishop:
9513       case WhitePromotionKnight:
9514       case BlackPromotionKnight:
9515       case WhitePromotionKing:
9516       case BlackPromotionKing:
9517       case NormalMove:
9518       case WhiteKingSideCastle:
9519       case WhiteQueenSideCastle:
9520       case BlackKingSideCastle:
9521       case BlackQueenSideCastle:
9522       case WhiteKingSideCastleWild:
9523       case WhiteQueenSideCastleWild:
9524       case BlackKingSideCastleWild:
9525       case BlackQueenSideCastleWild:
9526       /* PUSH Fabien */
9527       case WhiteHSideCastleFR:
9528       case WhiteASideCastleFR:
9529       case BlackHSideCastleFR:
9530       case BlackASideCastleFR:
9531       /* POP Fabien */
9532         if (appData.debugMode)
9533           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9534         fromX = currentMoveString[0] - AAA;
9535         fromY = currentMoveString[1] - ONE;
9536         toX = currentMoveString[2] - AAA;
9537         toY = currentMoveString[3] - ONE;
9538         promoChar = currentMoveString[4];
9539         break;
9540
9541       case WhiteDrop:
9542       case BlackDrop:
9543         if (appData.debugMode)
9544           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9545         fromX = moveType == WhiteDrop ?
9546           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9547         (int) CharToPiece(ToLower(currentMoveString[0]));
9548         fromY = DROP_RANK;
9549         toX = currentMoveString[2] - AAA;
9550         toY = currentMoveString[3] - ONE;
9551         break;
9552
9553       case WhiteWins:
9554       case BlackWins:
9555       case GameIsDrawn:
9556       case GameUnfinished:
9557         if (appData.debugMode)
9558           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9559         p = strchr(yy_text, '{');
9560         if (p == NULL) p = strchr(yy_text, '(');
9561         if (p == NULL) {
9562             p = yy_text;
9563             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9564         } else {
9565             q = strchr(p, *p == '{' ? '}' : ')');
9566             if (q != NULL) *q = NULLCHAR;
9567             p++;
9568         }
9569         GameEnds(moveType, p, GE_FILE);
9570         done = TRUE;
9571         if (cmailMsgLoaded) {
9572             ClearHighlights();
9573             flipView = WhiteOnMove(currentMove);
9574             if (moveType == GameUnfinished) flipView = !flipView;
9575             if (appData.debugMode)
9576               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9577         }
9578         break;
9579
9580       case (ChessMove) 0:       /* end of file */
9581         if (appData.debugMode)
9582           fprintf(debugFP, "Parser hit end of file\n");
9583         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9584           case MT_NONE:
9585           case MT_CHECK:
9586             break;
9587           case MT_CHECKMATE:
9588           case MT_STAINMATE:
9589             if (WhiteOnMove(currentMove)) {
9590                 GameEnds(BlackWins, "Black mates", GE_FILE);
9591             } else {
9592                 GameEnds(WhiteWins, "White mates", GE_FILE);
9593             }
9594             break;
9595           case MT_STALEMATE:
9596             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9597             break;
9598         }
9599         done = TRUE;
9600         break;
9601
9602       case MoveNumberOne:
9603         if (lastLoadGameStart == GNUChessGame) {
9604             /* GNUChessGames have numbers, but they aren't move numbers */
9605             if (appData.debugMode)
9606               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9607                       yy_text, (int) moveType);
9608             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9609         }
9610         /* else fall thru */
9611
9612       case XBoardGame:
9613       case GNUChessGame:
9614       case PGNTag:
9615         /* Reached start of next game in file */
9616         if (appData.debugMode)
9617           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9618         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9619           case MT_NONE:
9620           case MT_CHECK:
9621             break;
9622           case MT_CHECKMATE:
9623           case MT_STAINMATE:
9624             if (WhiteOnMove(currentMove)) {
9625                 GameEnds(BlackWins, "Black mates", GE_FILE);
9626             } else {
9627                 GameEnds(WhiteWins, "White mates", GE_FILE);
9628             }
9629             break;
9630           case MT_STALEMATE:
9631             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9632             break;
9633         }
9634         done = TRUE;
9635         break;
9636
9637       case PositionDiagram:     /* should not happen; ignore */
9638       case ElapsedTime:         /* ignore */
9639       case NAG:                 /* ignore */
9640         if (appData.debugMode)
9641           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9642                   yy_text, (int) moveType);
9643         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9644
9645       case IllegalMove:
9646         if (appData.testLegality) {
9647             if (appData.debugMode)
9648               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9649             sprintf(move, _("Illegal move: %d.%s%s"),
9650                     (forwardMostMove / 2) + 1,
9651                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9652             DisplayError(move, 0);
9653             done = TRUE;
9654         } else {
9655             if (appData.debugMode)
9656               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9657                       yy_text, currentMoveString);
9658             fromX = currentMoveString[0] - AAA;
9659             fromY = currentMoveString[1] - ONE;
9660             toX = currentMoveString[2] - AAA;
9661             toY = currentMoveString[3] - ONE;
9662             promoChar = currentMoveString[4];
9663         }
9664         break;
9665
9666       case AmbiguousMove:
9667         if (appData.debugMode)
9668           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9669         sprintf(move, _("Ambiguous move: %d.%s%s"),
9670                 (forwardMostMove / 2) + 1,
9671                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9672         DisplayError(move, 0);
9673         done = TRUE;
9674         break;
9675
9676       default:
9677       case ImpossibleMove:
9678         if (appData.debugMode)
9679           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9680         sprintf(move, _("Illegal move: %d.%s%s"),
9681                 (forwardMostMove / 2) + 1,
9682                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9683         DisplayError(move, 0);
9684         done = TRUE;
9685         break;
9686     }
9687
9688     if (done) {
9689         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9690             DrawPosition(FALSE, boards[currentMove]);
9691             DisplayBothClocks();
9692             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9693               DisplayComment(currentMove - 1, commentList[currentMove]);
9694         }
9695         (void) StopLoadGameTimer();
9696         gameFileFP = NULL;
9697         cmailOldMove = forwardMostMove;
9698         return FALSE;
9699     } else {
9700         /* currentMoveString is set as a side-effect of yylex */
9701         strcat(currentMoveString, "\n");
9702         strcpy(moveList[forwardMostMove], currentMoveString);
9703
9704         thinkOutput[0] = NULLCHAR;
9705         MakeMove(fromX, fromY, toX, toY, promoChar);
9706         currentMove = forwardMostMove;
9707         return TRUE;
9708     }
9709 }
9710
9711 /* Load the nth game from the given file */
9712 int
9713 LoadGameFromFile(filename, n, title, useList)
9714      char *filename;
9715      int n;
9716      char *title;
9717      /*Boolean*/ int useList;
9718 {
9719     FILE *f;
9720     char buf[MSG_SIZ];
9721
9722     if (strcmp(filename, "-") == 0) {
9723         f = stdin;
9724         title = "stdin";
9725     } else {
9726         f = fopen(filename, "rb");
9727         if (f == NULL) {
9728           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9729             DisplayError(buf, errno);
9730             return FALSE;
9731         }
9732     }
9733     if (fseek(f, 0, 0) == -1) {
9734         /* f is not seekable; probably a pipe */
9735         useList = FALSE;
9736     }
9737     if (useList && n == 0) {
9738         int error = GameListBuild(f);
9739         if (error) {
9740             DisplayError(_("Cannot build game list"), error);
9741         } else if (!ListEmpty(&gameList) &&
9742                    ((ListGame *) gameList.tailPred)->number > 1) {
9743           // TODO convert to GTK
9744           //        GameListPopUp(f, title);
9745             return TRUE;
9746         }
9747         GameListDestroy();
9748         n = 1;
9749     }
9750     if (n == 0) n = 1;
9751     return LoadGame(f, n, title, FALSE);
9752 }
9753
9754
9755 void
9756 MakeRegisteredMove()
9757 {
9758     int fromX, fromY, toX, toY;
9759     char promoChar;
9760     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9761         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9762           case CMAIL_MOVE:
9763           case CMAIL_DRAW:
9764             if (appData.debugMode)
9765               fprintf(debugFP, "Restoring %s for game %d\n",
9766                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9767
9768             thinkOutput[0] = NULLCHAR;
9769             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9770             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9771             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9772             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9773             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9774             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9775             MakeMove(fromX, fromY, toX, toY, promoChar);
9776             ShowMove(fromX, fromY, toX, toY);
9777             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9778               case MT_NONE:
9779               case MT_CHECK:
9780                 break;
9781
9782               case MT_CHECKMATE:
9783               case MT_STAINMATE:
9784                 if (WhiteOnMove(currentMove)) {
9785                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9786                 } else {
9787                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9788                 }
9789                 break;
9790
9791               case MT_STALEMATE:
9792                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9793                 break;
9794             }
9795
9796             break;
9797
9798           case CMAIL_RESIGN:
9799             if (WhiteOnMove(currentMove)) {
9800                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9801             } else {
9802                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9803             }
9804             break;
9805
9806           case CMAIL_ACCEPT:
9807             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9808             break;
9809
9810           default:
9811             break;
9812         }
9813     }
9814
9815     return;
9816 }
9817
9818 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9819 int
9820 CmailLoadGame(f, gameNumber, title, useList)
9821      FILE *f;
9822      int gameNumber;
9823      char *title;
9824      int useList;
9825 {
9826     int retVal;
9827
9828     if (gameNumber > nCmailGames) {
9829         DisplayError(_("No more games in this message"), 0);
9830         return FALSE;
9831     }
9832     if (f == lastLoadGameFP) {
9833         int offset = gameNumber - lastLoadGameNumber;
9834         if (offset == 0) {
9835             cmailMsg[0] = NULLCHAR;
9836             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9837                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9838                 nCmailMovesRegistered--;
9839             }
9840             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9841             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9842                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9843             }
9844         } else {
9845             if (! RegisterMove()) return FALSE;
9846         }
9847     }
9848
9849     retVal = LoadGame(f, gameNumber, title, useList);
9850
9851     /* Make move registered during previous look at this game, if any */
9852     MakeRegisteredMove();
9853
9854     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9855         commentList[currentMove]
9856           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9857         DisplayComment(currentMove - 1, commentList[currentMove]);
9858     }
9859
9860     return retVal;
9861 }
9862
9863 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9864 int
9865 ReloadGame(offset)
9866      int offset;
9867 {
9868     int gameNumber = lastLoadGameNumber + offset;
9869     if (lastLoadGameFP == NULL) {
9870         DisplayError(_("No game has been loaded yet"), 0);
9871         return FALSE;
9872     }
9873     if (gameNumber <= 0) {
9874         DisplayError(_("Can't back up any further"), 0);
9875         return FALSE;
9876     }
9877     if (cmailMsgLoaded) {
9878         return CmailLoadGame(lastLoadGameFP, gameNumber,
9879                              lastLoadGameTitle, lastLoadGameUseList);
9880     } else {
9881         return LoadGame(lastLoadGameFP, gameNumber,
9882                         lastLoadGameTitle, lastLoadGameUseList);
9883     }
9884 }
9885
9886
9887
9888 /* Load the nth game from open file f */
9889 int
9890 LoadGame(f, gameNumber, title, useList)
9891      FILE *f;
9892      int gameNumber;
9893      char *title;
9894      int useList;
9895 {
9896     ChessMove cm;
9897     char buf[MSG_SIZ];
9898     int gn = gameNumber;
9899     ListGame *lg = NULL;
9900     int numPGNTags = 0;
9901     int err;
9902     GameMode oldGameMode;
9903     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9904
9905     if (appData.debugMode)
9906         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9907
9908     if (gameMode == Training )
9909         SetTrainingModeOff();
9910
9911     oldGameMode = gameMode;
9912     if (gameMode != BeginningOfGame) 
9913       {
9914         Reset(FALSE, TRUE);
9915       };
9916
9917     gameFileFP = f;
9918     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9919       {
9920         fclose(lastLoadGameFP);
9921       };
9922
9923     if (useList) 
9924       {
9925         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9926         
9927         if (lg) 
9928           {
9929             fseek(f, lg->offset, 0);
9930             GameListHighlight(gameNumber);
9931             gn = 1;
9932           }
9933         else 
9934           {
9935             DisplayError(_("Game number out of range"), 0);
9936             return FALSE;
9937           };
9938       } 
9939     else 
9940       {
9941         GameListDestroy();
9942         if (fseek(f, 0, 0) == -1) 
9943           {
9944             if (f == lastLoadGameFP ?
9945                 gameNumber == lastLoadGameNumber + 1 :
9946                 gameNumber == 1) 
9947               {
9948                 gn = 1;
9949               } 
9950             else 
9951               {
9952                 DisplayError(_("Can't seek on game file"), 0);
9953                 return FALSE;
9954               };
9955           };
9956       };
9957
9958     lastLoadGameFP      = f;
9959     lastLoadGameNumber  = gameNumber;
9960     strcpy(lastLoadGameTitle, title);
9961     lastLoadGameUseList = useList;
9962
9963     yynewfile(f);
9964
9965     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9966       {
9967         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9968                  lg->gameInfo.black);
9969         DisplayTitle(buf);
9970       } 
9971     else if (*title != NULLCHAR) 
9972       {
9973         if (gameNumber > 1) 
9974           {
9975             sprintf(buf, "%s %d", title, gameNumber);
9976             DisplayTitle(buf);
9977           } 
9978         else 
9979           {
9980             DisplayTitle(title);
9981           };
9982       };
9983
9984     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9985       {
9986         gameMode = PlayFromGameFile;
9987         ModeHighlight();
9988       };
9989
9990     currentMove = forwardMostMove = backwardMostMove = 0;
9991     CopyBoard(boards[0], initialPosition);
9992     StopClocks();
9993
9994     /*
9995      * Skip the first gn-1 games in the file.
9996      * Also skip over anything that precedes an identifiable
9997      * start of game marker, to avoid being confused by
9998      * garbage at the start of the file.  Currently
9999      * recognized start of game markers are the move number "1",
10000      * the pattern "gnuchess .* game", the pattern
10001      * "^[#;%] [^ ]* game file", and a PGN tag block.
10002      * A game that starts with one of the latter two patterns
10003      * will also have a move number 1, possibly
10004      * following a position diagram.
10005      * 5-4-02: Let's try being more lenient and allowing a game to
10006      * start with an unnumbered move.  Does that break anything?
10007      */
10008     cm = lastLoadGameStart = (ChessMove) 0;
10009     while (gn > 0) {
10010         yyboardindex = forwardMostMove;
10011         cm = (ChessMove) yylex();
10012         switch (cm) {
10013           case (ChessMove) 0:
10014             if (cmailMsgLoaded) {
10015                 nCmailGames = CMAIL_MAX_GAMES - gn;
10016             } else {
10017                 Reset(TRUE, TRUE);
10018                 DisplayError(_("Game not found in file"), 0);
10019             }
10020             return FALSE;
10021
10022           case GNUChessGame:
10023           case XBoardGame:
10024             gn--;
10025             lastLoadGameStart = cm;
10026             break;
10027
10028           case MoveNumberOne:
10029             switch (lastLoadGameStart) {
10030               case GNUChessGame:
10031               case XBoardGame:
10032               case PGNTag:
10033                 break;
10034               case MoveNumberOne:
10035               case (ChessMove) 0:
10036                 gn--;           /* count this game */
10037                 lastLoadGameStart = cm;
10038                 break;
10039               default:
10040                 /* impossible */
10041                 break;
10042             }
10043             break;
10044
10045           case PGNTag:
10046             switch (lastLoadGameStart) {
10047               case GNUChessGame:
10048               case PGNTag:
10049               case MoveNumberOne:
10050               case (ChessMove) 0:
10051                 gn--;           /* count this game */
10052                 lastLoadGameStart = cm;
10053                 break;
10054               case XBoardGame:
10055                 lastLoadGameStart = cm; /* game counted already */
10056                 break;
10057               default:
10058                 /* impossible */
10059                 break;
10060             }
10061             if (gn > 0) {
10062                 do {
10063                     yyboardindex = forwardMostMove;
10064                     cm = (ChessMove) yylex();
10065                 } while (cm == PGNTag || cm == Comment);
10066             }
10067             break;
10068
10069           case WhiteWins:
10070           case BlackWins:
10071           case GameIsDrawn:
10072             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10073                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10074                     != CMAIL_OLD_RESULT) {
10075                     nCmailResults ++ ;
10076                     cmailResult[  CMAIL_MAX_GAMES
10077                                 - gn - 1] = CMAIL_OLD_RESULT;
10078                 }
10079             }
10080             break;
10081
10082           case NormalMove:
10083             /* Only a NormalMove can be at the start of a game
10084              * without a position diagram. */
10085             if (lastLoadGameStart == (ChessMove) 0) {
10086               gn--;
10087               lastLoadGameStart = MoveNumberOne;
10088             }
10089             break;
10090
10091           default:
10092             break;
10093         }
10094     }
10095
10096     if (appData.debugMode)
10097       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10098
10099     if (cm == XBoardGame) {
10100         /* Skip any header junk before position diagram and/or move 1 */
10101         for (;;) {
10102             yyboardindex = forwardMostMove;
10103             cm = (ChessMove) yylex();
10104
10105             if (cm == (ChessMove) 0 ||
10106                 cm == GNUChessGame || cm == XBoardGame) {
10107                 /* Empty game; pretend end-of-file and handle later */
10108                 cm = (ChessMove) 0;
10109                 break;
10110             }
10111
10112             if (cm == MoveNumberOne || cm == PositionDiagram ||
10113                 cm == PGNTag || cm == Comment)
10114               break;
10115         }
10116     } else if (cm == GNUChessGame) {
10117         if (gameInfo.event != NULL) {
10118             free(gameInfo.event);
10119         }
10120         gameInfo.event = StrSave(yy_text);
10121     }
10122
10123     startedFromSetupPosition = FALSE;
10124     while (cm == PGNTag) {
10125         if (appData.debugMode)
10126           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10127         err = ParsePGNTag(yy_text, &gameInfo);
10128         if (!err) numPGNTags++;
10129
10130         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10131         if(gameInfo.variant != oldVariant) {
10132             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10133             InitPosition(TRUE);
10134             oldVariant = gameInfo.variant;
10135             if (appData.debugMode)
10136               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10137         }
10138
10139
10140         if (gameInfo.fen != NULL) {
10141           Board initial_position;
10142           startedFromSetupPosition = TRUE;
10143           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10144             Reset(TRUE, TRUE);
10145             DisplayError(_("Bad FEN position in file"), 0);
10146             return FALSE;
10147           }
10148           CopyBoard(boards[0], initial_position);
10149           if (blackPlaysFirst) {
10150             currentMove = forwardMostMove = backwardMostMove = 1;
10151             CopyBoard(boards[1], initial_position);
10152             strcpy(moveList[0], "");
10153             strcpy(parseList[0], "");
10154             timeRemaining[0][1] = whiteTimeRemaining;
10155             timeRemaining[1][1] = blackTimeRemaining;
10156             if (commentList[0] != NULL) {
10157               commentList[1] = commentList[0];
10158               commentList[0] = NULL;
10159             }
10160           } else {
10161             currentMove = forwardMostMove = backwardMostMove = 0;
10162           }
10163           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10164           {   int i;
10165               initialRulePlies = FENrulePlies;
10166               for( i=0; i< nrCastlingRights; i++ )
10167                   initialRights[i] = initial_position[CASTLING][i];
10168           }
10169           yyboardindex = forwardMostMove;
10170           free(gameInfo.fen);
10171           gameInfo.fen = NULL;
10172         }
10173
10174         yyboardindex = forwardMostMove;
10175         cm = (ChessMove) yylex();
10176
10177         /* Handle comments interspersed among the tags */
10178         while (cm == Comment) {
10179             char *p;
10180             if (appData.debugMode)
10181               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10182             p = yy_text;
10183             AppendComment(currentMove, p, FALSE);
10184             yyboardindex = forwardMostMove;
10185             cm = (ChessMove) yylex();
10186         }
10187     }
10188
10189     /* don't rely on existence of Event tag since if game was
10190      * pasted from clipboard the Event tag may not exist
10191      */
10192     if (numPGNTags > 0){
10193         char *tags;
10194         if (gameInfo.variant == VariantNormal) {
10195           gameInfo.variant = StringToVariant(gameInfo.event);
10196         }
10197         if (!matchMode) {
10198           if( appData.autoDisplayTags ) {
10199             tags = PGNTags(&gameInfo);
10200             TagsPopUp(tags, CmailMsg());
10201             free(tags);
10202           }
10203         }
10204     } else {
10205         /* Make something up, but don't display it now */
10206         SetGameInfo();
10207         TagsPopDown();
10208     }
10209
10210     if (cm == PositionDiagram) {
10211         int i, j;
10212         char *p;
10213         Board initial_position;
10214
10215         if (appData.debugMode)
10216           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10217
10218         if (!startedFromSetupPosition) {
10219             p = yy_text;
10220             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10221               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10222                 switch (*p) {
10223                   case '[':
10224                   case '-':
10225                   case ' ':
10226                   case '\t':
10227                   case '\n':
10228                   case '\r':
10229                     break;
10230                   default:
10231                     initial_position[i][j++] = CharToPiece(*p);
10232                     break;
10233                 }
10234             while (*p == ' ' || *p == '\t' ||
10235                    *p == '\n' || *p == '\r') p++;
10236
10237             if (strncmp(p, "black", strlen("black"))==0)
10238               blackPlaysFirst = TRUE;
10239             else
10240               blackPlaysFirst = FALSE;
10241             startedFromSetupPosition = TRUE;
10242
10243             CopyBoard(boards[0], initial_position);
10244             if (blackPlaysFirst) {
10245                 currentMove = forwardMostMove = backwardMostMove = 1;
10246                 CopyBoard(boards[1], initial_position);
10247                 strcpy(moveList[0], "");
10248                 strcpy(parseList[0], "");
10249                 timeRemaining[0][1] = whiteTimeRemaining;
10250                 timeRemaining[1][1] = blackTimeRemaining;
10251                 if (commentList[0] != NULL) {
10252                     commentList[1] = commentList[0];
10253                     commentList[0] = NULL;
10254                 }
10255             } else {
10256                 currentMove = forwardMostMove = backwardMostMove = 0;
10257             }
10258         }
10259         yyboardindex = forwardMostMove;
10260         cm = (ChessMove) yylex();
10261     }
10262
10263     if (first.pr == NoProc) {
10264         StartChessProgram(&first);
10265     }
10266     InitChessProgram(&first, FALSE);
10267     SendToProgram("force\n", &first);
10268     if (startedFromSetupPosition) {
10269         SendBoard(&first, forwardMostMove);
10270     if (appData.debugMode) {
10271         fprintf(debugFP, "Load Game\n");
10272     }
10273         DisplayBothClocks();
10274     }
10275
10276     /* [HGM] server: flag to write setup moves in broadcast file as one */
10277     loadFlag = appData.suppressLoadMoves;
10278
10279     while (cm == Comment) {
10280         char *p;
10281         if (appData.debugMode)
10282           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10283         p = yy_text;
10284         AppendComment(currentMove, p, FALSE);
10285         yyboardindex = forwardMostMove;
10286         cm = (ChessMove) yylex();
10287     }
10288
10289     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10290         cm == WhiteWins || cm == BlackWins ||
10291         cm == GameIsDrawn || cm == GameUnfinished) {
10292         DisplayMessage("", _("No moves in game"));
10293         if (cmailMsgLoaded) {
10294             if (appData.debugMode)
10295               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10296             ClearHighlights();
10297             flipView = FALSE;
10298         }
10299         DrawPosition(FALSE, boards[currentMove]);
10300         DisplayBothClocks();
10301         gameMode = EditGame;
10302         ModeHighlight();
10303         gameFileFP = NULL;
10304         cmailOldMove = 0;
10305         return TRUE;
10306     }
10307
10308     // [HGM] PV info: routine tests if comment empty
10309     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10310         DisplayComment(currentMove - 1, commentList[currentMove]);
10311     }
10312     if (!matchMode && appData.timeDelay != 0)
10313       DrawPosition(FALSE, boards[currentMove]);
10314
10315     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10316       programStats.ok_to_send = 1;
10317     }
10318
10319     /* if the first token after the PGN tags is a move
10320      * and not move number 1, retrieve it from the parser
10321      */
10322     if (cm != MoveNumberOne)
10323         LoadGameOneMove(cm);
10324
10325     /* load the remaining moves from the file */
10326     while (LoadGameOneMove((ChessMove)0)) {
10327       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10328       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10329     }
10330
10331     /* rewind to the start of the game */
10332     currentMove = backwardMostMove;
10333
10334     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10335
10336     if (oldGameMode == AnalyzeFile ||
10337         oldGameMode == AnalyzeMode) {
10338       AnalyzeFileEvent();
10339     }
10340
10341     if (matchMode || appData.timeDelay == 0) {
10342       ToEndEvent();
10343       gameMode = EditGame;
10344       ModeHighlight();
10345     } else if (appData.timeDelay > 0) {
10346       AutoPlayGameLoop();
10347     }
10348
10349     if (appData.debugMode)
10350         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10351
10352     loadFlag = 0; /* [HGM] true game starts */
10353     return TRUE;
10354 }
10355
10356 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10357 int
10358 ReloadPosition(offset)
10359      int offset;
10360 {
10361     int positionNumber = lastLoadPositionNumber + offset;
10362     if (lastLoadPositionFP == NULL) {
10363         DisplayError(_("No position has been loaded yet"), 0);
10364         return FALSE;
10365     }
10366     if (positionNumber <= 0) {
10367         DisplayError(_("Can't back up any further"), 0);
10368         return FALSE;
10369     }
10370     return LoadPosition(lastLoadPositionFP, positionNumber,
10371                         lastLoadPositionTitle);
10372 }
10373
10374 /* Load the nth position from the given file */
10375 int
10376 LoadPositionFromFile(filename, n, title)
10377      char *filename;
10378      int n;
10379      char *title;
10380 {
10381     FILE *f;
10382     char buf[MSG_SIZ];
10383
10384     if (strcmp(filename, "-") == 0) {
10385         return LoadPosition(stdin, n, "stdin");
10386     } else {
10387         f = fopen(filename, "rb");
10388         if (f == NULL) {
10389             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10390             DisplayError(buf, errno);
10391             return FALSE;
10392         } else {
10393             return LoadPosition(f, n, title);
10394         }
10395     }
10396 }
10397
10398 /* Load the nth position from the given open file, and close it */
10399 int
10400 LoadPosition(f, positionNumber, title)
10401      FILE *f;
10402      int positionNumber;
10403      char *title;
10404 {
10405     char *p, line[MSG_SIZ];
10406     Board initial_position;
10407     int i, j, fenMode, pn;
10408
10409     if (gameMode == Training )
10410         SetTrainingModeOff();
10411
10412     if (gameMode != BeginningOfGame) {
10413         Reset(FALSE, TRUE);
10414     }
10415     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10416         fclose(lastLoadPositionFP);
10417     }
10418     if (positionNumber == 0) positionNumber = 1;
10419     lastLoadPositionFP = f;
10420     lastLoadPositionNumber = positionNumber;
10421     strcpy(lastLoadPositionTitle, title);
10422     if (first.pr == NoProc) {
10423       StartChessProgram(&first);
10424       InitChessProgram(&first, FALSE);
10425     }
10426     pn = positionNumber;
10427     if (positionNumber < 0) {
10428         /* Negative position number means to seek to that byte offset */
10429         if (fseek(f, -positionNumber, 0) == -1) {
10430             DisplayError(_("Can't seek on position file"), 0);
10431             return FALSE;
10432         };
10433         pn = 1;
10434     } else {
10435         if (fseek(f, 0, 0) == -1) {
10436             if (f == lastLoadPositionFP ?
10437                 positionNumber == lastLoadPositionNumber + 1 :
10438                 positionNumber == 1) {
10439                 pn = 1;
10440             } else {
10441                 DisplayError(_("Can't seek on position file"), 0);
10442                 return FALSE;
10443             }
10444         }
10445     }
10446     /* See if this file is FEN or old-style xboard */
10447     if (fgets(line, MSG_SIZ, f) == NULL) {
10448         DisplayError(_("Position not found in file"), 0);
10449         return FALSE;
10450     }
10451     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10452     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10453
10454     if (pn >= 2) {
10455         if (fenMode || line[0] == '#') pn--;
10456         while (pn > 0) {
10457             /* skip positions before number pn */
10458             if (fgets(line, MSG_SIZ, f) == NULL) {
10459                 Reset(TRUE, TRUE);
10460                 DisplayError(_("Position not found in file"), 0);
10461                 return FALSE;
10462             }
10463             if (fenMode || line[0] == '#') pn--;
10464         }
10465     }
10466
10467     if (fenMode) {
10468         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10469             DisplayError(_("Bad FEN position in file"), 0);
10470             return FALSE;
10471         }
10472     } else {
10473         (void) fgets(line, MSG_SIZ, f);
10474         (void) fgets(line, MSG_SIZ, f);
10475
10476         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10477             (void) fgets(line, MSG_SIZ, f);
10478             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10479                 if (*p == ' ')
10480                   continue;
10481                 initial_position[i][j++] = CharToPiece(*p);
10482             }
10483         }
10484
10485         blackPlaysFirst = FALSE;
10486         if (!feof(f)) {
10487             (void) fgets(line, MSG_SIZ, f);
10488             if (strncmp(line, "black", strlen("black"))==0)
10489               blackPlaysFirst = TRUE;
10490         }
10491     }
10492     startedFromSetupPosition = TRUE;
10493
10494     SendToProgram("force\n", &first);
10495     CopyBoard(boards[0], initial_position);
10496     if (blackPlaysFirst) {
10497         currentMove = forwardMostMove = backwardMostMove = 1;
10498         strcpy(moveList[0], "");
10499         strcpy(parseList[0], "");
10500         CopyBoard(boards[1], initial_position);
10501         DisplayMessage("", _("Black to play"));
10502     } else {
10503         currentMove = forwardMostMove = backwardMostMove = 0;
10504         DisplayMessage("", _("White to play"));
10505     }
10506     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10507     SendBoard(&first, forwardMostMove);
10508     if (appData.debugMode) {
10509 int i, j;
10510   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10511   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10512         fprintf(debugFP, "Load Position\n");
10513     }
10514
10515     if (positionNumber > 1) {
10516         sprintf(line, "%s %d", title, positionNumber);
10517         DisplayTitle(line);
10518     } else {
10519         DisplayTitle(title);
10520     }
10521     gameMode = EditGame;
10522     ModeHighlight();
10523     ResetClocks();
10524     timeRemaining[0][1] = whiteTimeRemaining;
10525     timeRemaining[1][1] = blackTimeRemaining;
10526     DrawPosition(FALSE, boards[currentMove]);
10527
10528     return TRUE;
10529 }
10530
10531
10532 void
10533 CopyPlayerNameIntoFileName(dest, src)
10534      char **dest, *src;
10535 {
10536     while (*src != NULLCHAR && *src != ',') {
10537         if (*src == ' ') {
10538             *(*dest)++ = '_';
10539             src++;
10540         } else {
10541             *(*dest)++ = *src++;
10542         }
10543     }
10544 }
10545
10546 char *DefaultFileName(ext)
10547      char *ext;
10548 {
10549     static char def[MSG_SIZ];
10550     char *p;
10551
10552     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10553         p = def;
10554         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10555         *p++ = '-';
10556         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10557         *p++ = '.';
10558         strcpy(p, ext);
10559     } else {
10560         def[0] = NULLCHAR;
10561     }
10562     return def;
10563 }
10564
10565 /* Save the current game to the given file */
10566 int
10567 SaveGameToFile(filename, append)
10568      char *filename;
10569      int append;
10570 {
10571     FILE *f;
10572     char buf[MSG_SIZ];
10573
10574     if (strcmp(filename, "-") == 0) {
10575         return SaveGame(stdout, 0, NULL);
10576     } else {
10577         f = fopen(filename, append ? "a" : "w");
10578         if (f == NULL) {
10579             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10580             DisplayError(buf, errno);
10581             return FALSE;
10582         } else {
10583             return SaveGame(f, 0, NULL);
10584         }
10585     }
10586 }
10587
10588 char *
10589 SavePart(str)
10590      char *str;
10591 {
10592     static char buf[MSG_SIZ];
10593     char *p;
10594
10595     p = strchr(str, ' ');
10596     if (p == NULL) return str;
10597     strncpy(buf, str, p - str);
10598     buf[p - str] = NULLCHAR;
10599     return buf;
10600 }
10601
10602 #define PGN_MAX_LINE 75
10603
10604 #define PGN_SIDE_WHITE  0
10605 #define PGN_SIDE_BLACK  1
10606
10607 /* [AS] */
10608 static int FindFirstMoveOutOfBook( int side )
10609 {
10610     int result = -1;
10611
10612     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10613         int index = backwardMostMove;
10614         int has_book_hit = 0;
10615
10616         if( (index % 2) != side ) {
10617             index++;
10618         }
10619
10620         while( index < forwardMostMove ) {
10621             /* Check to see if engine is in book */
10622             int depth = pvInfoList[index].depth;
10623             int score = pvInfoList[index].score;
10624             int in_book = 0;
10625
10626             if( depth <= 2 ) {
10627                 in_book = 1;
10628             }
10629             else if( score == 0 && depth == 63 ) {
10630                 in_book = 1; /* Zappa */
10631             }
10632             else if( score == 2 && depth == 99 ) {
10633                 in_book = 1; /* Abrok */
10634             }
10635
10636             has_book_hit += in_book;
10637
10638             if( ! in_book ) {
10639                 result = index;
10640
10641                 break;
10642             }
10643
10644             index += 2;
10645         }
10646     }
10647
10648     return result;
10649 }
10650
10651 /* [AS] */
10652 void GetOutOfBookInfo( char * buf )
10653 {
10654     int oob[2];
10655     int i;
10656     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10657
10658     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10659     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10660
10661     *buf = '\0';
10662
10663     if( oob[0] >= 0 || oob[1] >= 0 ) {
10664         for( i=0; i<2; i++ ) {
10665             int idx = oob[i];
10666
10667             if( idx >= 0 ) {
10668                 if( i > 0 && oob[0] >= 0 ) {
10669                     strcat( buf, "   " );
10670                 }
10671
10672                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10673                 sprintf( buf+strlen(buf), "%s%.2f",
10674                     pvInfoList[idx].score >= 0 ? "+" : "",
10675                     pvInfoList[idx].score / 100.0 );
10676             }
10677         }
10678     }
10679 }
10680
10681 /* Save game in PGN style and close the file */
10682 int
10683 SaveGamePGN(f)
10684      FILE *f;
10685 {
10686     int i, offset, linelen, newblock;
10687     time_t tm;
10688 //    char *movetext;
10689     char numtext[32];
10690     int movelen, numlen, blank;
10691     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10692
10693     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10694
10695     tm = time((time_t *) NULL);
10696
10697     PrintPGNTags(f, &gameInfo);
10698
10699     if (backwardMostMove > 0 || startedFromSetupPosition) {
10700         char *fen = PositionToFEN(backwardMostMove, NULL);
10701         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10702         fprintf(f, "\n{--------------\n");
10703         PrintPosition(f, backwardMostMove);
10704         fprintf(f, "--------------}\n");
10705         free(fen);
10706     }
10707     else {
10708         /* [AS] Out of book annotation */
10709         if( appData.saveOutOfBookInfo ) {
10710             char buf[64];
10711
10712             GetOutOfBookInfo( buf );
10713
10714             if( buf[0] != '\0' ) {
10715                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10716             }
10717         }
10718
10719         fprintf(f, "\n");
10720     }
10721
10722     i = backwardMostMove;
10723     linelen = 0;
10724     newblock = TRUE;
10725
10726     while (i < forwardMostMove) {
10727         /* Print comments preceding this move */
10728         if (commentList[i] != NULL) {
10729             if (linelen > 0) fprintf(f, "\n");
10730             fprintf(f, "%s", commentList[i]);
10731             linelen = 0;
10732             newblock = TRUE;
10733         }
10734
10735         /* Format move number */
10736         if ((i % 2) == 0) {
10737             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10738         } else {
10739             if (newblock) {
10740                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10741             } else {
10742                 numtext[0] = NULLCHAR;
10743             }
10744         }
10745         numlen = strlen(numtext);
10746         newblock = FALSE;
10747
10748         /* Print move number */
10749         blank = linelen > 0 && numlen > 0;
10750         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10751             fprintf(f, "\n");
10752             linelen = 0;
10753             blank = 0;
10754         }
10755         if (blank) {
10756             fprintf(f, " ");
10757             linelen++;
10758         }
10759         fprintf(f, "%s", numtext);
10760         linelen += numlen;
10761
10762         /* Get move */
10763         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10764         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10765
10766         /* Print move */
10767         blank = linelen > 0 && movelen > 0;
10768         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10769             fprintf(f, "\n");
10770             linelen = 0;
10771             blank = 0;
10772         }
10773         if (blank) {
10774             fprintf(f, " ");
10775             linelen++;
10776         }
10777         fprintf(f, "%s", move_buffer);
10778         linelen += movelen;
10779
10780         /* [AS] Add PV info if present */
10781         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10782             /* [HGM] add time */
10783             char buf[MSG_SIZ]; int seconds;
10784
10785             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10786
10787             if( seconds <= 0) buf[0] = 0; else
10788             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10789                 seconds = (seconds + 4)/10; // round to full seconds
10790                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10791                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10792             }
10793
10794             sprintf( move_buffer, "{%s%.2f/%d%s}",
10795                 pvInfoList[i].score >= 0 ? "+" : "",
10796                 pvInfoList[i].score / 100.0,
10797                 pvInfoList[i].depth,
10798                 buf );
10799
10800             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10801
10802             /* Print score/depth */
10803             blank = linelen > 0 && movelen > 0;
10804             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10805                 fprintf(f, "\n");
10806                 linelen = 0;
10807                 blank = 0;
10808             }
10809             if (blank) {
10810                 fprintf(f, " ");
10811                 linelen++;
10812             }
10813             fprintf(f, "%s", move_buffer);
10814             linelen += movelen;
10815         }
10816
10817         i++;
10818     }
10819
10820     /* Start a new line */
10821     if (linelen > 0) fprintf(f, "\n");
10822
10823     /* Print comments after last move */
10824     if (commentList[i] != NULL) {
10825         fprintf(f, "%s\n", commentList[i]);
10826     }
10827
10828     /* Print result */
10829     if (gameInfo.resultDetails != NULL &&
10830         gameInfo.resultDetails[0] != NULLCHAR) {
10831         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10832                 PGNResult(gameInfo.result));
10833     } else {
10834         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10835     }
10836
10837     fclose(f);
10838     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10839     return TRUE;
10840 }
10841
10842 /* Save game in old style and close the file */
10843 int
10844 SaveGameOldStyle(f)
10845      FILE *f;
10846 {
10847     int i, offset;
10848     time_t tm;
10849
10850     tm = time((time_t *) NULL);
10851
10852     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10853     PrintOpponents(f);
10854
10855     if (backwardMostMove > 0 || startedFromSetupPosition) {
10856         fprintf(f, "\n[--------------\n");
10857         PrintPosition(f, backwardMostMove);
10858         fprintf(f, "--------------]\n");
10859     } else {
10860         fprintf(f, "\n");
10861     }
10862
10863     i = backwardMostMove;
10864     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10865
10866     while (i < forwardMostMove) {
10867         if (commentList[i] != NULL) {
10868             fprintf(f, "[%s]\n", commentList[i]);
10869         }
10870
10871         if ((i % 2) == 1) {
10872             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10873             i++;
10874         } else {
10875             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10876             i++;
10877             if (commentList[i] != NULL) {
10878                 fprintf(f, "\n");
10879                 continue;
10880             }
10881             if (i >= forwardMostMove) {
10882                 fprintf(f, "\n");
10883                 break;
10884             }
10885             fprintf(f, "%s\n", parseList[i]);
10886             i++;
10887         }
10888     }
10889
10890     if (commentList[i] != NULL) {
10891         fprintf(f, "[%s]\n", commentList[i]);
10892     }
10893
10894     /* This isn't really the old style, but it's close enough */
10895     if (gameInfo.resultDetails != NULL &&
10896         gameInfo.resultDetails[0] != NULLCHAR) {
10897         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10898                 gameInfo.resultDetails);
10899     } else {
10900         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10901     }
10902
10903     fclose(f);
10904     return TRUE;
10905 }
10906
10907 /* Save the current game to open file f and close the file */
10908 int
10909 SaveGame(f, dummy, dummy2)
10910      FILE *f;
10911      int dummy;
10912      char *dummy2;
10913 {
10914     if (gameMode == EditPosition) EditPositionDone(TRUE);
10915     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10916     if (appData.oldSaveStyle)
10917       return SaveGameOldStyle(f);
10918     else
10919       return SaveGamePGN(f);
10920 }
10921
10922 /* Save the current position to the given file */
10923 int
10924 SavePositionToFile(filename)
10925      char *filename;
10926 {
10927     FILE *f;
10928     char buf[MSG_SIZ];
10929
10930     if (strcmp(filename, "-") == 0) {
10931         return SavePosition(stdout, 0, NULL);
10932     } else {
10933         f = fopen(filename, "a");
10934         if (f == NULL) {
10935             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10936             DisplayError(buf, errno);
10937             return FALSE;
10938         } else {
10939             SavePosition(f, 0, NULL);
10940             return TRUE;
10941         }
10942     }
10943 }
10944
10945 /* Save the current position to the given open file and close the file */
10946 int
10947 SavePosition(f, dummy, dummy2)
10948      FILE *f;
10949      int dummy;
10950      char *dummy2;
10951 {
10952     time_t tm;
10953     char *fen;
10954     if (gameMode == EditPosition) EditPositionDone(TRUE);
10955     if (appData.oldSaveStyle) {
10956         tm = time((time_t *) NULL);
10957
10958         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10959         PrintOpponents(f);
10960         fprintf(f, "[--------------\n");
10961         PrintPosition(f, currentMove);
10962         fprintf(f, "--------------]\n");
10963     } else {
10964         fen = PositionToFEN(currentMove, NULL);
10965         fprintf(f, "%s\n", fen);
10966         free(fen);
10967     }
10968     fclose(f);
10969     return TRUE;
10970 }
10971
10972 void
10973 ReloadCmailMsgEvent(unregister)
10974      int unregister;
10975 {
10976 #if !WIN32
10977     static char *inFilename = NULL;
10978     static char *outFilename;
10979     int i;
10980     struct stat inbuf, outbuf;
10981     int status;
10982
10983     /* Any registered moves are unregistered if unregister is set, */
10984     /* i.e. invoked by the signal handler */
10985     if (unregister) {
10986         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10987             cmailMoveRegistered[i] = FALSE;
10988             if (cmailCommentList[i] != NULL) {
10989                 free(cmailCommentList[i]);
10990                 cmailCommentList[i] = NULL;
10991             }
10992         }
10993         nCmailMovesRegistered = 0;
10994     }
10995
10996     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10997         cmailResult[i] = CMAIL_NOT_RESULT;
10998     }
10999     nCmailResults = 0;
11000
11001     if (inFilename == NULL) {
11002         /* Because the filenames are static they only get malloced once  */
11003         /* and they never get freed                                      */
11004         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11005         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11006
11007         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11008         sprintf(outFilename, "%s.out", appData.cmailGameName);
11009     }
11010
11011     status = stat(outFilename, &outbuf);
11012     if (status < 0) {
11013         cmailMailedMove = FALSE;
11014     } else {
11015         status = stat(inFilename, &inbuf);
11016         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11017     }
11018
11019     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11020        counts the games, notes how each one terminated, etc.
11021
11022        It would be nice to remove this kludge and instead gather all
11023        the information while building the game list.  (And to keep it
11024        in the game list nodes instead of having a bunch of fixed-size
11025        parallel arrays.)  Note this will require getting each game's
11026        termination from the PGN tags, as the game list builder does
11027        not process the game moves.  --mann
11028        */
11029     cmailMsgLoaded = TRUE;
11030     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11031
11032     /* Load first game in the file or popup game menu */
11033     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11034
11035 #endif /* !WIN32 */
11036     return;
11037 }
11038
11039 int
11040 RegisterMove()
11041 {
11042     FILE *f;
11043     char string[MSG_SIZ];
11044
11045     if (   cmailMailedMove
11046         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11047         return TRUE;            /* Allow free viewing  */
11048     }
11049
11050     /* Unregister move to ensure that we don't leave RegisterMove        */
11051     /* with the move registered when the conditions for registering no   */
11052     /* longer hold                                                       */
11053     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11054         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11055         nCmailMovesRegistered --;
11056
11057         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11058           {
11059               free(cmailCommentList[lastLoadGameNumber - 1]);
11060               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11061           }
11062     }
11063
11064     if (cmailOldMove == -1) {
11065         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11066         return FALSE;
11067     }
11068
11069     if (currentMove > cmailOldMove + 1) {
11070         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11071         return FALSE;
11072     }
11073
11074     if (currentMove < cmailOldMove) {
11075         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11076         return FALSE;
11077     }
11078
11079     if (forwardMostMove > currentMove) {
11080         /* Silently truncate extra moves */
11081         TruncateGame();
11082     }
11083
11084     if (   (currentMove == cmailOldMove + 1)
11085         || (   (currentMove == cmailOldMove)
11086             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11087                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11088         if (gameInfo.result != GameUnfinished) {
11089             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11090         }
11091
11092         if (commentList[currentMove] != NULL) {
11093             cmailCommentList[lastLoadGameNumber - 1]
11094               = StrSave(commentList[currentMove]);
11095         }
11096         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11097
11098         if (appData.debugMode)
11099           fprintf(debugFP, "Saving %s for game %d\n",
11100                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11101
11102         sprintf(string,
11103                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11104
11105         f = fopen(string, "w");
11106         if (appData.oldSaveStyle) {
11107             SaveGameOldStyle(f); /* also closes the file */
11108
11109             sprintf(string, "%s.pos.out", appData.cmailGameName);
11110             f = fopen(string, "w");
11111             SavePosition(f, 0, NULL); /* also closes the file */
11112         } else {
11113             fprintf(f, "{--------------\n");
11114             PrintPosition(f, currentMove);
11115             fprintf(f, "--------------}\n\n");
11116
11117             SaveGame(f, 0, NULL); /* also closes the file*/
11118         }
11119
11120         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11121         nCmailMovesRegistered ++;
11122     } else if (nCmailGames == 1) {
11123         DisplayError(_("You have not made a move yet"), 0);
11124         return FALSE;
11125     }
11126
11127     return TRUE;
11128 }
11129
11130 void
11131 MailMoveEvent()
11132 {
11133 #if !WIN32
11134     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11135     FILE *commandOutput;
11136     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11137     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11138     int nBuffers;
11139     int i;
11140     int archived;
11141     char *arcDir;
11142
11143     if (! cmailMsgLoaded) {
11144         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11145         return;
11146     }
11147
11148     if (nCmailGames == nCmailResults) {
11149         DisplayError(_("No unfinished games"), 0);
11150         return;
11151     }
11152
11153 #if CMAIL_PROHIBIT_REMAIL
11154     if (cmailMailedMove) {
11155         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);
11156         DisplayError(msg, 0);
11157         return;
11158     }
11159 #endif
11160
11161     if (! (cmailMailedMove || RegisterMove())) return;
11162
11163     if (   cmailMailedMove
11164         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11165         sprintf(string, partCommandString,
11166                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11167         commandOutput = popen(string, "r");
11168
11169         if (commandOutput == NULL) {
11170             DisplayError(_("Failed to invoke cmail"), 0);
11171         } else {
11172             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11173                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11174             }
11175             if (nBuffers > 1) {
11176                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11177                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11178                 nBytes = MSG_SIZ - 1;
11179             } else {
11180                 (void) memcpy(msg, buffer, nBytes);
11181             }
11182             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11183
11184             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11185                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11186
11187                 archived = TRUE;
11188                 for (i = 0; i < nCmailGames; i ++) {
11189                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11190                         archived = FALSE;
11191                     }
11192                 }
11193                 if (   archived
11194                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11195                         != NULL)) {
11196                     sprintf(buffer, "%s/%s.%s.archive",
11197                             arcDir,
11198                             appData.cmailGameName,
11199                             gameInfo.date);
11200                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11201                     cmailMsgLoaded = FALSE;
11202                 }
11203             }
11204
11205             DisplayInformation(msg);
11206             pclose(commandOutput);
11207         }
11208     } else {
11209         if ((*cmailMsg) != '\0') {
11210             DisplayInformation(cmailMsg);
11211         }
11212     }
11213
11214     return;
11215 #endif /* !WIN32 */
11216 }
11217
11218 char *
11219 CmailMsg()
11220 {
11221 #if WIN32
11222     return NULL;
11223 #else
11224     int  prependComma = 0;
11225     char number[5];
11226     char string[MSG_SIZ];       /* Space for game-list */
11227     int  i;
11228
11229     if (!cmailMsgLoaded) return "";
11230
11231     if (cmailMailedMove) {
11232         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11233     } else {
11234         /* Create a list of games left */
11235         sprintf(string, "[");
11236         for (i = 0; i < nCmailGames; i ++) {
11237             if (! (   cmailMoveRegistered[i]
11238                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11239                 if (prependComma) {
11240                     sprintf(number, ",%d", i + 1);
11241                 } else {
11242                     sprintf(number, "%d", i + 1);
11243                     prependComma = 1;
11244                 }
11245
11246                 strcat(string, number);
11247             }
11248         }
11249         strcat(string, "]");
11250
11251         if (nCmailMovesRegistered + nCmailResults == 0) {
11252             switch (nCmailGames) {
11253               case 1:
11254                 sprintf(cmailMsg,
11255                         _("Still need to make move for game\n"));
11256                 break;
11257
11258               case 2:
11259                 sprintf(cmailMsg,
11260                         _("Still need to make moves for both games\n"));
11261                 break;
11262
11263               default:
11264                 sprintf(cmailMsg,
11265                         _("Still need to make moves for all %d games\n"),
11266                         nCmailGames);
11267                 break;
11268             }
11269         } else {
11270             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11271               case 1:
11272                 sprintf(cmailMsg,
11273                         _("Still need to make a move for game %s\n"),
11274                         string);
11275                 break;
11276
11277               case 0:
11278                 if (nCmailResults == nCmailGames) {
11279                     sprintf(cmailMsg, _("No unfinished games\n"));
11280                 } else {
11281                     sprintf(cmailMsg, _("Ready to send mail\n"));
11282                 }
11283                 break;
11284
11285               default:
11286                 sprintf(cmailMsg,
11287                         _("Still need to make moves for games %s\n"),
11288                         string);
11289             }
11290         }
11291     }
11292     return cmailMsg;
11293 #endif /* WIN32 */
11294 }
11295
11296 void
11297 ResetGameEvent()
11298 {
11299     if (gameMode == Training)
11300       SetTrainingModeOff();
11301
11302     Reset(TRUE, TRUE);
11303     cmailMsgLoaded = FALSE;
11304     if (appData.icsActive) {
11305       SendToICS(ics_prefix);
11306       SendToICS("refresh\n");
11307     }
11308 }
11309
11310 void
11311 ExitEvent(status)
11312      int status;
11313 {
11314     exiting++;
11315     if (exiting > 2) {
11316       /* Give up on clean exit */
11317       exit(status);
11318     }
11319     if (exiting > 1) {
11320       /* Keep trying for clean exit */
11321       return;
11322     }
11323
11324     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11325
11326     if (telnetISR != NULL) {
11327       RemoveInputSource(telnetISR);
11328     }
11329     if (icsPR != NoProc) {
11330       DestroyChildProcess(icsPR, TRUE);
11331     }
11332
11333     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11334     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11335
11336     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11337     /* make sure this other one finishes before killing it!                  */
11338     if(endingGame) { int count = 0;
11339         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11340         while(endingGame && count++ < 10) DoSleep(1);
11341         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11342     }
11343
11344     /* Kill off chess programs */
11345     if (first.pr != NoProc) {
11346         ExitAnalyzeMode();
11347
11348         DoSleep( appData.delayBeforeQuit );
11349         SendToProgram("quit\n", &first);
11350         DoSleep( appData.delayAfterQuit );
11351         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11352     }
11353     if (second.pr != NoProc) {
11354         DoSleep( appData.delayBeforeQuit );
11355         SendToProgram("quit\n", &second);
11356         DoSleep( appData.delayAfterQuit );
11357         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11358     }
11359     if (first.isr != NULL) {
11360         RemoveInputSource(first.isr);
11361     }
11362     if (second.isr != NULL) {
11363         RemoveInputSource(second.isr);
11364     }
11365
11366     ShutDownFrontEnd();
11367     exit(status);
11368 }
11369
11370 void
11371 PauseEvent()
11372 {
11373     if (appData.debugMode)
11374         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11375     if (pausing) {
11376         pausing = FALSE;
11377         ModeHighlight();
11378         if (gameMode == MachinePlaysWhite ||
11379             gameMode == MachinePlaysBlack) {
11380             StartClocks();
11381         } else {
11382             DisplayBothClocks();
11383         }
11384         if (gameMode == PlayFromGameFile) {
11385             if (appData.timeDelay >= 0)
11386                 AutoPlayGameLoop();
11387         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11388             Reset(FALSE, TRUE);
11389             SendToICS(ics_prefix);
11390             SendToICS("refresh\n");
11391         } else if (currentMove < forwardMostMove) {
11392             ForwardInner(forwardMostMove);
11393         }
11394         pauseExamInvalid = FALSE;
11395     } else {
11396         switch (gameMode) {
11397           default:
11398             return;
11399           case IcsExamining:
11400             pauseExamForwardMostMove = forwardMostMove;
11401             pauseExamInvalid = FALSE;
11402             /* fall through */
11403           case IcsObserving:
11404           case IcsPlayingWhite:
11405           case IcsPlayingBlack:
11406             pausing = TRUE;
11407             ModeHighlight();
11408             return;
11409           case PlayFromGameFile:
11410             (void) StopLoadGameTimer();
11411             pausing = TRUE;
11412             ModeHighlight();
11413             break;
11414           case BeginningOfGame:
11415             if (appData.icsActive) return;
11416             /* else fall through */
11417           case MachinePlaysWhite:
11418           case MachinePlaysBlack:
11419           case TwoMachinesPlay:
11420             if (forwardMostMove == 0)
11421               return;           /* don't pause if no one has moved */
11422             if ((gameMode == MachinePlaysWhite &&
11423                  !WhiteOnMove(forwardMostMove)) ||
11424                 (gameMode == MachinePlaysBlack &&
11425                  WhiteOnMove(forwardMostMove))) {
11426                 StopClocks();
11427             }
11428             pausing = TRUE;
11429             ModeHighlight();
11430             break;
11431         }
11432     }
11433 }
11434
11435 void
11436 EditCommentEvent()
11437 {
11438     char title[MSG_SIZ];
11439
11440     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11441         strcpy(title, _("Edit comment"));
11442     } else {
11443         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11444                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11445                 parseList[currentMove - 1]);
11446     }
11447
11448     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11449 }
11450
11451
11452 void
11453 EditTagsEvent()
11454 {
11455     char *tags = PGNTags(&gameInfo);
11456     EditTagsPopUp(tags);
11457     free(tags);
11458 }
11459
11460 void
11461 AnalyzeModeEvent()
11462 {
11463     if (appData.noChessProgram || gameMode == AnalyzeMode)
11464       return;
11465
11466     if (gameMode != AnalyzeFile) {
11467         if (!appData.icsEngineAnalyze) {
11468                EditGameEvent();
11469                if (gameMode != EditGame) return;
11470         }
11471         ResurrectChessProgram();
11472         SendToProgram("analyze\n", &first);
11473         first.analyzing = TRUE;
11474         /*first.maybeThinking = TRUE;*/
11475         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11476         EngineOutputPopUp();
11477     }
11478     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11479     pausing = FALSE;
11480     ModeHighlight();
11481     SetGameInfo();
11482
11483     StartAnalysisClock();
11484     GetTimeMark(&lastNodeCountTime);
11485     lastNodeCount = 0;
11486 }
11487
11488 void
11489 AnalyzeFileEvent()
11490 {
11491     if (appData.noChessProgram || gameMode == AnalyzeFile)
11492       return;
11493
11494     if (gameMode != AnalyzeMode) {
11495         EditGameEvent();
11496         if (gameMode != EditGame) return;
11497         ResurrectChessProgram();
11498         SendToProgram("analyze\n", &first);
11499         first.analyzing = TRUE;
11500         /*first.maybeThinking = TRUE;*/
11501         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11502         EngineOutputPopUp();
11503     }
11504     gameMode = AnalyzeFile;
11505     pausing = FALSE;
11506     ModeHighlight();
11507     SetGameInfo();
11508
11509     StartAnalysisClock();
11510     GetTimeMark(&lastNodeCountTime);
11511     lastNodeCount = 0;
11512 }
11513
11514 void
11515 MachineWhiteEvent()
11516 {
11517     char buf[MSG_SIZ];
11518     char *bookHit = NULL;
11519
11520     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11521       return;
11522
11523
11524     if (gameMode == PlayFromGameFile ||
11525         gameMode == TwoMachinesPlay  ||
11526         gameMode == Training         ||
11527         gameMode == AnalyzeMode      ||
11528         gameMode == EndOfGame)
11529         EditGameEvent();
11530
11531     if (gameMode == EditPosition) 
11532         EditPositionDone(TRUE);
11533
11534     if (!WhiteOnMove(currentMove)) {
11535         DisplayError(_("It is not White's turn"), 0);
11536         return;
11537     }
11538
11539     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11540       ExitAnalyzeMode();
11541
11542     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11543         gameMode == AnalyzeFile)
11544         TruncateGame();
11545
11546     ResurrectChessProgram();    /* in case it isn't running */
11547     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11548         gameMode = MachinePlaysWhite;
11549         ResetClocks();
11550     } else
11551     gameMode = MachinePlaysWhite;
11552     pausing = FALSE;
11553     ModeHighlight();
11554     SetGameInfo();
11555     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11556     DisplayTitle(buf);
11557     if (first.sendName) {
11558       sprintf(buf, "name %s\n", gameInfo.black);
11559       SendToProgram(buf, &first);
11560     }
11561     if (first.sendTime) {
11562       if (first.useColors) {
11563         SendToProgram("black\n", &first); /*gnu kludge*/
11564       }
11565       SendTimeRemaining(&first, TRUE);
11566     }
11567     if (first.useColors) {
11568       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11569     }
11570     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11571     SetMachineThinkingEnables();
11572     first.maybeThinking = TRUE;
11573     StartClocks();
11574     firstMove = FALSE;
11575
11576     if (appData.autoFlipView && !flipView) {
11577       flipView = !flipView;
11578       DrawPosition(FALSE, NULL);
11579       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11580     }
11581
11582     if(bookHit) { // [HGM] book: simulate book reply
11583         static char bookMove[MSG_SIZ]; // a bit generous?
11584
11585         programStats.nodes = programStats.depth = programStats.time =
11586         programStats.score = programStats.got_only_move = 0;
11587         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11588
11589         strcpy(bookMove, "move ");
11590         strcat(bookMove, bookHit);
11591         HandleMachineMove(bookMove, &first);
11592     }
11593 }
11594
11595 void
11596 MachineBlackEvent()
11597 {
11598   char buf[MSG_SIZ];
11599   char *bookHit = NULL;
11600   
11601   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11602     return;
11603   
11604   
11605   if (gameMode == PlayFromGameFile 
11606       || gameMode == TwoMachinesPlay  
11607       || gameMode == Training     
11608       || gameMode == AnalyzeMode
11609       || gameMode == EndOfGame)
11610     EditGameEvent();
11611   
11612   if (gameMode == EditPosition) 
11613     EditPositionDone(TRUE);
11614   
11615   if (WhiteOnMove(currentMove)) 
11616     {
11617       DisplayError(_("It is not Black's turn"), 0);
11618       return;
11619     }
11620   
11621   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11622     ExitAnalyzeMode();
11623   
11624   if (gameMode == EditGame || gameMode == AnalyzeMode 
11625       || gameMode == AnalyzeFile)
11626     TruncateGame();
11627   
11628   ResurrectChessProgram();      /* in case it isn't running */
11629   gameMode = MachinePlaysBlack;
11630   pausing  = FALSE;
11631   ModeHighlight();
11632   SetGameInfo();
11633   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11634   DisplayTitle(buf);
11635   if (first.sendName) 
11636     {
11637       sprintf(buf, "name %s\n", gameInfo.white);
11638       SendToProgram(buf, &first);
11639     }
11640   if (first.sendTime) 
11641     {
11642       if (first.useColors) 
11643         {
11644           SendToProgram("white\n", &first); /*gnu kludge*/
11645         }
11646       SendTimeRemaining(&first, FALSE);
11647     }
11648   if (first.useColors) 
11649     {
11650       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11651     }
11652   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11653   SetMachineThinkingEnables();
11654   first.maybeThinking = TRUE;
11655   StartClocks();
11656   
11657   if (appData.autoFlipView && flipView) 
11658     {
11659       flipView = !flipView;
11660       DrawPosition(FALSE, NULL);
11661       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11662     }
11663   if(bookHit) 
11664     { // [HGM] book: simulate book reply
11665       static char bookMove[MSG_SIZ]; // a bit generous?
11666       
11667       programStats.nodes = programStats.depth = programStats.time 
11668         = programStats.score = programStats.got_only_move = 0;
11669       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11670       
11671       strcpy(bookMove, "move ");
11672       strcat(bookMove, bookHit);
11673       HandleMachineMove(bookMove, &first);
11674     }
11675   return;
11676 }
11677
11678
11679 void
11680 DisplayTwoMachinesTitle()
11681 {
11682     char buf[MSG_SIZ];
11683     if (appData.matchGames > 0) {
11684         if (first.twoMachinesColor[0] == 'w') {
11685             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11686                     gameInfo.white, gameInfo.black,
11687                     first.matchWins, second.matchWins,
11688                     matchGame - 1 - (first.matchWins + second.matchWins));
11689         } else {
11690             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11691                     gameInfo.white, gameInfo.black,
11692                     second.matchWins, first.matchWins,
11693                     matchGame - 1 - (first.matchWins + second.matchWins));
11694         }
11695     } else {
11696         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11697     }
11698     DisplayTitle(buf);
11699 }
11700
11701 void
11702 TwoMachinesEvent P((void))
11703 {
11704     int i;
11705     char buf[MSG_SIZ];
11706     ChessProgramState *onmove;
11707     char *bookHit = NULL;
11708
11709     if (appData.noChessProgram) return;
11710
11711     switch (gameMode) {
11712       case TwoMachinesPlay:
11713         return;
11714       case MachinePlaysWhite:
11715       case MachinePlaysBlack:
11716         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11717             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11718             return;
11719         }
11720         /* fall through */
11721       case BeginningOfGame:
11722       case PlayFromGameFile:
11723       case EndOfGame:
11724         EditGameEvent();
11725         if (gameMode != EditGame) return;
11726         break;
11727       case EditPosition:
11728         EditPositionDone(TRUE);
11729         break;
11730       case AnalyzeMode:
11731       case AnalyzeFile:
11732         ExitAnalyzeMode();
11733         break;
11734       case EditGame:
11735       default:
11736         break;
11737     }
11738
11739 //    forwardMostMove = currentMove;
11740     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11741     ResurrectChessProgram();    /* in case first program isn't running */
11742
11743     if (second.pr == NULL) {
11744         StartChessProgram(&second);
11745         if (second.protocolVersion == 1) {
11746           TwoMachinesEventIfReady();
11747         } else {
11748           /* kludge: allow timeout for initial "feature" command */
11749           FreezeUI();
11750           DisplayMessage("", _("Starting second chess program"));
11751           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11752         }
11753         return;
11754     }
11755     DisplayMessage("", "");
11756     InitChessProgram(&second, FALSE);
11757     SendToProgram("force\n", &second);
11758     if (startedFromSetupPosition) {
11759         SendBoard(&second, backwardMostMove);
11760     if (appData.debugMode) {
11761         fprintf(debugFP, "Two Machines\n");
11762     }
11763     }
11764     for (i = backwardMostMove; i < forwardMostMove; i++) {
11765         SendMoveToProgram(i, &second);
11766     }
11767
11768     gameMode = TwoMachinesPlay;
11769     pausing = FALSE;
11770     ModeHighlight();
11771     SetGameInfo();
11772     DisplayTwoMachinesTitle();
11773     firstMove = TRUE;
11774     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11775         onmove = &first;
11776     } else {
11777         onmove = &second;
11778     }
11779
11780     SendToProgram(first.computerString, &first);
11781     if (first.sendName) {
11782       sprintf(buf, "name %s\n", second.tidy);
11783       SendToProgram(buf, &first);
11784     }
11785     SendToProgram(second.computerString, &second);
11786     if (second.sendName) {
11787       sprintf(buf, "name %s\n", first.tidy);
11788       SendToProgram(buf, &second);
11789     }
11790
11791     ResetClocks();
11792     if (!first.sendTime || !second.sendTime) {
11793         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11794         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11795     }
11796     if (onmove->sendTime) {
11797       if (onmove->useColors) {
11798         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11799       }
11800       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11801     }
11802     if (onmove->useColors) {
11803       SendToProgram(onmove->twoMachinesColor, onmove);
11804     }
11805     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11806 //    SendToProgram("go\n", onmove);
11807     onmove->maybeThinking = TRUE;
11808     SetMachineThinkingEnables();
11809
11810     StartClocks();
11811
11812     if(bookHit) { // [HGM] book: simulate book reply
11813         static char bookMove[MSG_SIZ]; // a bit generous?
11814
11815         programStats.nodes = programStats.depth = programStats.time =
11816         programStats.score = programStats.got_only_move = 0;
11817         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11818
11819         strcpy(bookMove, "move ");
11820         strcat(bookMove, bookHit);
11821         savedMessage = bookMove; // args for deferred call
11822         savedState = onmove;
11823         ScheduleDelayedEvent(DeferredBookMove, 1);
11824     }
11825 }
11826
11827 void
11828 TrainingEvent()
11829 {
11830     if (gameMode == Training) {
11831       SetTrainingModeOff();
11832       gameMode = PlayFromGameFile;
11833       DisplayMessage("", _("Training mode off"));
11834     } else {
11835       gameMode = Training;
11836       animateTraining = appData.animate;
11837
11838       /* make sure we are not already at the end of the game */
11839       if (currentMove < forwardMostMove) {
11840         SetTrainingModeOn();
11841         DisplayMessage("", _("Training mode on"));
11842       } else {
11843         gameMode = PlayFromGameFile;
11844         DisplayError(_("Already at end of game"), 0);
11845       }
11846     }
11847     ModeHighlight();
11848 }
11849
11850 void
11851 IcsClientEvent()
11852 {
11853     if (!appData.icsActive) return;
11854     switch (gameMode) {
11855       case IcsPlayingWhite:
11856       case IcsPlayingBlack:
11857       case IcsObserving:
11858       case IcsIdle:
11859       case BeginningOfGame:
11860       case IcsExamining:
11861         return;
11862
11863       case EditGame:
11864         break;
11865
11866       case EditPosition:
11867         EditPositionDone(TRUE);
11868         break;
11869
11870       case AnalyzeMode:
11871       case AnalyzeFile:
11872         ExitAnalyzeMode();
11873         break;
11874
11875       default:
11876         EditGameEvent();
11877         break;
11878     }
11879
11880     gameMode = IcsIdle;
11881     ModeHighlight();
11882     return;
11883 }
11884
11885
11886 void
11887 EditGameEvent()
11888 {
11889     int i;
11890
11891     switch (gameMode) {
11892       case Training:
11893         SetTrainingModeOff();
11894         break;
11895       case MachinePlaysWhite:
11896       case MachinePlaysBlack:
11897       case BeginningOfGame:
11898         SendToProgram("force\n", &first);
11899         SetUserThinkingEnables();
11900         break;
11901       case PlayFromGameFile:
11902         (void) StopLoadGameTimer();
11903         if (gameFileFP != NULL) {
11904             gameFileFP = NULL;
11905         }
11906         break;
11907       case EditPosition:
11908         EditPositionDone(TRUE);
11909         break;
11910       case AnalyzeMode:
11911       case AnalyzeFile:
11912         ExitAnalyzeMode();
11913         SendToProgram("force\n", &first);
11914         break;
11915       case TwoMachinesPlay:
11916         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11917         ResurrectChessProgram();
11918         SetUserThinkingEnables();
11919         break;
11920       case EndOfGame:
11921         ResurrectChessProgram();
11922         break;
11923       case IcsPlayingBlack:
11924       case IcsPlayingWhite:
11925         DisplayError(_("Warning: You are still playing a game"), 0);
11926         break;
11927       case IcsObserving:
11928         DisplayError(_("Warning: You are still observing a game"), 0);
11929         break;
11930       case IcsExamining:
11931         DisplayError(_("Warning: You are still examining a game"), 0);
11932         break;
11933       case IcsIdle:
11934         break;
11935       case EditGame:
11936       default:
11937         return;
11938     }
11939
11940     pausing = FALSE;
11941     StopClocks();
11942     first.offeredDraw = second.offeredDraw = 0;
11943
11944     if (gameMode == PlayFromGameFile) {
11945         whiteTimeRemaining = timeRemaining[0][currentMove];
11946         blackTimeRemaining = timeRemaining[1][currentMove];
11947         DisplayTitle("");
11948     }
11949
11950     if (gameMode == MachinePlaysWhite ||
11951         gameMode == MachinePlaysBlack ||
11952         gameMode == TwoMachinesPlay ||
11953         gameMode == EndOfGame) {
11954         i = forwardMostMove;
11955         while (i > currentMove) {
11956             SendToProgram("undo\n", &first);
11957             i--;
11958         }
11959         whiteTimeRemaining = timeRemaining[0][currentMove];
11960         blackTimeRemaining = timeRemaining[1][currentMove];
11961         DisplayBothClocks();
11962         if (whiteFlag || blackFlag) {
11963             whiteFlag = blackFlag = 0;
11964         }
11965         DisplayTitle("");
11966     }
11967
11968     gameMode = EditGame;
11969     ModeHighlight();
11970     SetGameInfo();
11971 }
11972
11973
11974 void
11975 EditPositionEvent()
11976 {
11977     if (gameMode == EditPosition) {
11978         EditGameEvent();
11979         return;
11980     }
11981
11982     EditGameEvent();
11983     if (gameMode != EditGame) return;
11984
11985     gameMode = EditPosition;
11986     ModeHighlight();
11987     SetGameInfo();
11988     if (currentMove > 0)
11989       CopyBoard(boards[0], boards[currentMove]);
11990
11991     blackPlaysFirst = !WhiteOnMove(currentMove);
11992     ResetClocks();
11993     currentMove = forwardMostMove = backwardMostMove = 0;
11994     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11995     DisplayMove(-1);
11996 }
11997
11998 void
11999 ExitAnalyzeMode()
12000 {
12001     /* [DM] icsEngineAnalyze - possible call from other functions */
12002     if (appData.icsEngineAnalyze) {
12003         appData.icsEngineAnalyze = FALSE;
12004
12005         DisplayMessage("",_("Close ICS engine analyze..."));
12006     }
12007     if (first.analysisSupport && first.analyzing) {
12008       SendToProgram("exit\n", &first);
12009       first.analyzing = FALSE;
12010     }
12011     thinkOutput[0] = NULLCHAR;
12012 }
12013
12014 void
12015 EditPositionDone(Boolean fakeRights)
12016 {
12017     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12018
12019     startedFromSetupPosition = TRUE;
12020     InitChessProgram(&first, FALSE);
12021     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12022       boards[0][EP_STATUS] = EP_NONE;
12023       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12024     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12025         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12026         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12027       } else boards[0][CASTLING][2] = NoRights;
12028     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12029         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12030         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12031       } else boards[0][CASTLING][5] = NoRights;
12032     }
12033     SendToProgram("force\n", &first);
12034     if (blackPlaysFirst) {
12035         strcpy(moveList[0], "");
12036         strcpy(parseList[0], "");
12037         currentMove = forwardMostMove = backwardMostMove = 1;
12038         CopyBoard(boards[1], boards[0]);
12039     } else {
12040         currentMove = forwardMostMove = backwardMostMove = 0;
12041     }
12042     SendBoard(&first, forwardMostMove);
12043     if (appData.debugMode) {
12044         fprintf(debugFP, "EditPosDone\n");
12045     }
12046     DisplayTitle("");
12047     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12048     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12049     gameMode = EditGame;
12050     ModeHighlight();
12051     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12052     ClearHighlights(); /* [AS] */
12053 }
12054
12055 /* Pause for `ms' milliseconds */
12056 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12057 void
12058 TimeDelay(ms)
12059      long ms;
12060 {
12061     TimeMark m1, m2;
12062
12063     GetTimeMark(&m1);
12064     do {
12065         GetTimeMark(&m2);
12066     } while (SubtractTimeMarks(&m2, &m1) < ms);
12067 }
12068
12069 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12070 void
12071 SendMultiLineToICS(buf)
12072      char *buf;
12073 {
12074     char temp[MSG_SIZ+1], *p;
12075     int len;
12076
12077     len = strlen(buf);
12078     if (len > MSG_SIZ)
12079       len = MSG_SIZ;
12080
12081     strncpy(temp, buf, len);
12082     temp[len] = 0;
12083
12084     p = temp;
12085     while (*p) {
12086         if (*p == '\n' || *p == '\r')
12087           *p = ' ';
12088         ++p;
12089     }
12090
12091     strcat(temp, "\n");
12092     SendToICS(temp);
12093     SendToPlayer(temp, strlen(temp));
12094 }
12095
12096 void
12097 SetWhiteToPlayEvent()
12098 {
12099     if (gameMode == EditPosition) {
12100         blackPlaysFirst = FALSE;
12101         DisplayBothClocks();    /* works because currentMove is 0 */
12102     } else if (gameMode == IcsExamining) {
12103         SendToICS(ics_prefix);
12104         SendToICS("tomove white\n");
12105     }
12106 }
12107
12108 void
12109 SetBlackToPlayEvent()
12110 {
12111     if (gameMode == EditPosition) {
12112         blackPlaysFirst = TRUE;
12113         currentMove = 1;        /* kludge */
12114         DisplayBothClocks();
12115         currentMove = 0;
12116     } else if (gameMode == IcsExamining) {
12117         SendToICS(ics_prefix);
12118         SendToICS("tomove black\n");
12119     }
12120 }
12121
12122 void
12123 EditPositionMenuEvent(selection, x, y)
12124      ChessSquare selection;
12125      int x, y;
12126 {
12127     char buf[MSG_SIZ];
12128     ChessSquare piece = boards[0][y][x];
12129
12130     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12131
12132     switch (selection) {
12133       case ClearBoard:
12134         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12135             SendToICS(ics_prefix);
12136             SendToICS("bsetup clear\n");
12137         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12138             SendToICS(ics_prefix);
12139             SendToICS("clearboard\n");
12140         } else {
12141             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12142                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12143                 for (y = 0; y < BOARD_HEIGHT; y++) {
12144                     if (gameMode == IcsExamining) {
12145                         if (boards[currentMove][y][x] != EmptySquare) {
12146                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12147                                     AAA + x, ONE + y);
12148                             SendToICS(buf);
12149                         }
12150                     } else {
12151                         boards[0][y][x] = p;
12152                     }
12153                 }
12154             }
12155         }
12156         if (gameMode == EditPosition) {
12157             DrawPosition(FALSE, boards[0]);
12158         }
12159         break;
12160
12161       case WhitePlay:
12162         SetWhiteToPlayEvent();
12163         break;
12164
12165       case BlackPlay:
12166         SetBlackToPlayEvent();
12167         break;
12168
12169       case EmptySquare:
12170         if (gameMode == IcsExamining) {
12171             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12172             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12173             SendToICS(buf);
12174         } else {
12175             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12176                 if(x == BOARD_LEFT-2) {
12177                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12178                     boards[0][y][1] = 0;
12179                 } else
12180                 if(x == BOARD_RGHT+1) {
12181                     if(y >= gameInfo.holdingsSize) break;
12182                     boards[0][y][BOARD_WIDTH-2] = 0;
12183                 } else break;
12184             }
12185             boards[0][y][x] = EmptySquare;
12186             DrawPosition(FALSE, boards[0]);
12187         }
12188         break;
12189
12190       case PromotePiece:
12191         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12192            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12193             selection = (ChessSquare) (PROMOTED piece);
12194         } else if(piece == EmptySquare) selection = WhiteSilver;
12195         else selection = (ChessSquare)((int)piece - 1);
12196         goto defaultlabel;
12197
12198       case DemotePiece:
12199         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12200            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12201             selection = (ChessSquare) (DEMOTED piece);
12202         } else if(piece == EmptySquare) selection = BlackSilver;
12203         else selection = (ChessSquare)((int)piece + 1);
12204         goto defaultlabel;
12205
12206       case WhiteQueen:
12207       case BlackQueen:
12208         if(gameInfo.variant == VariantShatranj ||
12209            gameInfo.variant == VariantXiangqi  ||
12210            gameInfo.variant == VariantCourier  ||
12211            gameInfo.variant == VariantMakruk     )
12212             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12213         goto defaultlabel;
12214
12215       case WhiteKing:
12216       case BlackKing:
12217         if(gameInfo.variant == VariantXiangqi)
12218             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12219         if(gameInfo.variant == VariantKnightmate)
12220             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12221       default:
12222         defaultlabel:
12223         if (gameMode == IcsExamining) {
12224             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12225             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12226                     PieceToChar(selection), AAA + x, ONE + y);
12227             SendToICS(buf);
12228         } else {
12229             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12230                 int n;
12231                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12232                     n = PieceToNumber(selection - BlackPawn);
12233                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12234                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12235                     boards[0][BOARD_HEIGHT-1-n][1]++;
12236                 } else
12237                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12238                     n = PieceToNumber(selection);
12239                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12240                     boards[0][n][BOARD_WIDTH-1] = selection;
12241                     boards[0][n][BOARD_WIDTH-2]++;
12242                 }
12243             } else
12244             boards[0][y][x] = selection;
12245             DrawPosition(TRUE, boards[0]);
12246         }
12247         break;
12248     }
12249 }
12250
12251
12252 void
12253 DropMenuEvent(selection, x, y)
12254      ChessSquare selection;
12255      int x, y;
12256 {
12257     ChessMove moveType;
12258
12259     switch (gameMode) {
12260       case IcsPlayingWhite:
12261       case MachinePlaysBlack:
12262         if (!WhiteOnMove(currentMove)) {
12263             DisplayMoveError(_("It is Black's turn"));
12264             return;
12265         }
12266         moveType = WhiteDrop;
12267         break;
12268       case IcsPlayingBlack:
12269       case MachinePlaysWhite:
12270         if (WhiteOnMove(currentMove)) {
12271             DisplayMoveError(_("It is White's turn"));
12272             return;
12273         }
12274         moveType = BlackDrop;
12275         break;
12276       case EditGame:
12277         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12278         break;
12279       default:
12280         return;
12281     }
12282
12283     if (moveType == BlackDrop && selection < BlackPawn) {
12284       selection = (ChessSquare) ((int) selection
12285                                  + (int) BlackPawn - (int) WhitePawn);
12286     }
12287     if (boards[currentMove][y][x] != EmptySquare) {
12288         DisplayMoveError(_("That square is occupied"));
12289         return;
12290     }
12291
12292     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12293 }
12294
12295 void
12296 AcceptEvent()
12297 {
12298     /* Accept a pending offer of any kind from opponent */
12299
12300     if (appData.icsActive) {
12301         SendToICS(ics_prefix);
12302         SendToICS("accept\n");
12303     } else if (cmailMsgLoaded) {
12304         if (currentMove == cmailOldMove &&
12305             commentList[cmailOldMove] != NULL &&
12306             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12307                    "Black offers a draw" : "White offers a draw")) {
12308             TruncateGame();
12309             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12310             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12311         } else {
12312             DisplayError(_("There is no pending offer on this move"), 0);
12313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12314         }
12315     } else {
12316         /* Not used for offers from chess program */
12317     }
12318 }
12319
12320 void
12321 DeclineEvent()
12322 {
12323     /* Decline a pending offer of any kind from opponent */
12324
12325     if (appData.icsActive) {
12326         SendToICS(ics_prefix);
12327         SendToICS("decline\n");
12328     } else if (cmailMsgLoaded) {
12329         if (currentMove == cmailOldMove &&
12330             commentList[cmailOldMove] != NULL &&
12331             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12332                    "Black offers a draw" : "White offers a draw")) {
12333 #ifdef NOTDEF
12334             AppendComment(cmailOldMove, "Draw declined", TRUE);
12335             DisplayComment(cmailOldMove - 1, "Draw declined");
12336 #endif /*NOTDEF*/
12337         } else {
12338             DisplayError(_("There is no pending offer on this move"), 0);
12339         }
12340     } else {
12341         /* Not used for offers from chess program */
12342     }
12343 }
12344
12345 void
12346 RematchEvent()
12347 {
12348     /* Issue ICS rematch command */
12349     if (appData.icsActive) {
12350         SendToICS(ics_prefix);
12351         SendToICS("rematch\n");
12352     }
12353 }
12354
12355 void
12356 CallFlagEvent()
12357 {
12358     /* Call your opponent's flag (claim a win on time) */
12359     if (appData.icsActive) {
12360         SendToICS(ics_prefix);
12361         SendToICS("flag\n");
12362     } else {
12363         switch (gameMode) {
12364           default:
12365             return;
12366           case MachinePlaysWhite:
12367             if (whiteFlag) {
12368                 if (blackFlag)
12369                   GameEnds(GameIsDrawn, "Both players ran out of time",
12370                            GE_PLAYER);
12371                 else
12372                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12373             } else {
12374                 DisplayError(_("Your opponent is not out of time"), 0);
12375             }
12376             break;
12377           case MachinePlaysBlack:
12378             if (blackFlag) {
12379                 if (whiteFlag)
12380                   GameEnds(GameIsDrawn, "Both players ran out of time",
12381                            GE_PLAYER);
12382                 else
12383                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12384             } else {
12385                 DisplayError(_("Your opponent is not out of time"), 0);
12386             }
12387             break;
12388         }
12389     }
12390 }
12391
12392 void
12393 DrawEvent()
12394 {
12395     /* Offer draw or accept pending draw offer from opponent */
12396
12397     if (appData.icsActive) {
12398         /* Note: tournament rules require draw offers to be
12399            made after you make your move but before you punch
12400            your clock.  Currently ICS doesn't let you do that;
12401            instead, you immediately punch your clock after making
12402            a move, but you can offer a draw at any time. */
12403
12404         SendToICS(ics_prefix);
12405         SendToICS("draw\n");
12406         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12407     } else if (cmailMsgLoaded) {
12408         if (currentMove == cmailOldMove &&
12409             commentList[cmailOldMove] != NULL &&
12410             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12411                    "Black offers a draw" : "White offers a draw")) {
12412             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12413             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12414         } else if (currentMove == cmailOldMove + 1) {
12415             char *offer = WhiteOnMove(cmailOldMove) ?
12416               "White offers a draw" : "Black offers a draw";
12417             AppendComment(currentMove, offer, TRUE);
12418             DisplayComment(currentMove - 1, offer);
12419             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12420         } else {
12421             DisplayError(_("You must make your move before offering a draw"), 0);
12422             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12423         }
12424     } else if (first.offeredDraw) {
12425         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12426     } else {
12427         if (first.sendDrawOffers) {
12428             SendToProgram("draw\n", &first);
12429             userOfferedDraw = TRUE;
12430         }
12431     }
12432 }
12433
12434 void
12435 AdjournEvent()
12436 {
12437     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12438
12439     if (appData.icsActive) {
12440         SendToICS(ics_prefix);
12441         SendToICS("adjourn\n");
12442     } else {
12443         /* Currently GNU Chess doesn't offer or accept Adjourns */
12444     }
12445 }
12446
12447
12448 void
12449 AbortEvent()
12450 {
12451     /* Offer Abort or accept pending Abort offer from opponent */
12452
12453     if (appData.icsActive) {
12454         SendToICS(ics_prefix);
12455         SendToICS("abort\n");
12456     } else {
12457         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12458     }
12459 }
12460
12461 void
12462 ResignEvent()
12463 {
12464     /* Resign.  You can do this even if it's not your turn. */
12465
12466     if (appData.icsActive) {
12467         SendToICS(ics_prefix);
12468         SendToICS("resign\n");
12469     } else {
12470         switch (gameMode) {
12471           case MachinePlaysWhite:
12472             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12473             break;
12474           case MachinePlaysBlack:
12475             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12476             break;
12477           case EditGame:
12478             if (cmailMsgLoaded) {
12479                 TruncateGame();
12480                 if (WhiteOnMove(cmailOldMove)) {
12481                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12482                 } else {
12483                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12484                 }
12485                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12486             }
12487             break;
12488           default:
12489             break;
12490         }
12491     }
12492 }
12493
12494
12495 void
12496 StopObservingEvent()
12497 {
12498     /* Stop observing current games */
12499     SendToICS(ics_prefix);
12500     SendToICS("unobserve\n");
12501 }
12502
12503 void
12504 StopExaminingEvent()
12505 {
12506     /* Stop observing current game */
12507     SendToICS(ics_prefix);
12508     SendToICS("unexamine\n");
12509 }
12510
12511 void
12512 ForwardInner(target)
12513      int target;
12514 {
12515     int limit;
12516
12517     if (appData.debugMode)
12518         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12519                 target, currentMove, forwardMostMove);
12520
12521     if (gameMode == EditPosition)
12522       return;
12523
12524     if (gameMode == PlayFromGameFile && !pausing)
12525       PauseEvent();
12526
12527     if (gameMode == IcsExamining && pausing)
12528       limit = pauseExamForwardMostMove;
12529     else
12530       limit = forwardMostMove;
12531
12532     if (target > limit) target = limit;
12533
12534     if (target > 0 && moveList[target - 1][0]) {
12535         int fromX, fromY, toX, toY;
12536         toX = moveList[target - 1][2] - AAA;
12537         toY = moveList[target - 1][3] - ONE;
12538         if (moveList[target - 1][1] == '@') {
12539             if (appData.highlightLastMove) {
12540                 SetHighlights(-1, -1, toX, toY);
12541             }
12542         } else {
12543             fromX = moveList[target - 1][0] - AAA;
12544             fromY = moveList[target - 1][1] - ONE;
12545             if (target == currentMove + 1) {
12546                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12547             }
12548             if (appData.highlightLastMove) {
12549                 SetHighlights(fromX, fromY, toX, toY);
12550             }
12551         }
12552     }
12553     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12554         gameMode == Training || gameMode == PlayFromGameFile ||
12555         gameMode == AnalyzeFile) {
12556         while (currentMove < target) {
12557             SendMoveToProgram(currentMove++, &first);
12558         }
12559     } else {
12560         currentMove = target;
12561     }
12562
12563     if (gameMode == EditGame || gameMode == EndOfGame) {
12564         whiteTimeRemaining = timeRemaining[0][currentMove];
12565         blackTimeRemaining = timeRemaining[1][currentMove];
12566     }
12567     DisplayBothClocks();
12568     DisplayMove(currentMove - 1);
12569     DrawPosition(FALSE, boards[currentMove]);
12570     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12571     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12572         DisplayComment(currentMove - 1, commentList[currentMove]);
12573     }
12574 }
12575
12576
12577 void
12578 ForwardEvent()
12579 {
12580     if (gameMode == IcsExamining && !pausing) {
12581         SendToICS(ics_prefix);
12582         SendToICS("forward\n");
12583     } else {
12584         ForwardInner(currentMove + 1);
12585     }
12586 }
12587
12588 void
12589 ToEndEvent()
12590 {
12591     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12592         /* to optimze, we temporarily turn off analysis mode while we feed
12593          * the remaining moves to the engine. Otherwise we get analysis output
12594          * after each move.
12595          */
12596         if (first.analysisSupport) {
12597           SendToProgram("exit\nforce\n", &first);
12598           first.analyzing = FALSE;
12599         }
12600     }
12601
12602     if (gameMode == IcsExamining && !pausing) {
12603         SendToICS(ics_prefix);
12604         SendToICS("forward 999999\n");
12605     } else {
12606         ForwardInner(forwardMostMove);
12607     }
12608
12609     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12610         /* we have fed all the moves, so reactivate analysis mode */
12611         SendToProgram("analyze\n", &first);
12612         first.analyzing = TRUE;
12613         /*first.maybeThinking = TRUE;*/
12614         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12615     }
12616 }
12617
12618 void
12619 BackwardInner(target)
12620      int target;
12621 {
12622     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12623
12624     if (appData.debugMode)
12625         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12626                 target, currentMove, forwardMostMove);
12627
12628     if (gameMode == EditPosition) return;
12629     if (currentMove <= backwardMostMove) {
12630         ClearHighlights();
12631         DrawPosition(full_redraw, boards[currentMove]);
12632         return;
12633     }
12634     if (gameMode == PlayFromGameFile && !pausing)
12635       PauseEvent();
12636
12637     if (moveList[target][0]) {
12638         int fromX, fromY, toX, toY;
12639         toX = moveList[target][2] - AAA;
12640         toY = moveList[target][3] - ONE;
12641         if (moveList[target][1] == '@') {
12642             if (appData.highlightLastMove) {
12643                 SetHighlights(-1, -1, toX, toY);
12644             }
12645         } else {
12646             fromX = moveList[target][0] - AAA;
12647             fromY = moveList[target][1] - ONE;
12648             if (target == currentMove - 1) {
12649                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12650             }
12651             if (appData.highlightLastMove) {
12652                 SetHighlights(fromX, fromY, toX, toY);
12653             }
12654         }
12655     }
12656     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12657         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12658         while (currentMove > target) {
12659             SendToProgram("undo\n", &first);
12660             currentMove--;
12661         }
12662     } else {
12663         currentMove = target;
12664     }
12665
12666     if (gameMode == EditGame || gameMode == EndOfGame) {
12667         whiteTimeRemaining = timeRemaining[0][currentMove];
12668         blackTimeRemaining = timeRemaining[1][currentMove];
12669     }
12670     DisplayBothClocks();
12671     DisplayMove(currentMove - 1);
12672     DrawPosition(full_redraw, boards[currentMove]);
12673     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12674     // [HGM] PV info: routine tests if comment empty
12675     DisplayComment(currentMove - 1, commentList[currentMove]);
12676 }
12677
12678 void
12679 BackwardEvent()
12680 {
12681     if (gameMode == IcsExamining && !pausing) {
12682         SendToICS(ics_prefix);
12683         SendToICS("backward\n");
12684     } else {
12685         BackwardInner(currentMove - 1);
12686     }
12687 }
12688
12689 void
12690 ToStartEvent()
12691 {
12692     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12693         /* to optimize, we temporarily turn off analysis mode while we undo
12694          * all the moves. Otherwise we get analysis output after each undo.
12695          */
12696         if (first.analysisSupport) {
12697           SendToProgram("exit\nforce\n", &first);
12698           first.analyzing = FALSE;
12699         }
12700     }
12701
12702     if (gameMode == IcsExamining && !pausing) {
12703         SendToICS(ics_prefix);
12704         SendToICS("backward 999999\n");
12705     } else {
12706         BackwardInner(backwardMostMove);
12707     }
12708
12709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12710         /* we have fed all the moves, so reactivate analysis mode */
12711         SendToProgram("analyze\n", &first);
12712         first.analyzing = TRUE;
12713         /*first.maybeThinking = TRUE;*/
12714         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12715     }
12716 }
12717
12718 void
12719 ToNrEvent(int to)
12720 {
12721   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12722   if (to >= forwardMostMove) to = forwardMostMove;
12723   if (to <= backwardMostMove) to = backwardMostMove;
12724   if (to < currentMove) {
12725     BackwardInner(to);
12726   } else {
12727     ForwardInner(to);
12728   }
12729 }
12730
12731 void
12732 RevertEvent()
12733 {
12734     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12735         return;
12736     }
12737     if (gameMode != IcsExamining) {
12738         DisplayError(_("You are not examining a game"), 0);
12739         return;
12740     }
12741     if (pausing) {
12742         DisplayError(_("You can't revert while pausing"), 0);
12743         return;
12744     }
12745     SendToICS(ics_prefix);
12746     SendToICS("revert\n");
12747 }
12748
12749 void
12750 RetractMoveEvent()
12751 {
12752     switch (gameMode) {
12753       case MachinePlaysWhite:
12754       case MachinePlaysBlack:
12755         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12756             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12757             return;
12758         }
12759         if (forwardMostMove < 2) return;
12760         currentMove = forwardMostMove = forwardMostMove - 2;
12761         whiteTimeRemaining = timeRemaining[0][currentMove];
12762         blackTimeRemaining = timeRemaining[1][currentMove];
12763         DisplayBothClocks();
12764         DisplayMove(currentMove - 1);
12765         ClearHighlights();/*!! could figure this out*/
12766         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12767         SendToProgram("remove\n", &first);
12768         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12769         break;
12770
12771       case BeginningOfGame:
12772       default:
12773         break;
12774
12775       case IcsPlayingWhite:
12776       case IcsPlayingBlack:
12777         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12778             SendToICS(ics_prefix);
12779             SendToICS("takeback 2\n");
12780         } else {
12781             SendToICS(ics_prefix);
12782             SendToICS("takeback 1\n");
12783         }
12784         break;
12785     }
12786 }
12787
12788 void
12789 MoveNowEvent()
12790 {
12791     ChessProgramState *cps;
12792
12793     switch (gameMode) {
12794       case MachinePlaysWhite:
12795         if (!WhiteOnMove(forwardMostMove)) {
12796             DisplayError(_("It is your turn"), 0);
12797             return;
12798         }
12799         cps = &first;
12800         break;
12801       case MachinePlaysBlack:
12802         if (WhiteOnMove(forwardMostMove)) {
12803             DisplayError(_("It is your turn"), 0);
12804             return;
12805         }
12806         cps = &first;
12807         break;
12808       case TwoMachinesPlay:
12809         if (WhiteOnMove(forwardMostMove) ==
12810             (first.twoMachinesColor[0] == 'w')) {
12811             cps = &first;
12812         } else {
12813             cps = &second;
12814         }
12815         break;
12816       case BeginningOfGame:
12817       default:
12818         return;
12819     }
12820     SendToProgram("?\n", cps);
12821 }
12822
12823 void
12824 TruncateGameEvent()
12825 {
12826     EditGameEvent();
12827     if (gameMode != EditGame) return;
12828     TruncateGame();
12829 }
12830
12831 void
12832 TruncateGame()
12833 {
12834     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12835     if (forwardMostMove > currentMove) {
12836         if (gameInfo.resultDetails != NULL) {
12837             free(gameInfo.resultDetails);
12838             gameInfo.resultDetails = NULL;
12839             gameInfo.result = GameUnfinished;
12840         }
12841         forwardMostMove = currentMove;
12842         HistorySet(parseList, backwardMostMove, forwardMostMove,
12843                    currentMove-1);
12844     }
12845 }
12846
12847 void
12848 HintEvent()
12849 {
12850     if (appData.noChessProgram) return;
12851     switch (gameMode) {
12852       case MachinePlaysWhite:
12853         if (WhiteOnMove(forwardMostMove)) {
12854             DisplayError(_("Wait until your turn"), 0);
12855             return;
12856         }
12857         break;
12858       case BeginningOfGame:
12859       case MachinePlaysBlack:
12860         if (!WhiteOnMove(forwardMostMove)) {
12861             DisplayError(_("Wait until your turn"), 0);
12862             return;
12863         }
12864         break;
12865       default:
12866         DisplayError(_("No hint available"), 0);
12867         return;
12868     }
12869     SendToProgram("hint\n", &first);
12870     hintRequested = TRUE;
12871 }
12872
12873 void
12874 BookEvent()
12875 {
12876     if (appData.noChessProgram) return;
12877     switch (gameMode) {
12878       case MachinePlaysWhite:
12879         if (WhiteOnMove(forwardMostMove)) {
12880             DisplayError(_("Wait until your turn"), 0);
12881             return;
12882         }
12883         break;
12884       case BeginningOfGame:
12885       case MachinePlaysBlack:
12886         if (!WhiteOnMove(forwardMostMove)) {
12887             DisplayError(_("Wait until your turn"), 0);
12888             return;
12889         }
12890         break;
12891       case EditPosition:
12892         EditPositionDone(TRUE);
12893         break;
12894       case TwoMachinesPlay:
12895         return;
12896       default:
12897         break;
12898     }
12899     SendToProgram("bk\n", &first);
12900     bookOutput[0] = NULLCHAR;
12901     bookRequested = TRUE;
12902 }
12903
12904 void
12905 AboutGameEvent()
12906 {
12907     char *tags = PGNTags(&gameInfo);
12908     TagsPopUp(tags, CmailMsg());
12909     free(tags);
12910 }
12911
12912 /* end button procedures */
12913
12914 void
12915 PrintPosition(fp, move)
12916      FILE *fp;
12917      int move;
12918 {
12919     int i, j;
12920
12921     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12922         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12923             char c = PieceToChar(boards[move][i][j]);
12924             fputc(c == 'x' ? '.' : c, fp);
12925             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12926         }
12927     }
12928     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12929       fprintf(fp, "white to play\n");
12930     else
12931       fprintf(fp, "black to play\n");
12932 }
12933
12934 void
12935 PrintOpponents(fp)
12936      FILE *fp;
12937 {
12938     if (gameInfo.white != NULL) {
12939         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12940     } else {
12941         fprintf(fp, "\n");
12942     }
12943 }
12944
12945 /* Find last component of program's own name, using some heuristics */
12946 void
12947 TidyProgramName(prog, host, buf)
12948      char *prog, *host, buf[MSG_SIZ];
12949 {
12950     char *p, *q;
12951     int local = (strcmp(host, "localhost") == 0);
12952     while (!local && (p = strchr(prog, ';')) != NULL) {
12953         p++;
12954         while (*p == ' ') p++;
12955         prog = p;
12956     }
12957     if (*prog == '"' || *prog == '\'') {
12958         q = strchr(prog + 1, *prog);
12959     } else {
12960         q = strchr(prog, ' ');
12961     }
12962     if (q == NULL) q = prog + strlen(prog);
12963     p = q;
12964     while (p >= prog && *p != '/' && *p != '\\') p--;
12965     p++;
12966     if(p == prog && *p == '"') p++;
12967     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12968     memcpy(buf, p, q - p);
12969     buf[q - p] = NULLCHAR;
12970     if (!local) {
12971         strcat(buf, "@");
12972         strcat(buf, host);
12973     }
12974 }
12975
12976 char *
12977 TimeControlTagValue()
12978 {
12979     char buf[MSG_SIZ];
12980     if (!appData.clockMode) {
12981         strcpy(buf, "-");
12982     } else if (movesPerSession > 0) {
12983         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12984     } else if (timeIncrement == 0) {
12985         sprintf(buf, "%ld", timeControl/1000);
12986     } else {
12987         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12988     }
12989     return StrSave(buf);
12990 }
12991
12992 void
12993 SetGameInfo()
12994 {
12995     /* This routine is used only for certain modes */
12996     VariantClass v = gameInfo.variant;
12997     ChessMove r = GameUnfinished;
12998     char *p = NULL;
12999
13000     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13001         r = gameInfo.result; 
13002         p = gameInfo.resultDetails; 
13003         gameInfo.resultDetails = NULL;
13004     }
13005     ClearGameInfo(&gameInfo);
13006     gameInfo.variant = v;
13007
13008     switch (gameMode) {
13009       case MachinePlaysWhite:
13010         gameInfo.event = StrSave( appData.pgnEventHeader );
13011         gameInfo.site = StrSave(HostName());
13012         gameInfo.date = PGNDate();
13013         gameInfo.round = StrSave("-");
13014         gameInfo.white = StrSave(first.tidy);
13015         gameInfo.black = StrSave(UserName());
13016         gameInfo.timeControl = TimeControlTagValue();
13017         break;
13018
13019       case MachinePlaysBlack:
13020         gameInfo.event = StrSave( appData.pgnEventHeader );
13021         gameInfo.site = StrSave(HostName());
13022         gameInfo.date = PGNDate();
13023         gameInfo.round = StrSave("-");
13024         gameInfo.white = StrSave(UserName());
13025         gameInfo.black = StrSave(first.tidy);
13026         gameInfo.timeControl = TimeControlTagValue();
13027         break;
13028
13029       case TwoMachinesPlay:
13030         gameInfo.event = StrSave( appData.pgnEventHeader );
13031         gameInfo.site = StrSave(HostName());
13032         gameInfo.date = PGNDate();
13033         if (matchGame > 0) {
13034             char buf[MSG_SIZ];
13035             sprintf(buf, "%d", matchGame);
13036             gameInfo.round = StrSave(buf);
13037         } else {
13038             gameInfo.round = StrSave("-");
13039         }
13040         if (first.twoMachinesColor[0] == 'w') {
13041             gameInfo.white = StrSave(first.tidy);
13042             gameInfo.black = StrSave(second.tidy);
13043         } else {
13044             gameInfo.white = StrSave(second.tidy);
13045             gameInfo.black = StrSave(first.tidy);
13046         }
13047         gameInfo.timeControl = TimeControlTagValue();
13048         break;
13049
13050       case EditGame:
13051         gameInfo.event = StrSave("Edited game");
13052         gameInfo.site = StrSave(HostName());
13053         gameInfo.date = PGNDate();
13054         gameInfo.round = StrSave("-");
13055         gameInfo.white = StrSave("-");
13056         gameInfo.black = StrSave("-");
13057         gameInfo.result = r;
13058         gameInfo.resultDetails = p;
13059         break;
13060
13061       case EditPosition:
13062         gameInfo.event = StrSave("Edited position");
13063         gameInfo.site = StrSave(HostName());
13064         gameInfo.date = PGNDate();
13065         gameInfo.round = StrSave("-");
13066         gameInfo.white = StrSave("-");
13067         gameInfo.black = StrSave("-");
13068         break;
13069
13070       case IcsPlayingWhite:
13071       case IcsPlayingBlack:
13072       case IcsObserving:
13073       case IcsExamining:
13074         break;
13075
13076       case PlayFromGameFile:
13077         gameInfo.event = StrSave("Game from non-PGN file");
13078         gameInfo.site = StrSave(HostName());
13079         gameInfo.date = PGNDate();
13080         gameInfo.round = StrSave("-");
13081         gameInfo.white = StrSave("?");
13082         gameInfo.black = StrSave("?");
13083         break;
13084
13085       default:
13086         break;
13087     }
13088 }
13089
13090 void
13091 ReplaceComment(index, text)
13092      int index;
13093      char *text;
13094 {
13095     int len;
13096
13097     while (*text == '\n') text++;
13098     len = strlen(text);
13099     while (len > 0 && text[len - 1] == '\n') len--;
13100
13101     if (commentList[index] != NULL)
13102       free(commentList[index]);
13103
13104     if (len == 0) {
13105         commentList[index] = NULL;
13106         return;
13107     }
13108   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13109       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13110       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13111     commentList[index] = (char *) malloc(len + 2);
13112     strncpy(commentList[index], text, len);
13113     commentList[index][len] = '\n';
13114     commentList[index][len + 1] = NULLCHAR;
13115   } else { 
13116     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13117     char *p;
13118     commentList[index] = (char *) malloc(len + 6);
13119     strcpy(commentList[index], "{\n");
13120     strncpy(commentList[index]+2, text, len);
13121     commentList[index][len+2] = NULLCHAR;
13122     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13123     strcat(commentList[index], "\n}\n");
13124   }
13125 }
13126
13127 void
13128 CrushCRs(text)
13129      char *text;
13130 {
13131   char *p = text;
13132   char *q = text;
13133   char ch;
13134
13135   do {
13136     ch = *p++;
13137     if (ch == '\r') continue;
13138     *q++ = ch;
13139   } while (ch != '\0');
13140 }
13141
13142 void
13143 AppendComment(index, text, addBraces)
13144      int index;
13145      char *text;
13146      Boolean addBraces; // [HGM] braces: tells if we should add {}
13147 {
13148     int oldlen, len;
13149     char *old;
13150
13151 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13152     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13153
13154     CrushCRs(text);
13155     while (*text == '\n') text++;
13156     len = strlen(text);
13157     while (len > 0 && text[len - 1] == '\n') len--;
13158
13159     if (len == 0) return;
13160
13161     if (commentList[index] != NULL) {
13162         old = commentList[index];
13163         oldlen = strlen(old);
13164         while(commentList[index][oldlen-1] ==  '\n')
13165           commentList[index][--oldlen] = NULLCHAR;
13166         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13167         strcpy(commentList[index], old);
13168         free(old);
13169         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13170         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13171           if(addBraces) addBraces = FALSE; else { text++; len--; }
13172           while (*text == '\n') { text++; len--; }
13173           commentList[index][--oldlen] = NULLCHAR;
13174       }
13175         if(addBraces) strcat(commentList[index], "\n{\n");
13176         else          strcat(commentList[index], "\n");
13177         strcat(commentList[index], text);
13178         if(addBraces) strcat(commentList[index], "\n}\n");
13179         else          strcat(commentList[index], "\n");
13180     } else {
13181         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13182         if(addBraces)
13183              strcpy(commentList[index], "{\n");
13184         else commentList[index][0] = NULLCHAR;
13185         strcat(commentList[index], text);
13186         strcat(commentList[index], "\n");
13187         if(addBraces) strcat(commentList[index], "}\n");
13188     }
13189 }
13190
13191 static char * FindStr( char * text, char * sub_text )
13192 {
13193     char * result = strstr( text, sub_text );
13194
13195     if( result != NULL ) {
13196         result += strlen( sub_text );
13197     }
13198
13199     return result;
13200 }
13201
13202 /* [AS] Try to extract PV info from PGN comment */
13203 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13204 char *GetInfoFromComment( int index, char * text )
13205 {
13206     char * sep = text;
13207
13208     if( text != NULL && index > 0 ) {
13209         int score = 0;
13210         int depth = 0;
13211         int time = -1, sec = 0, deci;
13212         char * s_eval = FindStr( text, "[%eval " );
13213         char * s_emt = FindStr( text, "[%emt " );
13214
13215         if( s_eval != NULL || s_emt != NULL ) {
13216             /* New style */
13217             char delim;
13218
13219             if( s_eval != NULL ) {
13220                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13221                     return text;
13222                 }
13223
13224                 if( delim != ']' ) {
13225                     return text;
13226                 }
13227             }
13228
13229             if( s_emt != NULL ) {
13230             }
13231                 return text;
13232         }
13233         else {
13234             /* We expect something like: [+|-]nnn.nn/dd */
13235             int score_lo = 0;
13236
13237             if(*text != '{') return text; // [HGM] braces: must be normal comment
13238
13239             sep = strchr( text, '/' );
13240             if( sep == NULL || sep < (text+4) ) {
13241                 return text;
13242             }
13243
13244             time = -1; sec = -1; deci = -1;
13245             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13246                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13247                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13248                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13249                 return text;
13250             }
13251
13252             if( score_lo < 0 || score_lo >= 100 ) {
13253                 return text;
13254             }
13255
13256             if(sec >= 0) time = 600*time + 10*sec; else
13257             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13258
13259             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13260
13261             /* [HGM] PV time: now locate end of PV info */
13262             while( *++sep >= '0' && *sep <= '9'); // strip depth
13263             if(time >= 0)
13264             while( *++sep >= '0' && *sep <= '9'); // strip time
13265             if(sec >= 0)
13266             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13267             if(deci >= 0)
13268             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13269             while(*sep == ' ') sep++;
13270         }
13271
13272         if( depth <= 0 ) {
13273             return text;
13274         }
13275
13276         if( time < 0 ) {
13277             time = -1;
13278         }
13279
13280         pvInfoList[index-1].depth = depth;
13281         pvInfoList[index-1].score = score;
13282         pvInfoList[index-1].time  = 10*time; // centi-sec
13283         if(*sep == '}') *sep = 0; else *--sep = '{';
13284     }
13285     return sep;
13286 }
13287
13288 void
13289 SendToProgram(message, cps)
13290      char *message;
13291      ChessProgramState *cps;
13292 {
13293     int count, outCount, error;
13294     char buf[MSG_SIZ];
13295
13296     if (cps->pr == NULL) return;
13297     Attention(cps);
13298
13299     if (appData.debugMode) {
13300         TimeMark now;
13301         GetTimeMark(&now);
13302         fprintf(debugFP, "%ld >%-6s: %s",
13303                 SubtractTimeMarks(&now, &programStartTime),
13304                 cps->which, message);
13305     }
13306
13307     count = strlen(message);
13308     outCount = OutputToProcess(cps->pr, message, count, &error);
13309     if (outCount < count && !exiting
13310                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13311         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13312         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13313             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13314                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13315                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13316             } else {
13317                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13318             }
13319             gameInfo.resultDetails = StrSave(buf);
13320         }
13321         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13322     }
13323 }
13324
13325 void
13326 ReceiveFromProgram(isr, closure, message, count, error)
13327      InputSourceRef isr;
13328      VOIDSTAR closure;
13329      char *message;
13330      int count;
13331      int error;
13332 {
13333     char *end_str;
13334     char buf[MSG_SIZ];
13335     ChessProgramState *cps = (ChessProgramState *)closure;
13336
13337     if (isr != cps->isr) return; /* Killed intentionally */
13338     if (count <= 0) {
13339         if (count == 0) {
13340             sprintf(buf,
13341                     _("Error: %s chess program (%s) exited unexpectedly"),
13342                     cps->which, cps->program);
13343         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13344                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13345                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13346                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13347                 } else {
13348                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13349                 }
13350                 gameInfo.resultDetails = StrSave(buf);
13351             }
13352             RemoveInputSource(cps->isr);
13353             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13354         } else {
13355             sprintf(buf,
13356                     _("Error reading from %s chess program (%s)"),
13357                     cps->which, cps->program);
13358             RemoveInputSource(cps->isr);
13359
13360             /* [AS] Program is misbehaving badly... kill it */
13361             if( count == -2 ) {
13362                 DestroyChildProcess( cps->pr, 9 );
13363                 cps->pr = NoProc;
13364             }
13365
13366             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13367         }
13368         return;
13369     }
13370
13371     if ((end_str = strchr(message, '\r')) != NULL)
13372       *end_str = NULLCHAR;
13373     if ((end_str = strchr(message, '\n')) != NULL)
13374       *end_str = NULLCHAR;
13375
13376     if (appData.debugMode) {
13377         TimeMark now; int print = 1;
13378         char *quote = ""; char c; int i;
13379
13380         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13381                 char start = message[0];
13382                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13383                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13384                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13385                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13386                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13387                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13388                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13389                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13390                         { quote = "# "; print = (appData.engineComments == 2); }
13391                 message[0] = start; // restore original message
13392         }
13393         if(print) {
13394                 GetTimeMark(&now);
13395                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13396                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13397                         quote,
13398                         message);
13399         }
13400     }
13401
13402     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13403     if (appData.icsEngineAnalyze) {
13404         if (strstr(message, "whisper") != NULL ||
13405              strstr(message, "kibitz") != NULL ||
13406             strstr(message, "tellics") != NULL) return;
13407     }
13408
13409     HandleMachineMove(message, cps);
13410 }
13411
13412
13413 void
13414 SendTimeControl(cps, mps, tc, inc, sd, st)
13415      ChessProgramState *cps;
13416      int mps, inc, sd, st;
13417      long tc;
13418 {
13419     char buf[MSG_SIZ];
13420     int seconds;
13421
13422     if( timeControl_2 > 0 ) {
13423         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13424             tc = timeControl_2;
13425         }
13426     }
13427     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13428     inc /= cps->timeOdds;
13429     st  /= cps->timeOdds;
13430
13431     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13432
13433     if (st > 0) {
13434       /* Set exact time per move, normally using st command */
13435       if (cps->stKludge) {
13436         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13437         seconds = st % 60;
13438         if (seconds == 0) {
13439           sprintf(buf, "level 1 %d\n", st/60);
13440         } else {
13441           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13442         }
13443       } else {
13444         sprintf(buf, "st %d\n", st);
13445       }
13446     } else {
13447       /* Set conventional or incremental time control, using level command */
13448       if (seconds == 0) {
13449         /* Note old gnuchess bug -- minutes:seconds used to not work.
13450            Fixed in later versions, but still avoid :seconds
13451            when seconds is 0. */
13452         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13453       } else {
13454         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13455                 seconds, inc/1000);
13456       }
13457     }
13458     SendToProgram(buf, cps);
13459
13460     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13461     /* Orthogonally, limit search to given depth */
13462     if (sd > 0) {
13463       if (cps->sdKludge) {
13464         sprintf(buf, "depth\n%d\n", sd);
13465       } else {
13466         sprintf(buf, "sd %d\n", sd);
13467       }
13468       SendToProgram(buf, cps);
13469     }
13470
13471     if(cps->nps > 0) { /* [HGM] nps */
13472         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13473         else {
13474                 sprintf(buf, "nps %d\n", cps->nps);
13475               SendToProgram(buf, cps);
13476         }
13477     }
13478 }
13479
13480 ChessProgramState *WhitePlayer()
13481 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13482 {
13483     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13484        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13485         return &second;
13486     return &first;
13487 }
13488
13489 void
13490 SendTimeRemaining(cps, machineWhite)
13491      ChessProgramState *cps;
13492      int /*boolean*/ machineWhite;
13493 {
13494     char message[MSG_SIZ];
13495     long time, otime;
13496
13497     /* Note: this routine must be called when the clocks are stopped
13498        or when they have *just* been set or switched; otherwise
13499        it will be off by the time since the current tick started.
13500     */
13501     if (machineWhite) {
13502         time = whiteTimeRemaining / 10;
13503         otime = blackTimeRemaining / 10;
13504     } else {
13505         time = blackTimeRemaining / 10;
13506         otime = whiteTimeRemaining / 10;
13507     }
13508     /* [HGM] translate opponent's time by time-odds factor */
13509     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13510     if (appData.debugMode) {
13511         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13512     }
13513
13514     if (time <= 0) time = 1;
13515     if (otime <= 0) otime = 1;
13516
13517     sprintf(message, "time %ld\n", time);
13518     SendToProgram(message, cps);
13519
13520     sprintf(message, "otim %ld\n", otime);
13521     SendToProgram(message, cps);
13522 }
13523
13524 int
13525 BoolFeature(p, name, loc, cps)
13526      char **p;
13527      char *name;
13528      int *loc;
13529      ChessProgramState *cps;
13530 {
13531   char buf[MSG_SIZ];
13532   int len = strlen(name);
13533   int val;
13534   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13535     (*p) += len + 1;
13536     sscanf(*p, "%d", &val);
13537     *loc = (val != 0);
13538     while (**p && **p != ' ') (*p)++;
13539     sprintf(buf, "accepted %s\n", name);
13540     SendToProgram(buf, cps);
13541     return TRUE;
13542   }
13543   return FALSE;
13544 }
13545
13546 int
13547 IntFeature(p, name, loc, cps)
13548      char **p;
13549      char *name;
13550      int *loc;
13551      ChessProgramState *cps;
13552 {
13553   char buf[MSG_SIZ];
13554   int len = strlen(name);
13555   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13556     (*p) += len + 1;
13557     sscanf(*p, "%d", loc);
13558     while (**p && **p != ' ') (*p)++;
13559     sprintf(buf, "accepted %s\n", name);
13560     SendToProgram(buf, cps);
13561     return TRUE;
13562   }
13563   return FALSE;
13564 }
13565
13566 int
13567 StringFeature(p, name, loc, cps)
13568      char **p;
13569      char *name;
13570      char loc[];
13571      ChessProgramState *cps;
13572 {
13573   char buf[MSG_SIZ];
13574   int len = strlen(name);
13575   if (strncmp((*p), name, len) == 0
13576       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13577     (*p) += len + 2;
13578     sscanf(*p, "%[^\"]", loc);
13579     while (**p && **p != '\"') (*p)++;
13580     if (**p == '\"') (*p)++;
13581     sprintf(buf, "accepted %s\n", name);
13582     SendToProgram(buf, cps);
13583     return TRUE;
13584   }
13585   return FALSE;
13586 }
13587
13588 int
13589 ParseOption(Option *opt, ChessProgramState *cps)
13590 // [HGM] options: process the string that defines an engine option, and determine
13591 // name, type, default value, and allowed value range
13592 {
13593         char *p, *q, buf[MSG_SIZ];
13594         int n, min = (-1)<<31, max = 1<<31, def;
13595
13596         if(p = strstr(opt->name, " -spin ")) {
13597             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598             if(max < min) max = min; // enforce consistency
13599             if(def < min) def = min;
13600             if(def > max) def = max;
13601             opt->value = def;
13602             opt->min = min;
13603             opt->max = max;
13604             opt->type = Spin;
13605         } else if((p = strstr(opt->name, " -slider "))) {
13606             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13607             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13608             if(max < min) max = min; // enforce consistency
13609             if(def < min) def = min;
13610             if(def > max) def = max;
13611             opt->value = def;
13612             opt->min = min;
13613             opt->max = max;
13614             opt->type = Spin; // Slider;
13615         } else if((p = strstr(opt->name, " -string "))) {
13616             opt->textValue = p+9;
13617             opt->type = TextBox;
13618         } else if((p = strstr(opt->name, " -file "))) {
13619             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13620             opt->textValue = p+7;
13621             opt->type = TextBox; // FileName;
13622         } else if((p = strstr(opt->name, " -path "))) {
13623             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13624             opt->textValue = p+7;
13625             opt->type = TextBox; // PathName;
13626         } else if(p = strstr(opt->name, " -check ")) {
13627             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13628             opt->value = (def != 0);
13629             opt->type = CheckBox;
13630         } else if(p = strstr(opt->name, " -combo ")) {
13631             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13632             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13633             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13634             opt->value = n = 0;
13635             while(q = StrStr(q, " /// ")) {
13636                 n++; *q = 0;    // count choices, and null-terminate each of them
13637                 q += 5;
13638                 if(*q == '*') { // remember default, which is marked with * prefix
13639                     q++;
13640                     opt->value = n;
13641                 }
13642                 cps->comboList[cps->comboCnt++] = q;
13643             }
13644             cps->comboList[cps->comboCnt++] = NULL;
13645             opt->max = n + 1;
13646             opt->type = ComboBox;
13647         } else if(p = strstr(opt->name, " -button")) {
13648             opt->type = Button;
13649         } else if(p = strstr(opt->name, " -save")) {
13650             opt->type = SaveButton;
13651         } else return FALSE;
13652         *p = 0; // terminate option name
13653         // now look if the command-line options define a setting for this engine option.
13654         if(cps->optionSettings && cps->optionSettings[0])
13655             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13656         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13657                 sprintf(buf, "option %s", p);
13658                 if(p = strstr(buf, ",")) *p = 0;
13659                 strcat(buf, "\n");
13660                 SendToProgram(buf, cps);
13661         }
13662         return TRUE;
13663 }
13664
13665 void
13666 FeatureDone(cps, val)
13667      ChessProgramState* cps;
13668      int val;
13669 {
13670   DelayedEventCallback cb = GetDelayedEvent();
13671   if ((cb == InitBackEnd3 && cps == &first) ||
13672       (cb == TwoMachinesEventIfReady && cps == &second)) {
13673     CancelDelayedEvent();
13674     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13675   }
13676   cps->initDone = val;
13677 }
13678
13679 /* Parse feature command from engine */
13680 void
13681 ParseFeatures(args, cps)
13682      char* args;
13683      ChessProgramState *cps;
13684 {
13685   char *p = args;
13686   char *q;
13687   int val;
13688   char buf[MSG_SIZ];
13689
13690   for (;;) {
13691     while (*p == ' ') p++;
13692     if (*p == NULLCHAR) return;
13693
13694     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13695     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13696     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13697     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13698     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13699     if (BoolFeature(&p, "reuse", &val, cps)) {
13700       /* Engine can disable reuse, but can't enable it if user said no */
13701       if (!val) cps->reuse = FALSE;
13702       continue;
13703     }
13704     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13705     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13706       if (gameMode == TwoMachinesPlay) {
13707         DisplayTwoMachinesTitle();
13708       } else {
13709         DisplayTitle("");
13710       }
13711       continue;
13712     }
13713     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13714     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13715     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13716     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13717     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13718     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13719     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13720     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13721     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13722     if (IntFeature(&p, "done", &val, cps)) {
13723       FeatureDone(cps, val);
13724       continue;
13725     }
13726     /* Added by Tord: */
13727     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13728     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13729     /* End of additions by Tord */
13730
13731     /* [HGM] added features: */
13732     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13733     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13734     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13735     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13736     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13737     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13738     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13739         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13740             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13741             SendToProgram(buf, cps);
13742             continue;
13743         }
13744         if(cps->nrOptions >= MAX_OPTIONS) {
13745             cps->nrOptions--;
13746             sprintf(buf, "%s engine has too many options\n", cps->which);
13747             DisplayError(buf, 0);
13748         }
13749         continue;
13750     }
13751     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13752     /* End of additions by HGM */
13753
13754     /* unknown feature: complain and skip */
13755     q = p;
13756     while (*q && *q != '=') q++;
13757     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13758     SendToProgram(buf, cps);
13759     p = q;
13760     if (*p == '=') {
13761       p++;
13762       if (*p == '\"') {
13763         p++;
13764         while (*p && *p != '\"') p++;
13765         if (*p == '\"') p++;
13766       } else {
13767         while (*p && *p != ' ') p++;
13768       }
13769     }
13770   }
13771
13772 }
13773
13774 void
13775 PeriodicUpdatesEvent(newState)
13776      int newState;
13777 {
13778     if (newState == appData.periodicUpdates)
13779       return;
13780
13781     appData.periodicUpdates=newState;
13782
13783     /* Display type changes, so update it now */
13784 //    DisplayAnalysis();
13785
13786     /* Get the ball rolling again... */
13787     if (newState) {
13788         AnalysisPeriodicEvent(1);
13789         StartAnalysisClock();
13790     }
13791 }
13792
13793 void
13794 PonderNextMoveEvent(newState)
13795      int newState;
13796 {
13797     if (newState == appData.ponderNextMove) return;
13798     if (gameMode == EditPosition) EditPositionDone(TRUE);
13799     if (newState) {
13800         SendToProgram("hard\n", &first);
13801         if (gameMode == TwoMachinesPlay) {
13802             SendToProgram("hard\n", &second);
13803         }
13804     } else {
13805         SendToProgram("easy\n", &first);
13806         thinkOutput[0] = NULLCHAR;
13807         if (gameMode == TwoMachinesPlay) {
13808             SendToProgram("easy\n", &second);
13809         }
13810     }
13811     appData.ponderNextMove = newState;
13812 }
13813
13814 void
13815 NewSettingEvent(option, command, value)
13816      char *command;
13817      int option, value;
13818 {
13819     char buf[MSG_SIZ];
13820
13821     if (gameMode == EditPosition) EditPositionDone(TRUE);
13822     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13823     SendToProgram(buf, &first);
13824     if (gameMode == TwoMachinesPlay) {
13825         SendToProgram(buf, &second);
13826     }
13827 }
13828
13829 void
13830 ShowThinkingEvent()
13831 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13832 {
13833     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13834     int newState = appData.showThinking
13835         // [HGM] thinking: other features now need thinking output as well
13836         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13837
13838     if (oldState == newState) return;
13839     oldState = newState;
13840     if (gameMode == EditPosition) EditPositionDone(TRUE);
13841     if (oldState) {
13842         SendToProgram("post\n", &first);
13843         if (gameMode == TwoMachinesPlay) {
13844             SendToProgram("post\n", &second);
13845         }
13846     } else {
13847         SendToProgram("nopost\n", &first);
13848         thinkOutput[0] = NULLCHAR;
13849         if (gameMode == TwoMachinesPlay) {
13850             SendToProgram("nopost\n", &second);
13851         }
13852     }
13853 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13854 }
13855
13856 void
13857 AskQuestionEvent(title, question, replyPrefix, which)
13858      char *title; char *question; char *replyPrefix; char *which;
13859 {
13860   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13861   if (pr == NoProc) return;
13862   AskQuestion(title, question, replyPrefix, pr);
13863 }
13864
13865 void
13866 DisplayMove(moveNumber)
13867      int moveNumber;
13868 {
13869     char message[MSG_SIZ];
13870     char res[MSG_SIZ];
13871     char cpThinkOutput[MSG_SIZ];
13872
13873     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13874
13875     if (moveNumber == forwardMostMove - 1 ||
13876         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13877
13878         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13879
13880         if (strchr(cpThinkOutput, '\n')) {
13881             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13882         }
13883     } else {
13884         *cpThinkOutput = NULLCHAR;
13885     }
13886
13887     /* [AS] Hide thinking from human user */
13888     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13889         *cpThinkOutput = NULLCHAR;
13890         if( thinkOutput[0] != NULLCHAR ) {
13891             int i;
13892
13893             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13894                 cpThinkOutput[i] = '.';
13895             }
13896             cpThinkOutput[i] = NULLCHAR;
13897             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13898         }
13899     }
13900
13901     if (moveNumber == forwardMostMove - 1 &&
13902         gameInfo.resultDetails != NULL) {
13903         if (gameInfo.resultDetails[0] == NULLCHAR) {
13904             sprintf(res, " %s", PGNResult(gameInfo.result));
13905         } else {
13906             sprintf(res, " {%s} %s",
13907                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13908         }
13909     } else {
13910         res[0] = NULLCHAR;
13911     }
13912
13913     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13914         DisplayMessage(res, cpThinkOutput);
13915     } else {
13916         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13917                 WhiteOnMove(moveNumber) ? " " : ".. ",
13918                 parseList[moveNumber], res);
13919         DisplayMessage(message, cpThinkOutput);
13920     }
13921 }
13922
13923 void
13924 DisplayComment(moveNumber, text)
13925      int moveNumber;
13926      char *text;
13927 {
13928     char title[MSG_SIZ];
13929     char buf[8000]; // comment can be long!
13930     int score, depth;
13931     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13932       strcpy(title, "Comment");
13933     } else {
13934       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13935               WhiteOnMove(moveNumber) ? " " : ".. ",
13936               parseList[moveNumber]);
13937     }
13938     // [HGM] PV info: display PV info together with (or as) comment
13939     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13940       if(text == NULL) text = "";                                           
13941       score = pvInfoList[moveNumber].score;
13942       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13943               depth, (pvInfoList[moveNumber].time+50)/100, text);
13944       text = buf;
13945     }
13946     if (text != NULL && (appData.autoDisplayComment || commentUp))
13947       CommentPopUp(title, text);
13948 }
13949
13950 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13951  * might be busy thinking or pondering.  It can be omitted if your
13952  * gnuchess is configured to stop thinking immediately on any user
13953  * input.  However, that gnuchess feature depends on the FIONREAD
13954  * ioctl, which does not work properly on some flavors of Unix.
13955  */
13956 void
13957 Attention(cps)
13958      ChessProgramState *cps;
13959 {
13960 #if ATTENTION
13961     if (!cps->useSigint) return;
13962     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13963     switch (gameMode) {
13964       case MachinePlaysWhite:
13965       case MachinePlaysBlack:
13966       case TwoMachinesPlay:
13967       case IcsPlayingWhite:
13968       case IcsPlayingBlack:
13969       case AnalyzeMode:
13970       case AnalyzeFile:
13971         /* Skip if we know it isn't thinking */
13972         if (!cps->maybeThinking) return;
13973         if (appData.debugMode)
13974           fprintf(debugFP, "Interrupting %s\n", cps->which);
13975         InterruptChildProcess(cps->pr);
13976         cps->maybeThinking = FALSE;
13977         break;
13978       default:
13979         break;
13980     }
13981 #endif /*ATTENTION*/
13982 }
13983
13984 int
13985 CheckFlags()
13986 {
13987     if (whiteTimeRemaining <= 0) {
13988         if (!whiteFlag) {
13989             whiteFlag = TRUE;
13990             if (appData.icsActive) {
13991                 if (appData.autoCallFlag &&
13992                     gameMode == IcsPlayingBlack && !blackFlag) {
13993                   SendToICS(ics_prefix);
13994                   SendToICS("flag\n");
13995                 }
13996             } else {
13997                 if (blackFlag) {
13998                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13999                 } else {
14000                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14001                     if (appData.autoCallFlag) {
14002                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14003                         return TRUE;
14004                     }
14005                 }
14006             }
14007         }
14008     }
14009     if (blackTimeRemaining <= 0) {
14010         if (!blackFlag) {
14011             blackFlag = TRUE;
14012             if (appData.icsActive) {
14013                 if (appData.autoCallFlag &&
14014                     gameMode == IcsPlayingWhite && !whiteFlag) {
14015                   SendToICS(ics_prefix);
14016                   SendToICS("flag\n");
14017                 }
14018             } else {
14019                 if (whiteFlag) {
14020                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14021                 } else {
14022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14023                     if (appData.autoCallFlag) {
14024                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14025                         return TRUE;
14026                     }
14027                 }
14028             }
14029         }
14030     }
14031     return FALSE;
14032 }
14033
14034 void
14035 CheckTimeControl()
14036 {
14037     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14038         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14039
14040     /*
14041      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14042      */
14043     if ( !WhiteOnMove(forwardMostMove) )
14044         /* White made time control */
14045         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14046         /* [HGM] time odds: correct new time quota for time odds! */
14047                                             / WhitePlayer()->timeOdds;
14048       else
14049         /* Black made time control */
14050         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14051                                             / WhitePlayer()->other->timeOdds;
14052 }
14053
14054 void
14055 DisplayBothClocks()
14056 {
14057     int wom = gameMode == EditPosition ?
14058       !blackPlaysFirst : WhiteOnMove(currentMove);
14059     DisplayWhiteClock(whiteTimeRemaining, wom);
14060     DisplayBlackClock(blackTimeRemaining, !wom);
14061 }
14062
14063
14064 /* Timekeeping seems to be a portability nightmare.  I think everyone
14065    has ftime(), but I'm really not sure, so I'm including some ifdefs
14066    to use other calls if you don't.  Clocks will be less accurate if
14067    you have neither ftime nor gettimeofday.
14068 */
14069
14070 /* VS 2008 requires the #include outside of the function */
14071 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14072 #include <sys/timeb.h>
14073 #endif
14074
14075 /* Get the current time as a TimeMark */
14076 void
14077 GetTimeMark(tm)
14078      TimeMark *tm;
14079 {
14080 #if HAVE_GETTIMEOFDAY
14081
14082     struct timeval timeVal;
14083     struct timezone timeZone;
14084
14085     gettimeofday(&timeVal, &timeZone);
14086     tm->sec = (long) timeVal.tv_sec;
14087     tm->ms = (int) (timeVal.tv_usec / 1000L);
14088
14089 #else /*!HAVE_GETTIMEOFDAY*/
14090 #if HAVE_FTIME
14091
14092 // include <sys/timeb.h> / moved to just above start of function
14093     struct timeb timeB;
14094
14095     ftime(&timeB);
14096     tm->sec = (long) timeB.time;
14097     tm->ms = (int) timeB.millitm;
14098
14099 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14100     tm->sec = (long) time(NULL);
14101     tm->ms = 0;
14102 #endif
14103 #endif
14104 }
14105
14106 /* Return the difference in milliseconds between two
14107    time marks.  We assume the difference will fit in a long!
14108 */
14109 long
14110 SubtractTimeMarks(tm2, tm1)
14111      TimeMark *tm2, *tm1;
14112 {
14113     return 1000L*(tm2->sec - tm1->sec) +
14114            (long) (tm2->ms - tm1->ms);
14115 }
14116
14117
14118 /*
14119  * Code to manage the game clocks.
14120  *
14121  * In tournament play, black starts the clock and then white makes a move.
14122  * We give the human user a slight advantage if he is playing white---the
14123  * clocks don't run until he makes his first move, so it takes zero time.
14124  * Also, we don't account for network lag, so we could get out of sync
14125  * with GNU Chess's clock -- but then, referees are always right.
14126  */
14127
14128 static TimeMark tickStartTM;
14129 static long intendedTickLength;
14130
14131 long
14132 NextTickLength(timeRemaining)
14133      long timeRemaining;
14134 {
14135     long nominalTickLength, nextTickLength;
14136
14137     if (timeRemaining > 0L && timeRemaining <= 10000L)
14138       nominalTickLength = 100L;
14139     else
14140       nominalTickLength = 1000L;
14141     nextTickLength = timeRemaining % nominalTickLength;
14142     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14143
14144     return nextTickLength;
14145 }
14146
14147 /* Adjust clock one minute up or down */
14148 void
14149 AdjustClock(Boolean which, int dir)
14150 {
14151     if(which) blackTimeRemaining += 60000*dir;
14152     else      whiteTimeRemaining += 60000*dir;
14153     DisplayBothClocks();
14154 }
14155
14156 /* Stop clocks and reset to a fresh time control */
14157 void
14158 ResetClocks()
14159 {
14160     (void) StopClockTimer();
14161     if (appData.icsActive) {
14162         whiteTimeRemaining = blackTimeRemaining = 0;
14163     } else if (searchTime) {
14164         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14165         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14166     } else { /* [HGM] correct new time quote for time odds */
14167         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14168         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14169     }
14170     if (whiteFlag || blackFlag) {
14171         DisplayTitle("");
14172         whiteFlag = blackFlag = FALSE;
14173     }
14174     DisplayBothClocks();
14175 }
14176
14177 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14178
14179 /* Decrement running clock by amount of time that has passed */
14180 void
14181 DecrementClocks()
14182 {
14183     long timeRemaining;
14184     long lastTickLength, fudge;
14185     TimeMark now;
14186
14187     if (!appData.clockMode) return;
14188     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14189
14190     GetTimeMark(&now);
14191
14192     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14193
14194     /* Fudge if we woke up a little too soon */
14195     fudge = intendedTickLength - lastTickLength;
14196     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14197
14198     if (WhiteOnMove(forwardMostMove)) {
14199         if(whiteNPS >= 0) lastTickLength = 0;
14200         timeRemaining = whiteTimeRemaining -= lastTickLength;
14201         DisplayWhiteClock(whiteTimeRemaining - fudge,
14202                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14203     } else {
14204         if(blackNPS >= 0) lastTickLength = 0;
14205         timeRemaining = blackTimeRemaining -= lastTickLength;
14206         DisplayBlackClock(blackTimeRemaining - fudge,
14207                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14208     }
14209
14210     if (CheckFlags()) return;
14211
14212     tickStartTM = now;
14213     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14214     StartClockTimer(intendedTickLength);
14215
14216     /* if the time remaining has fallen below the alarm threshold, sound the
14217      * alarm. if the alarm has sounded and (due to a takeback or time control
14218      * with increment) the time remaining has increased to a level above the
14219      * threshold, reset the alarm so it can sound again.
14220      */
14221
14222     if (appData.icsActive && appData.icsAlarm) {
14223
14224         /* make sure we are dealing with the user's clock */
14225         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14226                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14227            )) return;
14228
14229         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14230             alarmSounded = FALSE;
14231         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14232             PlayAlarmSound();
14233             alarmSounded = TRUE;
14234         }
14235     }
14236 }
14237
14238
14239 /* A player has just moved, so stop the previously running
14240    clock and (if in clock mode) start the other one.
14241    We redisplay both clocks in case we're in ICS mode, because
14242    ICS gives us an update to both clocks after every move.
14243    Note that this routine is called *after* forwardMostMove
14244    is updated, so the last fractional tick must be subtracted
14245    from the color that is *not* on move now.
14246 */
14247 void
14248 SwitchClocks()
14249 {
14250     long lastTickLength;
14251     TimeMark now;
14252     int flagged = FALSE;
14253
14254     GetTimeMark(&now);
14255
14256     if (StopClockTimer() && appData.clockMode) {
14257         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14258         if (WhiteOnMove(forwardMostMove)) {
14259             if(blackNPS >= 0) lastTickLength = 0;
14260             blackTimeRemaining -= lastTickLength;
14261            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14262 //         if(pvInfoList[forwardMostMove-1].time == -1)
14263                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14264                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14265         } else {
14266            if(whiteNPS >= 0) lastTickLength = 0;
14267            whiteTimeRemaining -= lastTickLength;
14268            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14269 //         if(pvInfoList[forwardMostMove-1].time == -1)
14270                  pvInfoList[forwardMostMove-1].time =
14271                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14272         }
14273         flagged = CheckFlags();
14274     }
14275     CheckTimeControl();
14276
14277     if (flagged || !appData.clockMode) return;
14278
14279     switch (gameMode) {
14280       case MachinePlaysBlack:
14281       case MachinePlaysWhite:
14282       case BeginningOfGame:
14283         if (pausing) return;
14284         break;
14285
14286       case EditGame:
14287       case PlayFromGameFile:
14288       case IcsExamining:
14289         return;
14290
14291       default:
14292         break;
14293     }
14294
14295     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14296         if(WhiteOnMove(forwardMostMove))
14297              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14298         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14299     }
14300
14301     tickStartTM = now;
14302     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14303       whiteTimeRemaining : blackTimeRemaining);
14304     StartClockTimer(intendedTickLength);
14305 }
14306
14307
14308 /* Stop both clocks */
14309 void
14310 StopClocks()
14311 {
14312     long lastTickLength;
14313     TimeMark now;
14314
14315     if (!StopClockTimer()) return;
14316     if (!appData.clockMode) return;
14317
14318     GetTimeMark(&now);
14319
14320     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14321     if (WhiteOnMove(forwardMostMove)) {
14322         if(whiteNPS >= 0) lastTickLength = 0;
14323         whiteTimeRemaining -= lastTickLength;
14324         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14325     } else {
14326         if(blackNPS >= 0) lastTickLength = 0;
14327         blackTimeRemaining -= lastTickLength;
14328         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14329     }
14330     CheckFlags();
14331 }
14332
14333 /* Start clock of player on move.  Time may have been reset, so
14334    if clock is already running, stop and restart it. */
14335 void
14336 StartClocks()
14337 {
14338     (void) StopClockTimer(); /* in case it was running already */
14339     DisplayBothClocks();
14340     if (CheckFlags()) return;
14341
14342     if (!appData.clockMode) return;
14343     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14344
14345     GetTimeMark(&tickStartTM);
14346     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14347       whiteTimeRemaining : blackTimeRemaining);
14348
14349    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14350     whiteNPS = blackNPS = -1;
14351     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14352        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14353         whiteNPS = first.nps;
14354     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14355        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14356         blackNPS = first.nps;
14357     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14358         whiteNPS = second.nps;
14359     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14360         blackNPS = second.nps;
14361     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14362
14363     StartClockTimer(intendedTickLength);
14364 }
14365
14366 char *
14367 TimeString(ms)
14368      long ms;
14369 {
14370     long second, minute, hour, day;
14371     char *sign = "";
14372     static char buf[32];
14373
14374     if (ms > 0 && ms <= 9900) {
14375       /* convert milliseconds to tenths, rounding up */
14376       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14377
14378       sprintf(buf, " %03.1f ", tenths/10.0);
14379       return buf;
14380     }
14381
14382     /* convert milliseconds to seconds, rounding up */
14383     /* use floating point to avoid strangeness of integer division
14384        with negative dividends on many machines */
14385     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14386
14387     if (second < 0) {
14388         sign = "-";
14389         second = -second;
14390     }
14391
14392     day = second / (60 * 60 * 24);
14393     second = second % (60 * 60 * 24);
14394     hour = second / (60 * 60);
14395     second = second % (60 * 60);
14396     minute = second / 60;
14397     second = second % 60;
14398
14399     if (day > 0)
14400       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14401               sign, day, hour, minute, second);
14402     else if (hour > 0)
14403       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14404     else
14405       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14406
14407     return buf;
14408 }
14409
14410
14411 /*
14412  * This is necessary because some C libraries aren't ANSI C compliant yet.
14413  */
14414 char *
14415 StrStr(string, match)
14416      char *string, *match;
14417 {
14418     int i, length;
14419
14420     length = strlen(match);
14421
14422     for (i = strlen(string) - length; i >= 0; i--, string++)
14423       if (!strncmp(match, string, length))
14424         return string;
14425
14426     return NULL;
14427 }
14428
14429 char *
14430 StrCaseStr(string, match)
14431      char *string, *match;
14432 {
14433     int i, j, length;
14434
14435     length = strlen(match);
14436
14437     for (i = strlen(string) - length; i >= 0; i--, string++) {
14438         for (j = 0; j < length; j++) {
14439             if (ToLower(match[j]) != ToLower(string[j]))
14440               break;
14441         }
14442         if (j == length) return string;
14443     }
14444
14445     return NULL;
14446 }
14447
14448 #ifndef _amigados
14449 int
14450 StrCaseCmp(s1, s2)
14451      char *s1, *s2;
14452 {
14453     char c1, c2;
14454
14455     for (;;) {
14456         c1 = ToLower(*s1++);
14457         c2 = ToLower(*s2++);
14458         if (c1 > c2) return 1;
14459         if (c1 < c2) return -1;
14460         if (c1 == NULLCHAR) return 0;
14461     }
14462 }
14463
14464
14465 int
14466 ToLower(c)
14467      int c;
14468 {
14469     return isupper(c) ? tolower(c) : c;
14470 }
14471
14472
14473 int
14474 ToUpper(c)
14475      int c;
14476 {
14477     return islower(c) ? toupper(c) : c;
14478 }
14479 #endif /* !_amigados    */
14480
14481 char *
14482 StrSave(s)
14483      char *s;
14484 {
14485     char *ret;
14486
14487     if ((ret = (char *) malloc(strlen(s) + 1))) {
14488         strcpy(ret, s);
14489     }
14490     return ret;
14491 }
14492
14493 char *
14494 StrSavePtr(s, savePtr)
14495      char *s, **savePtr;
14496 {
14497     if (*savePtr) {
14498         free(*savePtr);
14499     }
14500     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14501         strcpy(*savePtr, s);
14502     }
14503     return(*savePtr);
14504 }
14505
14506 char *
14507 PGNDate()
14508 {
14509     time_t clock;
14510     struct tm *tm;
14511     char buf[MSG_SIZ];
14512
14513     clock = time((time_t *)NULL);
14514     tm = localtime(&clock);
14515     sprintf(buf, "%04d.%02d.%02d",
14516             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14517     return StrSave(buf);
14518 }
14519
14520
14521 char *
14522 PositionToFEN(move, overrideCastling)
14523      int move;
14524      char *overrideCastling;
14525 {
14526     int i, j, fromX, fromY, toX, toY;
14527     int whiteToPlay;
14528     char buf[128];
14529     char *p, *q;
14530     int emptycount;
14531     ChessSquare piece;
14532
14533     whiteToPlay = (gameMode == EditPosition) ?
14534       !blackPlaysFirst : (move % 2 == 0);
14535     p = buf;
14536
14537     /* Piece placement data */
14538     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14539         emptycount = 0;
14540         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14541             if (boards[move][i][j] == EmptySquare) {
14542                 emptycount++;
14543             } else { ChessSquare piece = boards[move][i][j];
14544                 if (emptycount > 0) {
14545                     if(emptycount<10) /* [HGM] can be >= 10 */
14546                         *p++ = '0' + emptycount;
14547                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14548                     emptycount = 0;
14549                 }
14550                 if(PieceToChar(piece) == '+') {
14551                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14552                     *p++ = '+';
14553                     piece = (ChessSquare)(DEMOTED piece);
14554                 }
14555                 *p++ = PieceToChar(piece);
14556                 if(p[-1] == '~') {
14557                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14558                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14559                     *p++ = '~';
14560                 }
14561             }
14562         }
14563         if (emptycount > 0) {
14564             if(emptycount<10) /* [HGM] can be >= 10 */
14565                 *p++ = '0' + emptycount;
14566             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14567             emptycount = 0;
14568         }
14569         *p++ = '/';
14570     }
14571     *(p - 1) = ' ';
14572
14573     /* [HGM] print Crazyhouse or Shogi holdings */
14574     if( gameInfo.holdingsWidth ) {
14575         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14576         q = p;
14577         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14578             piece = boards[move][i][BOARD_WIDTH-1];
14579             if( piece != EmptySquare )
14580               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14581                   *p++ = PieceToChar(piece);
14582         }
14583         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14584             piece = boards[move][BOARD_HEIGHT-i-1][0];
14585             if( piece != EmptySquare )
14586               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14587                   *p++ = PieceToChar(piece);
14588         }
14589
14590         if( q == p ) *p++ = '-';
14591         *p++ = ']';
14592         *p++ = ' ';
14593     }
14594
14595     /* Active color */
14596     *p++ = whiteToPlay ? 'w' : 'b';
14597     *p++ = ' ';
14598
14599   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14600     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14601   } else {
14602   if(nrCastlingRights) {
14603      q = p;
14604      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14605        /* [HGM] write directly from rights */
14606            if(boards[move][CASTLING][2] != NoRights &&
14607               boards[move][CASTLING][0] != NoRights   )
14608                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14609            if(boards[move][CASTLING][2] != NoRights &&
14610               boards[move][CASTLING][1] != NoRights   )
14611                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14612            if(boards[move][CASTLING][5] != NoRights &&
14613               boards[move][CASTLING][3] != NoRights   )
14614                 *p++ = boards[move][CASTLING][3] + AAA;
14615            if(boards[move][CASTLING][5] != NoRights &&
14616               boards[move][CASTLING][4] != NoRights   )
14617                 *p++ = boards[move][CASTLING][4] + AAA;
14618      } else {
14619
14620         /* [HGM] write true castling rights */
14621         if( nrCastlingRights == 6 ) {
14622             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14623                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14624             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14625                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14626             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14627                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14628             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14629                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14630         }
14631      }
14632      if (q == p) *p++ = '-'; /* No castling rights */
14633      *p++ = ' ';
14634   }
14635
14636   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14637      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14638     /* En passant target square */
14639     if (move > backwardMostMove) {
14640         fromX = moveList[move - 1][0] - AAA;
14641         fromY = moveList[move - 1][1] - ONE;
14642         toX = moveList[move - 1][2] - AAA;
14643         toY = moveList[move - 1][3] - ONE;
14644         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14645             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14646             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14647             fromX == toX) {
14648             /* 2-square pawn move just happened */
14649             *p++ = toX + AAA;
14650             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14651         } else {
14652             *p++ = '-';
14653         }
14654     } else if(move == backwardMostMove) {
14655         // [HGM] perhaps we should always do it like this, and forget the above?
14656         if((signed char)boards[move][EP_STATUS] >= 0) {
14657             *p++ = boards[move][EP_STATUS] + AAA;
14658             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14659         } else {
14660             *p++ = '-';
14661         }
14662     } else {
14663         *p++ = '-';
14664     }
14665     *p++ = ' ';
14666   }
14667   }
14668
14669     /* [HGM] find reversible plies */
14670     {   int i = 0, j=move;
14671
14672         if (appData.debugMode) { int k;
14673             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14674             for(k=backwardMostMove; k<=forwardMostMove; k++)
14675                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14676
14677         }
14678
14679         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14680         if( j == backwardMostMove ) i += initialRulePlies;
14681         sprintf(p, "%d ", i);
14682         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14683     }
14684     /* Fullmove number */
14685     sprintf(p, "%d", (move / 2) + 1);
14686
14687     return StrSave(buf);
14688 }
14689
14690 Boolean
14691 ParseFEN(board, blackPlaysFirst, fen)
14692     Board board;
14693      int *blackPlaysFirst;
14694      char *fen;
14695 {
14696     int i, j;
14697     char *p;
14698     int emptycount;
14699     ChessSquare piece;
14700
14701     p = fen;
14702
14703     /* [HGM] by default clear Crazyhouse holdings, if present */
14704     if(gameInfo.holdingsWidth) {
14705        for(i=0; i<BOARD_HEIGHT; i++) {
14706            board[i][0]             = EmptySquare; /* black holdings */
14707            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14708            board[i][1]             = (ChessSquare) 0; /* black counts */
14709            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14710        }
14711     }
14712
14713     /* Piece placement data */
14714     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14715         j = 0;
14716         for (;;) {
14717             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14718                 if (*p == '/') p++;
14719                 emptycount = gameInfo.boardWidth - j;
14720                 while (emptycount--)
14721                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14722                 break;
14723 #if(BOARD_FILES >= 10)
14724             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14725                 p++; emptycount=10;
14726                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14727                 while (emptycount--)
14728                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14729 #endif
14730             } else if (isdigit(*p)) {
14731                 emptycount = *p++ - '0';
14732                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14733                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14734                 while (emptycount--)
14735                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14736             } else if (*p == '+' || isalpha(*p)) {
14737                 if (j >= gameInfo.boardWidth) return FALSE;
14738                 if(*p=='+') {
14739                     piece = CharToPiece(*++p);
14740                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14741                     piece = (ChessSquare) (PROMOTED piece ); p++;
14742                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14743                 } else piece = CharToPiece(*p++);
14744
14745                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14746                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14747                     piece = (ChessSquare) (PROMOTED piece);
14748                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14749                     p++;
14750                 }
14751                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14752             } else {
14753                 return FALSE;
14754             }
14755         }
14756     }
14757     while (*p == '/' || *p == ' ') p++;
14758
14759     /* [HGM] look for Crazyhouse holdings here */
14760     while(*p==' ') p++;
14761     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14762         if(*p == '[') p++;
14763         if(*p == '-' ) *p++; /* empty holdings */ else {
14764             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14765             /* if we would allow FEN reading to set board size, we would   */
14766             /* have to add holdings and shift the board read so far here   */
14767             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14768                 *p++;
14769                 if((int) piece >= (int) BlackPawn ) {
14770                     i = (int)piece - (int)BlackPawn;
14771                     i = PieceToNumber((ChessSquare)i);
14772                     if( i >= gameInfo.holdingsSize ) return FALSE;
14773                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14774                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14775                 } else {
14776                     i = (int)piece - (int)WhitePawn;
14777                     i = PieceToNumber((ChessSquare)i);
14778                     if( i >= gameInfo.holdingsSize ) return FALSE;
14779                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14780                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14781                 }
14782             }
14783         }
14784         if(*p == ']') *p++;
14785     }
14786
14787     while(*p == ' ') p++;
14788
14789     /* Active color */
14790     switch (*p++) {
14791       case 'w':
14792         *blackPlaysFirst = FALSE;
14793         break;
14794       case 'b':
14795         *blackPlaysFirst = TRUE;
14796         break;
14797       default:
14798         return FALSE;
14799     }
14800
14801     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14802     /* return the extra info in global variiables             */
14803
14804     /* set defaults in case FEN is incomplete */
14805     board[EP_STATUS] = EP_UNKNOWN;
14806     for(i=0; i<nrCastlingRights; i++ ) {
14807         board[CASTLING][i] =
14808             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14809     }   /* assume possible unless obviously impossible */
14810     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14811     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14812     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14813                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14814     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14815     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14816     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14817                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14818     FENrulePlies = 0;
14819
14820     while(*p==' ') p++;
14821     if(nrCastlingRights) {
14822       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14823           /* castling indicator present, so default becomes no castlings */
14824           for(i=0; i<nrCastlingRights; i++ ) {
14825                  board[CASTLING][i] = NoRights;
14826           }
14827       }
14828       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14829              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14830              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14831              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14832         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14833
14834         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14835             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14836             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14837         }
14838         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14839             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14840         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14841                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14842         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14843                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14844         switch(c) {
14845           case'K':
14846               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14847               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14848               board[CASTLING][2] = whiteKingFile;
14849               break;
14850           case'Q':
14851               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14852               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14853               board[CASTLING][2] = whiteKingFile;
14854               break;
14855           case'k':
14856               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14857               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14858               board[CASTLING][5] = blackKingFile;
14859               break;
14860           case'q':
14861               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14862               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14863               board[CASTLING][5] = blackKingFile;
14864           case '-':
14865               break;
14866           default: /* FRC castlings */
14867               if(c >= 'a') { /* black rights */
14868                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14869                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14870                   if(i == BOARD_RGHT) break;
14871                   board[CASTLING][5] = i;
14872                   c -= AAA;
14873                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14874                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14875                   if(c > i)
14876                       board[CASTLING][3] = c;
14877                   else
14878                       board[CASTLING][4] = c;
14879               } else { /* white rights */
14880                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14881                     if(board[0][i] == WhiteKing) break;
14882                   if(i == BOARD_RGHT) break;
14883                   board[CASTLING][2] = i;
14884                   c -= AAA - 'a' + 'A';
14885                   if(board[0][c] >= WhiteKing) break;
14886                   if(c > i)
14887                       board[CASTLING][0] = c;
14888                   else
14889                       board[CASTLING][1] = c;
14890               }
14891         }
14892       }
14893       for(i=0; i<nrCastlingRights; i++)
14894         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14895     if (appData.debugMode) {
14896         fprintf(debugFP, "FEN castling rights:");
14897         for(i=0; i<nrCastlingRights; i++)
14898         fprintf(debugFP, " %d", board[CASTLING][i]);
14899         fprintf(debugFP, "\n");
14900     }
14901
14902       while(*p==' ') p++;
14903     }
14904
14905     /* read e.p. field in games that know e.p. capture */
14906     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14907        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14908       if(*p=='-') {
14909         p++; board[EP_STATUS] = EP_NONE;
14910       } else {
14911          char c = *p++ - AAA;
14912
14913          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14914          if(*p >= '0' && *p <='9') *p++;
14915          board[EP_STATUS] = c;
14916       }
14917     }
14918
14919
14920     if(sscanf(p, "%d", &i) == 1) {
14921         FENrulePlies = i; /* 50-move ply counter */
14922         /* (The move number is still ignored)    */
14923     }
14924
14925     return TRUE;
14926 }
14927
14928 void
14929 EditPositionPasteFEN(char *fen)
14930 {
14931   if (fen != NULL) {
14932     Board initial_position;
14933
14934     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14935       DisplayError(_("Bad FEN position in clipboard"), 0);
14936       return ;
14937     } else {
14938       int savedBlackPlaysFirst = blackPlaysFirst;
14939       EditPositionEvent();
14940       blackPlaysFirst = savedBlackPlaysFirst;
14941       CopyBoard(boards[0], initial_position);
14942       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14943       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14944       DisplayBothClocks();
14945       DrawPosition(FALSE, boards[currentMove]);
14946     }
14947   }
14948 }
14949
14950 static char cseq[12] = "\\   ";
14951
14952 Boolean set_cont_sequence(char *new_seq)
14953 {
14954     int len;
14955     Boolean ret;
14956
14957     // handle bad attempts to set the sequence
14958         if (!new_seq)
14959                 return 0; // acceptable error - no debug
14960
14961     len = strlen(new_seq);
14962     ret = (len > 0) && (len < sizeof(cseq));
14963     if (ret)
14964         strcpy(cseq, new_seq);
14965     else if (appData.debugMode)
14966         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14967     return ret;
14968 }
14969
14970 /*
14971     reformat a source message so words don't cross the width boundary.  internal
14972     newlines are not removed.  returns the wrapped size (no null character unless
14973     included in source message).  If dest is NULL, only calculate the size required
14974     for the dest buffer.  lp argument indicats line position upon entry, and it's
14975     passed back upon exit.
14976 */
14977 int wrap(char *dest, char *src, int count, int width, int *lp)
14978 {
14979     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14980
14981     cseq_len = strlen(cseq);
14982     old_line = line = *lp;
14983     ansi = len = clen = 0;
14984
14985     for (i=0; i < count; i++)
14986     {
14987         if (src[i] == '\033')
14988             ansi = 1;
14989
14990         // if we hit the width, back up
14991         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14992         {
14993             // store i & len in case the word is too long
14994             old_i = i, old_len = len;
14995
14996             // find the end of the last word
14997             while (i && src[i] != ' ' && src[i] != '\n')
14998             {
14999                 i--;
15000                 len--;
15001             }
15002
15003             // word too long?  restore i & len before splitting it
15004             if ((old_i-i+clen) >= width)
15005             {
15006                 i = old_i;
15007                 len = old_len;
15008             }
15009
15010             // extra space?
15011             if (i && src[i-1] == ' ')
15012                 len--;
15013
15014             if (src[i] != ' ' && src[i] != '\n')
15015             {
15016                 i--;
15017                 if (len)
15018                     len--;
15019             }
15020
15021             // now append the newline and continuation sequence
15022             if (dest)
15023                 dest[len] = '\n';
15024             len++;
15025             if (dest)
15026                 strncpy(dest+len, cseq, cseq_len);
15027             len += cseq_len;
15028             line = cseq_len;
15029             clen = cseq_len;
15030             continue;
15031         }
15032
15033         if (dest)
15034             dest[len] = src[i];
15035         len++;
15036         if (!ansi)
15037             line++;
15038         if (src[i] == '\n')
15039             line = 0;
15040         if (src[i] == 'm')
15041             ansi = 0;
15042     }
15043     if (dest && appData.debugMode)
15044     {
15045         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15046             count, width, line, len, *lp);
15047         show_bytes(debugFP, src, count);
15048         fprintf(debugFP, "\ndest: ");
15049         show_bytes(debugFP, dest, len);
15050         fprintf(debugFP, "\n");
15051     }
15052     *lp = dest ? line : old_line;
15053
15054     return len;
15055 }
15056
15057 // [HGM] vari: routines for shelving variations
15058
15059 void 
15060 PushTail(int firstMove, int lastMove)
15061 {
15062         int i, j, nrMoves = lastMove - firstMove;
15063
15064         if(appData.icsActive) { // only in local mode
15065                 forwardMostMove = currentMove; // mimic old ICS behavior
15066                 return;
15067         }
15068         if(storedGames >= MAX_VARIATIONS-1) return;
15069
15070         // push current tail of game on stack
15071         savedResult[storedGames] = gameInfo.result;
15072         savedDetails[storedGames] = gameInfo.resultDetails;
15073         gameInfo.resultDetails = NULL;
15074         savedFirst[storedGames] = firstMove;
15075         savedLast [storedGames] = lastMove;
15076         savedFramePtr[storedGames] = framePtr;
15077         framePtr -= nrMoves; // reserve space for the boards
15078         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15079             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15080             for(j=0; j<MOVE_LEN; j++)
15081                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15082             for(j=0; j<2*MOVE_LEN; j++)
15083                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15084             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15085             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15086             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15087             pvInfoList[firstMove+i-1].depth = 0;
15088             commentList[framePtr+i] = commentList[firstMove+i];
15089             commentList[firstMove+i] = NULL;
15090         }
15091
15092         storedGames++;
15093         forwardMostMove = currentMove; // truncte game so we can start variation
15094         if(storedGames == 1) GreyRevert(FALSE);
15095 }
15096
15097 Boolean
15098 PopTail(Boolean annotate)
15099 {
15100         int i, j, nrMoves;
15101         char buf[8000], moveBuf[20];
15102
15103         if(appData.icsActive) return FALSE; // only in local mode
15104         if(!storedGames) return FALSE; // sanity
15105
15106         storedGames--;
15107         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15108         nrMoves = savedLast[storedGames] - currentMove;
15109         if(annotate) {
15110                 int cnt = 10;
15111                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15112                 else strcpy(buf, "(");
15113                 for(i=currentMove; i<forwardMostMove; i++) {
15114                         if(WhiteOnMove(i))
15115                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15116                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15117                         strcat(buf, moveBuf);
15118                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15119                 }
15120                 strcat(buf, ")");
15121         }
15122         for(i=1; i<nrMoves; i++) { // copy last variation back
15123             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15124             for(j=0; j<MOVE_LEN; j++)
15125                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15126             for(j=0; j<2*MOVE_LEN; j++)
15127                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15128             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15129             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15130             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15131             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15132             commentList[currentMove+i] = commentList[framePtr+i];
15133             commentList[framePtr+i] = NULL;
15134         }
15135         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15136         framePtr = savedFramePtr[storedGames];
15137         gameInfo.result = savedResult[storedGames];
15138         if(gameInfo.resultDetails != NULL) {
15139             free(gameInfo.resultDetails);
15140       }
15141         gameInfo.resultDetails = savedDetails[storedGames];
15142         forwardMostMove = currentMove + nrMoves;
15143         if(storedGames == 0) GreyRevert(TRUE);
15144         return TRUE;
15145 }
15146
15147 void 
15148 CleanupTail()
15149 {       // remove all shelved variations
15150         int i;
15151         for(i=0; i<storedGames; i++) {
15152             if(savedDetails[i])
15153                 free(savedDetails[i]);
15154             savedDetails[i] = NULL;
15155         }
15156         for(i=framePtr; i<MAX_MOVES; i++) {
15157                 if(commentList[i]) free(commentList[i]);
15158                 commentList[i] = NULL;
15159         }
15160         framePtr = MAX_MOVES-1;
15161         storedGames = 0;
15162 }