122c8fd96bd27ed90bc2d64db9ef213d3f718b31
[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, char *title, int x, int y)
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(int x, int y)
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, _("Select piece:"), x, y);
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     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6146     if(gameInfo.variant == VariantShogi) {
6147         // non-Pawn promotes; must be shogi
6148         h = 1; w = 1; promoBoard[0][BOARD_LEFT+0] = piece;
6149         if(PieceToChar(PROMOTED piece) != '.') {
6150             // promoted version is enabled
6151             w = 2; promoBoard[0][BOARD_LEFT+1] = PROMOTED piece;
6152         }
6153     } else {
6154         // Pawn, promotes to any enabled other piece
6155         h = 1; w = nr = 0;
6156         for(i=1; i<EmptySquare/2; i++) {
6157             if(PieceToChar(piece+i) != '.' 
6158            && PieceToChar(piece + i) != '~' // suppress bughouse true pieces
6159                                                         ) {
6160                 list[w++] = piece+i; nr++;
6161             }
6162         }
6163         if(appData.testLegality && gameInfo.variant != VariantSuicide
6164                         && gameInfo.variant != VariantGiveaway) nr--,w--; // remove King
6165         h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6166         for(i=0; i < nr; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[i]; // layout
6167     }
6168     promotionChoice = 2; // wait for click on board
6169     PromoDialog(h, w, promoBoard, FALSE, _("Promote to:"), -1, -1);
6170 }
6171
6172 void
6173 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6174      Board board;
6175      int flags;
6176      ChessMove kind;
6177      int rf, ff, rt, ft;
6178      VOIDSTAR closure;
6179 {
6180     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6181     Markers *m = (Markers *) closure;
6182     if(rf == fromY && ff == fromX)
6183         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6184                          || kind == WhiteCapturesEnPassant
6185                          || kind == BlackCapturesEnPassant);
6186     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6187 }
6188
6189 void
6190 MarkTargetSquares(int clear)
6191 {
6192   int x, y;
6193   if(!appData.markers || !appData.highlightDragging || 
6194      !appData.testLegality || gameMode == EditPosition) return;
6195   if(clear) {
6196     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6197   } else {
6198     int capt = 0;
6199     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6200     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6201       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6202       if(capt)
6203       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6204     }
6205   }
6206   DrawPosition(TRUE, NULL);
6207 }
6208
6209 void LeftClick(ClickType clickType, int xPix, int yPix)
6210 {
6211     int x, y;
6212     Boolean saveAnimate;
6213     static int second = 0;
6214     char promoChoice = NULLCHAR;
6215
6216     if(appData.seekGraph && appData.icsActive && loggedOn &&
6217         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6218         SeekGraphClick(clickType, xPix, yPix, 0);
6219         return;
6220     }
6221
6222     if (clickType == Press) ErrorPopDown();
6223     MarkTargetSquares(1);
6224
6225     x = EventToSquare(xPix, BOARD_WIDTH);
6226     y = EventToSquare(yPix, BOARD_HEIGHT);
6227     if (!flipView && y >= 0) {
6228         y = BOARD_HEIGHT - 1 - y;
6229     }
6230     if (flipView && x >= 0) {
6231         x = BOARD_WIDTH - 1 - x;
6232     }
6233
6234     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6235         ChessSquare p = EmptySquare; Boolean inHoldings;
6236         if(promotionChoice == 3) {
6237             if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6238             else if(clickType == Release) promotionChoice = 0;
6239             fromX = fromY = -1;
6240             return;
6241         }
6242         if(clickType == Release) return; // ignore upclick of click-click destination
6243         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6244         inHoldings = gameInfo.holdingsWidth &&
6245                 (WhiteOnMove(currentMove) 
6246                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6247                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6248             // click in right holdings, for determining promotion piece
6249         if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6250             p = promoBoard[y][x];
6251             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6252             if(p != EmptySquare) {
6253                 char promoChar = PieceToChar(p);
6254                 if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '=';
6255                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar));
6256                 fromX = fromY = -1;
6257                 promotionChoice = 0;
6258                 return;
6259             }
6260         }
6261         promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6262         DrawPosition(FALSE, boards[currentMove]);
6263         return;
6264     }
6265
6266     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6267     if(clickType == Press
6268             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6269               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6270               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6271         return;
6272
6273     if (fromX == -1) {
6274       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6275         if (clickType == Press) {
6276             /* First square */
6277             if (OKToStartUserMove(x, y)) {
6278                 fromX = x;
6279                 fromY = y;
6280                 second = 0;
6281                 MarkTargetSquares(0);
6282                 DragPieceBegin(xPix, yPix);
6283                 if (appData.highlightDragging) {
6284                     SetHighlights(x, y, -1, -1);
6285                 }
6286             }
6287         }
6288         return;
6289       }
6290     }
6291
6292     /* fromX != -1 */
6293     if (clickType == Press && gameMode != EditPosition) {
6294         ChessSquare fromP;
6295         ChessSquare toP;
6296         int frc;
6297
6298         // ignore off-board to clicks
6299         if(y < 0 || x < 0) return;
6300
6301         /* Check if clicking again on the same color piece */
6302         fromP = boards[currentMove][fromY][fromX];
6303         toP = boards[currentMove][y][x];
6304         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6305         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6306              WhitePawn <= toP && toP <= WhiteKing &&
6307              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6308              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6309             (BlackPawn <= fromP && fromP <= BlackKing && 
6310              BlackPawn <= toP && toP <= BlackKing &&
6311              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6312              !(fromP == BlackKing && toP == BlackRook && frc))) {
6313             /* Clicked again on same color piece -- changed his mind */
6314             second = (x == fromX && y == fromY);
6315             if (appData.highlightDragging) {
6316                 SetHighlights(x, y, -1, -1);
6317             } else {
6318                 ClearHighlights();
6319             }
6320             if (OKToStartUserMove(x, y)) {
6321                 fromX = x;
6322                 fromY = y;
6323                 MarkTargetSquares(0);
6324                 DragPieceBegin(xPix, yPix);
6325             }
6326             return;
6327         }
6328         // ignore clicks on holdings
6329         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6330     }
6331
6332     if (clickType == Release && x == fromX && y == fromY) {
6333         DragPieceEnd(xPix, yPix);
6334         if (appData.animateDragging) {
6335             /* Undo animation damage if any */
6336             DrawPosition(FALSE, NULL);
6337         }
6338         if (second) {
6339             /* Second up/down in same square; just abort move */
6340             second = 0;
6341             fromX = fromY = -1;
6342             ClearHighlights();
6343             gotPremove = 0;
6344             ClearPremoveHighlights();
6345         } else {
6346             /* First upclick in same square; start click-click mode */
6347             SetHighlights(x, y, -1, -1);
6348         }
6349         return;
6350     }
6351
6352     /* we now have a different from- and (possibly off-board) to-square */
6353     /* Completed move */
6354     toX = x;
6355     toY = y;
6356     saveAnimate = appData.animate;
6357     if (clickType == Press) {
6358         /* Finish clickclick move */
6359         if (appData.animate || appData.highlightLastMove) {
6360             SetHighlights(fromX, fromY, toX, toY);
6361         } else {
6362             ClearHighlights();
6363         }
6364     } else {
6365         /* Finish drag move */
6366         if (appData.highlightLastMove) {
6367             SetHighlights(fromX, fromY, toX, toY);
6368         } else {
6369             ClearHighlights();
6370         }
6371         DragPieceEnd(xPix, yPix);
6372         /* Don't animate move and drag both */
6373         appData.animate = FALSE;
6374     }
6375
6376     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6377     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6378         ChessSquare piece = boards[currentMove][fromY][fromX];
6379         if(gameMode == EditPosition && piece != EmptySquare &&
6380            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6381             int n;
6382              
6383             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6384                 n = PieceToNumber(piece - (int)BlackPawn);
6385                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6386                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6387                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6388             } else
6389             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6390                 n = PieceToNumber(piece);
6391                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6392                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6393                 boards[currentMove][n][BOARD_WIDTH-2]++;
6394             }
6395             boards[currentMove][fromY][fromX] = EmptySquare;
6396         }
6397         ClearHighlights();
6398         fromX = fromY = -1;
6399         DrawPosition(TRUE, boards[currentMove]);
6400         return;
6401     }
6402
6403     // off-board moves should not be highlighted
6404     if(x < 0 || x < 0) ClearHighlights();
6405
6406     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6407         SetHighlights(fromX, fromY, toX, toY);
6408         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6409             // [HGM] super: promotion to captured piece selected from holdings
6410             ChessSquare p = boards[currentMove][fromY][fromX];
6411             promotionChoice = 1;
6412             CopyBoard(promoBoard, boards[currentMove]);
6413             // kludge follows to temporarily execute move on display, without promoting yet
6414             promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6415             promoBoard[toY][toX] = p;
6416             DrawPosition(FALSE, promoBoard);
6417             DisplayMessage("Click in holdings to choose piece", "");
6418             return;
6419         }
6420         CopyBoard(promoBoard, boards[currentMove]);
6421         PromoPopUp(boards[currentMove][fromY][fromX]);
6422     } else {
6423         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6424         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6425         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6426         fromX = fromY = -1;
6427     }
6428     appData.animate = saveAnimate;
6429     if (appData.animate || appData.animateDragging) {
6430         /* Undo animation damage if needed */
6431         DrawPosition(FALSE, NULL);
6432     }
6433 }
6434
6435 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6436 {   // front-end-free part taken out of PieceMenuPopup
6437     int whichMenu; int xSqr, ySqr;
6438
6439     if(seekGraphUp) { // [HGM] seekgraph
6440         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6441         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6442         return -2;
6443     }
6444
6445     xSqr = EventToSquare(x, BOARD_WIDTH);
6446     ySqr = EventToSquare(y, BOARD_HEIGHT);
6447     if (flipView)
6448       xSqr = BOARD_WIDTH - 1 - xSqr;
6449     else
6450       ySqr = BOARD_HEIGHT - 1 - ySqr;
6451     if(promotionChoice == 3 && action == Release
6452          && promoBoard[ySqr][xSqr] != EmptySquare && (xSqr != fromX || ySqr != fromY) // not needed if separate window
6453                                                 ) {
6454         EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6455         fromX = fromY = -1;
6456         promotionChoice = 0;
6457         return -1;
6458     }
6459     if (action == Release) UnLoadPV(); // [HGM] pv
6460     if (action != Press) return -2; // return code to be ignored
6461     switch (gameMode) {
6462       case IcsExamining:
6463         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6464       case EditPosition:
6465         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6466         if (xSqr < 0 || ySqr < 0) return -1;
6467         whichMenu = 0; // edit-position menu
6468         break;
6469       case IcsObserving:
6470         if(!appData.icsEngineAnalyze) return -1;
6471       case IcsPlayingWhite:
6472       case IcsPlayingBlack:
6473         if(!appData.zippyPlay) goto noZip;
6474       case AnalyzeMode:
6475       case AnalyzeFile:
6476       case MachinePlaysWhite:
6477       case MachinePlaysBlack:
6478       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6479         if (!appData.dropMenu) {
6480           LoadPV(x, y);
6481           return 2; // flag front-end to grab mouse events
6482         }
6483         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6484            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6485       case EditGame:
6486       noZip:
6487         if (xSqr < 0 || ySqr < 0) return -1;
6488         if (!appData.dropMenu || appData.testLegality &&
6489             gameInfo.variant != VariantBughouse &&
6490             gameInfo.variant != VariantCrazyhouse) return -1;
6491         whichMenu = 1; // drop menu
6492         break;
6493       default:
6494         return -1;
6495     }
6496
6497     if (((*xx = xSqr) < 0) ||
6498         ((*yy = ySqr) < 0)) {
6499         *xx = *yy = -1;
6500         return -1;
6501     }
6502
6503     fromX = *xx; fromY = *yy;
6504     if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu
6505
6506     return whichMenu;
6507 }
6508
6509 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6510 {
6511 //    char * hint = lastHint;
6512     FrontEndProgramStats stats;
6513
6514     stats.which = cps == &first ? 0 : 1;
6515     stats.depth = cpstats->depth;
6516     stats.nodes = cpstats->nodes;
6517     stats.score = cpstats->score;
6518     stats.time = cpstats->time;
6519     stats.pv = cpstats->movelist;
6520     stats.hint = lastHint;
6521     stats.an_move_index = 0;
6522     stats.an_move_count = 0;
6523
6524     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6525         stats.hint = cpstats->move_name;
6526         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6527         stats.an_move_count = cpstats->nr_moves;
6528     }
6529
6530     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6531
6532     SetProgramStats( &stats );
6533 }
6534
6535 int
6536 Adjudicate(ChessProgramState *cps)
6537 {       // [HGM] some adjudications useful with buggy engines
6538         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6539         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6540         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6541         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6542         int k, count = 0; static int bare = 1;
6543         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6544         Boolean canAdjudicate = !appData.icsActive;
6545
6546         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6547         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6548             if( appData.testLegality )
6549             {   /* [HGM] Some more adjudications for obstinate engines */
6550                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6551                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6552                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6553                 static int moveCount = 6;
6554                 ChessMove result;
6555                 char *reason = NULL;
6556
6557
6558                 /* Count what is on board. */
6559                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6560                 {   ChessSquare p = boards[forwardMostMove][i][j];
6561                     int m=i;
6562
6563                     switch((int) p)
6564                     {   /* count B,N,R and other of each side */
6565                         case WhiteKing:
6566                         case BlackKing:
6567                              NrK++; break; // [HGM] atomic: count Kings
6568                         case WhiteKnight:
6569                              NrWN++; break;
6570                         case WhiteBishop:
6571                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6572                              bishopsColor |= 1 << ((i^j)&1);
6573                              NrWB++; break;
6574                         case BlackKnight:
6575                              NrBN++; break;
6576                         case BlackBishop:
6577                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6578                              bishopsColor |= 1 << ((i^j)&1);
6579                              NrBB++; break;
6580                         case WhiteRook:
6581                              NrWR++; break;
6582                         case BlackRook:
6583                              NrBR++; break;
6584                         case WhiteQueen:
6585                              NrWQ++; break;
6586                         case BlackQueen:
6587                              NrBQ++; break;
6588                         case EmptySquare: 
6589                              break;
6590                         case BlackPawn:
6591                              m = 7-i;
6592                         case WhitePawn:
6593                              PawnAdvance += m; NrPawns++;
6594                     }
6595                     NrPieces += (p != EmptySquare);
6596                     NrW += ((int)p < (int)BlackPawn);
6597                     if(gameInfo.variant == VariantXiangqi && 
6598                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6599                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6600                         NrW -= ((int)p < (int)BlackPawn);
6601                     }
6602                 }
6603
6604                 /* Some material-based adjudications that have to be made before stalemate test */
6605                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6606                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6607                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6608                      if(canAdjudicate && appData.checkMates) {
6609                          if(engineOpponent)
6610                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6611                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6613                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6614                          return 1;
6615                      }
6616                 }
6617
6618                 /* Bare King in Shatranj (loses) or Losers (wins) */
6619                 if( NrW == 1 || NrPieces - NrW == 1) {
6620                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6621                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6622                      if(canAdjudicate && appData.checkMates) {
6623                          if(engineOpponent)
6624                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6625                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6627                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6628                          return 1;
6629                      }
6630                   } else
6631                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6632                   {    /* bare King */
6633                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6634                         if(canAdjudicate && appData.checkMates) {
6635                             /* but only adjudicate if adjudication enabled */
6636                             if(engineOpponent)
6637                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6638                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6640                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6641                             return 1;
6642                         }
6643                   }
6644                 } else bare = 1;
6645
6646
6647             // don't wait for engine to announce game end if we can judge ourselves
6648             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6649               case MT_CHECK:
6650                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6651                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6652                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6653                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6654                             checkCnt++;
6655                         if(checkCnt >= 2) {
6656                             reason = "Xboard adjudication: 3rd check";
6657                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6658                             break;
6659                         }
6660                     }
6661                 }
6662               case MT_NONE:
6663               default:
6664                 break;
6665               case MT_STALEMATE:
6666               case MT_STAINMATE:
6667                 reason = "Xboard adjudication: Stalemate";
6668                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6669                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6670                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6671                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6672                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6673                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6674                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6675                                                                         EP_CHECKMATE : EP_WINS);
6676                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6677                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6678                 }
6679                 break;
6680               case MT_CHECKMATE:
6681                 reason = "Xboard adjudication: Checkmate";
6682                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6683                 break;
6684             }
6685
6686                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6687                     case EP_STALEMATE:
6688                         result = GameIsDrawn; break;
6689                     case EP_CHECKMATE:
6690                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6691                     case EP_WINS:
6692                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6693                     default:
6694                         result = (ChessMove) 0;
6695                 }
6696                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6697                     if(engineOpponent)
6698                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6699                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6700                     GameEnds( result, reason, GE_XBOARD );
6701                     return 1;
6702                 }
6703
6704                 /* Next absolutely insufficient mating material. */
6705                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6706                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6707                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6708                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6709                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6710
6711                      /* always flag draws, for judging claims */
6712                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6713
6714                      if(canAdjudicate && appData.materialDraws) {
6715                          /* but only adjudicate them if adjudication enabled */
6716                          if(engineOpponent) {
6717                            SendToProgram("force\n", engineOpponent); // suppress reply
6718                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6719                          }
6720                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6721                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6722                          return 1;
6723                      }
6724                 }
6725
6726                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6727                 if(NrPieces == 4 && 
6728                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6729                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6730                    || NrWN==2 || NrBN==2     /* KNNK */
6731                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6732                   ) ) {
6733                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6734                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6735                           if(engineOpponent) {
6736                             SendToProgram("force\n", engineOpponent); // suppress reply
6737                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6738                           }
6739                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6740                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6741                           return 1;
6742                      }
6743                 } else moveCount = 6;
6744             }
6745         }
6746           
6747         if (appData.debugMode) { int i;
6748             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6749                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6750                     appData.drawRepeats);
6751             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6752               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6753             
6754         }
6755
6756         // Repetition draws and 50-move rule can be applied independently of legality testing
6757
6758                 /* Check for rep-draws */
6759                 count = 0;
6760                 for(k = forwardMostMove-2;
6761                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6762                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6763                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6764                     k-=2)
6765                 {   int rights=0;
6766                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6767                         /* compare castling rights */
6768                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6769                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6770                                 rights++; /* King lost rights, while rook still had them */
6771                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6772                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6773                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6774                                    rights++; /* but at least one rook lost them */
6775                         }
6776                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6777                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6778                                 rights++; 
6779                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6780                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6781                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6782                                    rights++;
6783                         }
6784                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6785                             && appData.drawRepeats > 1) {
6786                              /* adjudicate after user-specified nr of repeats */
6787                              if(engineOpponent) {
6788                                SendToProgram("force\n", engineOpponent); // suppress reply
6789                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6790                              }
6791                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6792                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6793                                 // [HGM] xiangqi: check for forbidden perpetuals
6794                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6795                                 for(m=forwardMostMove; m>k; m-=2) {
6796                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6797                                         ourPerpetual = 0; // the current mover did not always check
6798                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6799                                         hisPerpetual = 0; // the opponent did not always check
6800                                 }
6801                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6802                                                                         ourPerpetual, hisPerpetual);
6803                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6804                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6805                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6806                                     return 1;
6807                                 }
6808                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6809                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6810                                 // Now check for perpetual chases
6811                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6812                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6813                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6814                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6815                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6816                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6817                                         return 1;
6818                                     }
6819                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6820                                         break; // Abort repetition-checking loop.
6821                                 }
6822                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6823                              }
6824                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6825                              return 1;
6826                         }
6827                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6828                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6829                     }
6830                 }
6831
6832                 /* Now we test for 50-move draws. Determine ply count */
6833                 count = forwardMostMove;
6834                 /* look for last irreversble move */
6835                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6836                     count--;
6837                 /* if we hit starting position, add initial plies */
6838                 if( count == backwardMostMove )
6839                     count -= initialRulePlies;
6840                 count = forwardMostMove - count; 
6841                 if( count >= 100)
6842                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6843                          /* this is used to judge if draw claims are legal */
6844                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6845                          if(engineOpponent) {
6846                            SendToProgram("force\n", engineOpponent); // suppress reply
6847                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6848                          }
6849                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6850                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6851                          return 1;
6852                 }
6853
6854                 /* if draw offer is pending, treat it as a draw claim
6855                  * when draw condition present, to allow engines a way to
6856                  * claim draws before making their move to avoid a race
6857                  * condition occurring after their move
6858                  */
6859                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6860                          char *p = NULL;
6861                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6862                              p = "Draw claim: 50-move rule";
6863                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6864                              p = "Draw claim: 3-fold repetition";
6865                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6866                              p = "Draw claim: insufficient mating material";
6867                          if( p != NULL && canAdjudicate) {
6868                              if(engineOpponent) {
6869                                SendToProgram("force\n", engineOpponent); // suppress reply
6870                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6871                              }
6872                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6874                              return 1;
6875                          }
6876                 }
6877
6878                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6879                     if(engineOpponent) {
6880                       SendToProgram("force\n", engineOpponent); // suppress reply
6881                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6882                     }
6883                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6884                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6885                     return 1;
6886                 }
6887         return 0;
6888 }
6889
6890 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6891 {   // [HGM] book: this routine intercepts moves to simulate book replies
6892     char *bookHit = NULL;
6893
6894     //first determine if the incoming move brings opponent into his book
6895     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6896         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6897     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6898     if(bookHit != NULL && !cps->bookSuspend) {
6899         // make sure opponent is not going to reply after receiving move to book position
6900         SendToProgram("force\n", cps);
6901         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6902     }
6903     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6904     // now arrange restart after book miss
6905     if(bookHit) {
6906         // after a book hit we never send 'go', and the code after the call to this routine
6907         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6908         char buf[MSG_SIZ];
6909         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6910         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6911         SendToProgram(buf, cps);
6912         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6913     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6914         SendToProgram("go\n", cps);
6915         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6916     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6917         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6918             SendToProgram("go\n", cps); 
6919         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6920     }
6921     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6922 }
6923
6924 char *savedMessage;
6925 ChessProgramState *savedState;
6926 void DeferredBookMove(void)
6927 {
6928         if(savedState->lastPing != savedState->lastPong)
6929                     ScheduleDelayedEvent(DeferredBookMove, 10);
6930         else
6931         HandleMachineMove(savedMessage, savedState);
6932 }
6933
6934 void
6935 HandleMachineMove(message, cps)
6936      char *message;
6937      ChessProgramState *cps;
6938 {
6939     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6940     char realname[MSG_SIZ];
6941     int fromX, fromY, toX, toY;
6942     ChessMove moveType;
6943     char promoChar;
6944     char *p;
6945     int machineWhite;
6946     char *bookHit;
6947
6948     cps->userError = 0;
6949
6950 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6951     /*
6952      * Kludge to ignore BEL characters
6953      */
6954     while (*message == '\007') message++;
6955
6956     /*
6957      * [HGM] engine debug message: ignore lines starting with '#' character
6958      */
6959     if(cps->debug && *message == '#') return;
6960
6961     /*
6962      * Look for book output
6963      */
6964     if (cps == &first && bookRequested) {
6965         if (message[0] == '\t' || message[0] == ' ') {
6966             /* Part of the book output is here; append it */
6967             strcat(bookOutput, message);
6968             strcat(bookOutput, "  \n");
6969             return;
6970         } else if (bookOutput[0] != NULLCHAR) {
6971             /* All of book output has arrived; display it */
6972             char *p = bookOutput;
6973             while (*p != NULLCHAR) {
6974                 if (*p == '\t') *p = ' ';
6975                 p++;
6976             }
6977             DisplayInformation(bookOutput);
6978             bookRequested = FALSE;
6979             /* Fall through to parse the current output */
6980         }
6981     }
6982
6983     /*
6984      * Look for machine move.
6985      */
6986     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6987         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6988     {
6989         /* This method is only useful on engines that support ping */
6990         if (cps->lastPing != cps->lastPong) {
6991           if (gameMode == BeginningOfGame) {
6992             /* Extra move from before last new; ignore */
6993             if (appData.debugMode) {
6994                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6995             }
6996           } else {
6997             if (appData.debugMode) {
6998                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6999                         cps->which, gameMode);
7000             }
7001
7002             SendToProgram("undo\n", cps);
7003           }
7004           return;
7005         }
7006
7007         switch (gameMode) {
7008           case BeginningOfGame:
7009             /* Extra move from before last reset; ignore */
7010             if (appData.debugMode) {
7011                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7012             }
7013             return;
7014
7015           case EndOfGame:
7016           case IcsIdle:
7017           default:
7018             /* Extra move after we tried to stop.  The mode test is
7019                not a reliable way of detecting this problem, but it's
7020                the best we can do on engines that don't support ping.
7021             */
7022             if (appData.debugMode) {
7023                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7024                         cps->which, gameMode);
7025             }
7026             SendToProgram("undo\n", cps);
7027             return;
7028
7029           case MachinePlaysWhite:
7030           case IcsPlayingWhite:
7031             machineWhite = TRUE;
7032             break;
7033
7034           case MachinePlaysBlack:
7035           case IcsPlayingBlack:
7036             machineWhite = FALSE;
7037             break;
7038
7039           case TwoMachinesPlay:
7040             machineWhite = (cps->twoMachinesColor[0] == 'w');
7041             break;
7042         }
7043         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7044             if (appData.debugMode) {
7045                 fprintf(debugFP,
7046                         "Ignoring move out of turn by %s, gameMode %d"
7047                         ", forwardMost %d\n",
7048                         cps->which, gameMode, forwardMostMove);
7049             }
7050             return;
7051         }
7052
7053     if (appData.debugMode) { int f = forwardMostMove;
7054         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7055                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7056                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7057     }
7058         if(cps->alphaRank) AlphaRank(machineMove, 4);
7059         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7060                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7061             /* Machine move could not be parsed; ignore it. */
7062             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7063                     machineMove, cps->which);
7064             DisplayError(buf1, 0);
7065             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7066                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7067             if (gameMode == TwoMachinesPlay) {
7068               GameEnds(machineWhite ? BlackWins : WhiteWins,
7069                        buf1, GE_XBOARD);
7070             }
7071             return;
7072         }
7073
7074         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7075         /* So we have to redo legality test with true e.p. status here,  */
7076         /* to make sure an illegal e.p. capture does not slip through,   */
7077         /* to cause a forfeit on a justified illegal-move complaint      */
7078         /* of the opponent.                                              */
7079         if( gameMode==TwoMachinesPlay && appData.testLegality
7080             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7081                                                               ) {
7082            ChessMove moveType;
7083            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7084                              fromY, fromX, toY, toX, promoChar);
7085             if (appData.debugMode) {
7086                 int i;
7087                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7088                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7089                 fprintf(debugFP, "castling rights\n");
7090             }
7091             if(moveType == IllegalMove) {
7092                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7093                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7094                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7095                            buf1, GE_XBOARD);
7096                 return;
7097            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7098            /* [HGM] Kludge to handle engines that send FRC-style castling
7099               when they shouldn't (like TSCP-Gothic) */
7100            switch(moveType) {
7101              case WhiteASideCastleFR:
7102              case BlackASideCastleFR:
7103                toX+=2;
7104                currentMoveString[2]++;
7105                break;
7106              case WhiteHSideCastleFR:
7107              case BlackHSideCastleFR:
7108                toX--;
7109                currentMoveString[2]--;
7110                break;
7111              default: ; // nothing to do, but suppresses warning of pedantic compilers
7112            }
7113         }
7114         hintRequested = FALSE;
7115         lastHint[0] = NULLCHAR;
7116         bookRequested = FALSE;
7117         /* Program may be pondering now */
7118         cps->maybeThinking = TRUE;
7119         if (cps->sendTime == 2) cps->sendTime = 1;
7120         if (cps->offeredDraw) cps->offeredDraw--;
7121
7122         /* currentMoveString is set as a side-effect of ParseOneMove */
7123         strcpy(machineMove, currentMoveString);
7124         strcat(machineMove, "\n");
7125         strcpy(moveList[forwardMostMove], machineMove);
7126
7127         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7128
7129         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7130         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7131             int count = 0;
7132
7133             while( count < adjudicateLossPlies ) {
7134                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7135
7136                 if( count & 1 ) {
7137                     score = -score; /* Flip score for winning side */
7138                 }
7139
7140                 if( score > adjudicateLossThreshold ) {
7141                     break;
7142                 }
7143
7144                 count++;
7145             }
7146
7147             if( count >= adjudicateLossPlies ) {
7148                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7149
7150                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7151                     "Xboard adjudication", 
7152                     GE_XBOARD );
7153
7154                 return;
7155             }
7156         }
7157
7158         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7159
7160 #if ZIPPY
7161         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7162             first.initDone) {
7163           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7164                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7165                 SendToICS("draw ");
7166                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7167           }
7168           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7169           ics_user_moved = 1;
7170           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7171                 char buf[3*MSG_SIZ];
7172
7173                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7174                         programStats.score / 100.,
7175                         programStats.depth,
7176                         programStats.time / 100.,
7177                         (unsigned int)programStats.nodes,
7178                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7179                         programStats.movelist);
7180                 SendToICS(buf);
7181 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7182           }
7183         }
7184 #endif
7185
7186         /* [AS] Save move info and clear stats for next move */
7187         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7188         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7189         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7190         ClearProgramStats();
7191         thinkOutput[0] = NULLCHAR;
7192         hiddenThinkOutputState = 0;
7193
7194         bookHit = NULL;
7195         if (gameMode == TwoMachinesPlay) {
7196             /* [HGM] relaying draw offers moved to after reception of move */
7197             /* and interpreting offer as claim if it brings draw condition */
7198             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7199                 SendToProgram("draw\n", cps->other);
7200             }
7201             if (cps->other->sendTime) {
7202                 SendTimeRemaining(cps->other,
7203                                   cps->other->twoMachinesColor[0] == 'w');
7204             }
7205             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7206             if (firstMove && !bookHit) {
7207                 firstMove = FALSE;
7208                 if (cps->other->useColors) {
7209                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7210                 }
7211                 SendToProgram("go\n", cps->other);
7212             }
7213             cps->other->maybeThinking = TRUE;
7214         }
7215
7216         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7217
7218         if (!pausing && appData.ringBellAfterMoves) {
7219             RingBell();
7220         }
7221
7222         /*
7223          * Reenable menu items that were disabled while
7224          * machine was thinking
7225          */
7226         if (gameMode != TwoMachinesPlay)
7227             SetUserThinkingEnables();
7228
7229         // [HGM] book: after book hit opponent has received move and is now in force mode
7230         // force the book reply into it, and then fake that it outputted this move by jumping
7231         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7232         if(bookHit) {
7233                 static char bookMove[MSG_SIZ]; // a bit generous?
7234
7235                 strcpy(bookMove, "move ");
7236                 strcat(bookMove, bookHit);
7237                 message = bookMove;
7238                 cps = cps->other;
7239                 programStats.nodes = programStats.depth = programStats.time =
7240                 programStats.score = programStats.got_only_move = 0;
7241                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7242
7243                 if(cps->lastPing != cps->lastPong) {
7244                     savedMessage = message; // args for deferred call
7245                     savedState = cps;
7246                     ScheduleDelayedEvent(DeferredBookMove, 10);
7247                     return;
7248                 }
7249                 goto FakeBookMove;
7250         }
7251
7252         return;
7253     }
7254
7255     /* Set special modes for chess engines.  Later something general
7256      *  could be added here; for now there is just one kludge feature,
7257      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7258      *  when "xboard" is given as an interactive command.
7259      */
7260     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7261         cps->useSigint = FALSE;
7262         cps->useSigterm = FALSE;
7263     }
7264     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7265       ParseFeatures(message+8, cps);
7266       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7267     }
7268
7269     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7270      * want this, I was asked to put it in, and obliged.
7271      */
7272     if (!strncmp(message, "setboard ", 9)) {
7273         Board initial_position;
7274
7275         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7276
7277         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7278             DisplayError(_("Bad FEN received from engine"), 0);
7279             return ;
7280         } else {
7281            Reset(TRUE, FALSE);
7282            CopyBoard(boards[0], initial_position);
7283            initialRulePlies = FENrulePlies;
7284            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7285            else gameMode = MachinePlaysBlack;
7286            DrawPosition(FALSE, boards[currentMove]);
7287         }
7288         return;
7289     }
7290
7291     /*
7292      * Look for communication commands
7293      */
7294     if (!strncmp(message, "telluser ", 9)) {
7295         DisplayNote(message + 9);
7296         return;
7297     }
7298     if (!strncmp(message, "tellusererror ", 14)) {
7299         cps->userError = 1;
7300         DisplayError(message + 14, 0);
7301         return;
7302     }
7303     if (!strncmp(message, "tellopponent ", 13)) {
7304       if (appData.icsActive) {
7305         if (loggedOn) {
7306           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7307           SendToICS(buf1);
7308         }
7309       } else {
7310         DisplayNote(message + 13);
7311       }
7312       return;
7313     }
7314     if (!strncmp(message, "tellothers ", 11)) {
7315       if (appData.icsActive) {
7316         if (loggedOn) {
7317           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7318           SendToICS(buf1);
7319         }
7320       }
7321       return;
7322     }
7323     if (!strncmp(message, "tellall ", 8)) {
7324       if (appData.icsActive) {
7325         if (loggedOn) {
7326           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7327           SendToICS(buf1);
7328         }
7329       } else {
7330         DisplayNote(message + 8);
7331       }
7332       return;
7333     }
7334     if (strncmp(message, "warning", 7) == 0) {
7335         /* Undocumented feature, use tellusererror in new code */
7336         DisplayError(message, 0);
7337         return;
7338     }
7339     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7340         strcpy(realname, cps->tidy);
7341         strcat(realname, " query");
7342         AskQuestion(realname, buf2, buf1, cps->pr);
7343         return;
7344     }
7345     /* Commands from the engine directly to ICS.  We don't allow these to be
7346      *  sent until we are logged on. Crafty kibitzes have been known to
7347      *  interfere with the login process.
7348      */
7349     if (loggedOn) {
7350         if (!strncmp(message, "tellics ", 8)) {
7351             SendToICS(message + 8);
7352             SendToICS("\n");
7353             return;
7354         }
7355         if (!strncmp(message, "tellicsnoalias ", 15)) {
7356             SendToICS(ics_prefix);
7357             SendToICS(message + 15);
7358             SendToICS("\n");
7359             return;
7360         }
7361         /* The following are for backward compatibility only */
7362         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7363             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7364             SendToICS(ics_prefix);
7365             SendToICS(message);
7366             SendToICS("\n");
7367             return;
7368         }
7369     }
7370     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7371         return;
7372     }
7373     /*
7374      * If the move is illegal, cancel it and redraw the board.
7375      * Also deal with other error cases.  Matching is rather loose
7376      * here to accommodate engines written before the spec.
7377      */
7378     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7379         strncmp(message, "Error", 5) == 0) {
7380         if (StrStr(message, "name") ||
7381             StrStr(message, "rating") || StrStr(message, "?") ||
7382             StrStr(message, "result") || StrStr(message, "board") ||
7383             StrStr(message, "bk") || StrStr(message, "computer") ||
7384             StrStr(message, "variant") || StrStr(message, "hint") ||
7385             StrStr(message, "random") || StrStr(message, "depth") ||
7386             StrStr(message, "accepted")) {
7387             return;
7388         }
7389         if (StrStr(message, "protover")) {
7390           /* Program is responding to input, so it's apparently done
7391              initializing, and this error message indicates it is
7392              protocol version 1.  So we don't need to wait any longer
7393              for it to initialize and send feature commands. */
7394           FeatureDone(cps, 1);
7395           cps->protocolVersion = 1;
7396           return;
7397         }
7398         cps->maybeThinking = FALSE;
7399
7400         if (StrStr(message, "draw")) {
7401             /* Program doesn't have "draw" command */
7402             cps->sendDrawOffers = 0;
7403             return;
7404         }
7405         if (cps->sendTime != 1 &&
7406             (StrStr(message, "time") || StrStr(message, "otim"))) {
7407           /* Program apparently doesn't have "time" or "otim" command */
7408           cps->sendTime = 0;
7409           return;
7410         }
7411         if (StrStr(message, "analyze")) {
7412             cps->analysisSupport = FALSE;
7413             cps->analyzing = FALSE;
7414             Reset(FALSE, TRUE);
7415             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7416             DisplayError(buf2, 0);
7417             return;
7418         }
7419         if (StrStr(message, "(no matching move)st")) {
7420           /* Special kludge for GNU Chess 4 only */
7421           cps->stKludge = TRUE;
7422           SendTimeControl(cps, movesPerSession, timeControl,
7423                           timeIncrement, appData.searchDepth,
7424                           searchTime);
7425           return;
7426         }
7427         if (StrStr(message, "(no matching move)sd")) {
7428           /* Special kludge for GNU Chess 4 only */
7429           cps->sdKludge = TRUE;
7430           SendTimeControl(cps, movesPerSession, timeControl,
7431                           timeIncrement, appData.searchDepth,
7432                           searchTime);
7433           return;
7434         }
7435         if (!StrStr(message, "llegal")) {
7436             return;
7437         }
7438         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7439             gameMode == IcsIdle) return;
7440         if (forwardMostMove <= backwardMostMove) return;
7441         if (pausing) PauseEvent();
7442       if(appData.forceIllegal) {
7443             // [HGM] illegal: machine refused move; force position after move into it
7444           SendToProgram("force\n", cps);
7445           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7446                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7447                 // when black is to move, while there might be nothing on a2 or black
7448                 // might already have the move. So send the board as if white has the move.
7449                 // But first we must change the stm of the engine, as it refused the last move
7450                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7451                 if(WhiteOnMove(forwardMostMove)) {
7452                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7453                     SendBoard(cps, forwardMostMove); // kludgeless board
7454                 } else {
7455                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7456                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7457                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7458                 }
7459           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7460             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7461                  gameMode == TwoMachinesPlay)
7462               SendToProgram("go\n", cps);
7463             return;
7464       } else
7465         if (gameMode == PlayFromGameFile) {
7466             /* Stop reading this game file */
7467             gameMode = EditGame;
7468             ModeHighlight();
7469         }
7470         currentMove = --forwardMostMove;
7471         DisplayMove(currentMove-1); /* before DisplayMoveError */
7472         SwitchClocks();
7473         DisplayBothClocks();
7474         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7475                 parseList[currentMove], cps->which);
7476         DisplayMoveError(buf1);
7477         DrawPosition(FALSE, boards[currentMove]);
7478
7479         /* [HGM] illegal-move claim should forfeit game when Xboard */
7480         /* only passes fully legal moves                            */
7481         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7482             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7483                                 "False illegal-move claim", GE_XBOARD );
7484         }
7485         return;
7486     }
7487     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7488         /* Program has a broken "time" command that
7489            outputs a string not ending in newline.
7490            Don't use it. */
7491         cps->sendTime = 0;
7492     }
7493
7494     /*
7495      * If chess program startup fails, exit with an error message.
7496      * Attempts to recover here are futile.
7497      */
7498     if ((StrStr(message, "unknown host") != NULL)
7499         || (StrStr(message, "No remote directory") != NULL)
7500         || (StrStr(message, "not found") != NULL)
7501         || (StrStr(message, "No such file") != NULL)
7502         || (StrStr(message, "can't alloc") != NULL)
7503         || (StrStr(message, "Permission denied") != NULL)) {
7504
7505         cps->maybeThinking = FALSE;
7506         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7507                 cps->which, cps->program, cps->host, message);
7508         RemoveInputSource(cps->isr);
7509         DisplayFatalError(buf1, 0, 1);
7510         return;
7511     }
7512
7513     /*
7514      * Look for hint output
7515      */
7516     if (sscanf(message, "Hint: %s", buf1) == 1) {
7517         if (cps == &first && hintRequested) {
7518             hintRequested = FALSE;
7519             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7520                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7521                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7522                                     PosFlags(forwardMostMove),
7523                                     fromY, fromX, toY, toX, promoChar, buf1);
7524                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7525                 DisplayInformation(buf2);
7526             } else {
7527                 /* Hint move could not be parsed!? */
7528               snprintf(buf2, sizeof(buf2),
7529                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7530                         buf1, cps->which);
7531                 DisplayError(buf2, 0);
7532             }
7533         } else {
7534             strcpy(lastHint, buf1);
7535         }
7536         return;
7537     }
7538
7539     /*
7540      * Ignore other messages if game is not in progress
7541      */
7542     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7543         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7544
7545     /*
7546      * look for win, lose, draw, or draw offer
7547      */
7548     if (strncmp(message, "1-0", 3) == 0) {
7549         char *p, *q, *r = "";
7550         p = strchr(message, '{');
7551         if (p) {
7552             q = strchr(p, '}');
7553             if (q) {
7554                 *q = NULLCHAR;
7555                 r = p + 1;
7556             }
7557         }
7558         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7559         return;
7560     } else if (strncmp(message, "0-1", 3) == 0) {
7561         char *p, *q, *r = "";
7562         p = strchr(message, '{');
7563         if (p) {
7564             q = strchr(p, '}');
7565             if (q) {
7566                 *q = NULLCHAR;
7567                 r = p + 1;
7568             }
7569         }
7570         /* Kludge for Arasan 4.1 bug */
7571         if (strcmp(r, "Black resigns") == 0) {
7572             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7573             return;
7574         }
7575         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7576         return;
7577     } else if (strncmp(message, "1/2", 3) == 0) {
7578         char *p, *q, *r = "";
7579         p = strchr(message, '{');
7580         if (p) {
7581             q = strchr(p, '}');
7582             if (q) {
7583                 *q = NULLCHAR;
7584                 r = p + 1;
7585             }
7586         }
7587
7588         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7589         return;
7590
7591     } else if (strncmp(message, "White resign", 12) == 0) {
7592         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7593         return;
7594     } else if (strncmp(message, "Black resign", 12) == 0) {
7595         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7596         return;
7597     } else if (strncmp(message, "White matches", 13) == 0 ||
7598                strncmp(message, "Black matches", 13) == 0   ) {
7599         /* [HGM] ignore GNUShogi noises */
7600         return;
7601     } else if (strncmp(message, "White", 5) == 0 &&
7602                message[5] != '(' &&
7603                StrStr(message, "Black") == NULL) {
7604         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7605         return;
7606     } else if (strncmp(message, "Black", 5) == 0 &&
7607                message[5] != '(') {
7608         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7609         return;
7610     } else if (strcmp(message, "resign") == 0 ||
7611                strcmp(message, "computer resigns") == 0) {
7612         switch (gameMode) {
7613           case MachinePlaysBlack:
7614           case IcsPlayingBlack:
7615             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7616             break;
7617           case MachinePlaysWhite:
7618           case IcsPlayingWhite:
7619             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7620             break;
7621           case TwoMachinesPlay:
7622             if (cps->twoMachinesColor[0] == 'w')
7623               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7624             else
7625               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7626             break;
7627           default:
7628             /* can't happen */
7629             break;
7630         }
7631         return;
7632     } else if (strncmp(message, "opponent mates", 14) == 0) {
7633         switch (gameMode) {
7634           case MachinePlaysBlack:
7635           case IcsPlayingBlack:
7636             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7637             break;
7638           case MachinePlaysWhite:
7639           case IcsPlayingWhite:
7640             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7641             break;
7642           case TwoMachinesPlay:
7643             if (cps->twoMachinesColor[0] == 'w')
7644               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7645             else
7646               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7647             break;
7648           default:
7649             /* can't happen */
7650             break;
7651         }
7652         return;
7653     } else if (strncmp(message, "computer mates", 14) == 0) {
7654         switch (gameMode) {
7655           case MachinePlaysBlack:
7656           case IcsPlayingBlack:
7657             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7658             break;
7659           case MachinePlaysWhite:
7660           case IcsPlayingWhite:
7661             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7662             break;
7663           case TwoMachinesPlay:
7664             if (cps->twoMachinesColor[0] == 'w')
7665               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7666             else
7667               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7668             break;
7669           default:
7670             /* can't happen */
7671             break;
7672         }
7673         return;
7674     } else if (strncmp(message, "checkmate", 9) == 0) {
7675         if (WhiteOnMove(forwardMostMove)) {
7676             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7677         } else {
7678             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7679         }
7680         return;
7681     } else if (strstr(message, "Draw") != NULL ||
7682                strstr(message, "game is a draw") != NULL) {
7683         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7684         return;
7685     } else if (strstr(message, "offer") != NULL &&
7686                strstr(message, "draw") != NULL) {
7687 #if ZIPPY
7688         if (appData.zippyPlay && first.initDone) {
7689             /* Relay offer to ICS */
7690             SendToICS(ics_prefix);
7691             SendToICS("draw\n");
7692         }
7693 #endif
7694         cps->offeredDraw = 2; /* valid until this engine moves twice */
7695         if (gameMode == TwoMachinesPlay) {
7696             if (cps->other->offeredDraw) {
7697                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7698             /* [HGM] in two-machine mode we delay relaying draw offer      */
7699             /* until after we also have move, to see if it is really claim */
7700             }
7701         } else if (gameMode == MachinePlaysWhite ||
7702                    gameMode == MachinePlaysBlack) {
7703           if (userOfferedDraw) {
7704             DisplayInformation(_("Machine accepts your draw offer"));
7705             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7706           } else {
7707             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7708           }
7709         }
7710     }
7711
7712
7713     /*
7714      * Look for thinking output
7715      */
7716     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7717           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7718                                 ) {
7719         int plylev, mvleft, mvtot, curscore, time;
7720         char mvname[MOVE_LEN];
7721         u64 nodes; // [DM]
7722         char plyext;
7723         int ignore = FALSE;
7724         int prefixHint = FALSE;
7725         mvname[0] = NULLCHAR;
7726
7727         switch (gameMode) {
7728           case MachinePlaysBlack:
7729           case IcsPlayingBlack:
7730             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7731             break;
7732           case MachinePlaysWhite:
7733           case IcsPlayingWhite:
7734             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7735             break;
7736           case AnalyzeMode:
7737           case AnalyzeFile:
7738             break;
7739           case IcsObserving: /* [DM] icsEngineAnalyze */
7740             if (!appData.icsEngineAnalyze) ignore = TRUE;
7741             break;
7742           case TwoMachinesPlay:
7743             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7744                 ignore = TRUE;
7745             }
7746             break;
7747           default:
7748             ignore = TRUE;
7749             break;
7750         }
7751
7752         if (!ignore) {
7753             buf1[0] = NULLCHAR;
7754             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7755                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7756
7757                 if (plyext != ' ' && plyext != '\t') {
7758                     time *= 100;
7759                 }
7760
7761                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7762                 if( cps->scoreIsAbsolute && 
7763                     ( gameMode == MachinePlaysBlack ||
7764                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7765                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7766                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7767                      !WhiteOnMove(currentMove)
7768                     ) )
7769                 {
7770                     curscore = -curscore;
7771                 }
7772
7773
7774                 programStats.depth = plylev;
7775                 programStats.nodes = nodes;
7776                 programStats.time = time;
7777                 programStats.score = curscore;
7778                 programStats.got_only_move = 0;
7779
7780                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7781                         int ticklen;
7782
7783                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7784                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7785                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7786                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7787                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7788                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7789                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7790                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7791                 }
7792
7793                 /* Buffer overflow protection */
7794                 if (buf1[0] != NULLCHAR) {
7795                     if (strlen(buf1) >= sizeof(programStats.movelist)
7796                         && appData.debugMode) {
7797                         fprintf(debugFP,
7798                                 "PV is too long; using the first %u bytes.\n",
7799                                 (unsigned) sizeof(programStats.movelist) - 1);
7800                     }
7801
7802                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7803                 } else {
7804                     sprintf(programStats.movelist, " no PV\n");
7805                 }
7806
7807                 if (programStats.seen_stat) {
7808                     programStats.ok_to_send = 1;
7809                 }
7810
7811                 if (strchr(programStats.movelist, '(') != NULL) {
7812                     programStats.line_is_book = 1;
7813                     programStats.nr_moves = 0;
7814                     programStats.moves_left = 0;
7815                 } else {
7816                     programStats.line_is_book = 0;
7817                 }
7818
7819                 SendProgramStatsToFrontend( cps, &programStats );
7820
7821                 /*
7822                     [AS] Protect the thinkOutput buffer from overflow... this
7823                     is only useful if buf1 hasn't overflowed first!
7824                 */
7825                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7826                         plylev,
7827                         (gameMode == TwoMachinesPlay ?
7828                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7829                         ((double) curscore) / 100.0,
7830                         prefixHint ? lastHint : "",
7831                         prefixHint ? " " : "" );
7832
7833                 if( buf1[0] != NULLCHAR ) {
7834                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7835
7836                     if( strlen(buf1) > max_len ) {
7837                         if( appData.debugMode) {
7838                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7839                         }
7840                         buf1[max_len+1] = '\0';
7841                     }
7842
7843                     strcat( thinkOutput, buf1 );
7844                 }
7845
7846                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7847                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7848                     DisplayMove(currentMove - 1);
7849                 }
7850                 return;
7851
7852             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7853                 /* crafty (9.25+) says "(only move) <move>"
7854                  * if there is only 1 legal move
7855                  */
7856                 sscanf(p, "(only move) %s", buf1);
7857                 sprintf(thinkOutput, "%s (only move)", buf1);
7858                 sprintf(programStats.movelist, "%s (only move)", buf1);
7859                 programStats.depth = 1;
7860                 programStats.nr_moves = 1;
7861                 programStats.moves_left = 1;
7862                 programStats.nodes = 1;
7863                 programStats.time = 1;
7864                 programStats.got_only_move = 1;
7865
7866                 /* Not really, but we also use this member to
7867                    mean "line isn't going to change" (Crafty
7868                    isn't searching, so stats won't change) */
7869                 programStats.line_is_book = 1;
7870
7871                 SendProgramStatsToFrontend( cps, &programStats );
7872
7873                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7874                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7875                     DisplayMove(currentMove - 1);
7876                 }
7877                 return;
7878             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7879                               &time, &nodes, &plylev, &mvleft,
7880                               &mvtot, mvname) >= 5) {
7881                 /* The stat01: line is from Crafty (9.29+) in response
7882                    to the "." command */
7883                 programStats.seen_stat = 1;
7884                 cps->maybeThinking = TRUE;
7885
7886                 if (programStats.got_only_move || !appData.periodicUpdates)
7887                   return;
7888
7889                 programStats.depth = plylev;
7890                 programStats.time = time;
7891                 programStats.nodes = nodes;
7892                 programStats.moves_left = mvleft;
7893                 programStats.nr_moves = mvtot;
7894                 strcpy(programStats.move_name, mvname);
7895                 programStats.ok_to_send = 1;
7896                 programStats.movelist[0] = '\0';
7897
7898                 SendProgramStatsToFrontend( cps, &programStats );
7899
7900                 return;
7901
7902             } else if (strncmp(message,"++",2) == 0) {
7903                 /* Crafty 9.29+ outputs this */
7904                 programStats.got_fail = 2;
7905                 return;
7906
7907             } else if (strncmp(message,"--",2) == 0) {
7908                 /* Crafty 9.29+ outputs this */
7909                 programStats.got_fail = 1;
7910                 return;
7911
7912             } else if (thinkOutput[0] != NULLCHAR &&
7913                        strncmp(message, "    ", 4) == 0) {
7914                 unsigned message_len;
7915
7916                 p = message;
7917                 while (*p && *p == ' ') p++;
7918
7919                 message_len = strlen( p );
7920
7921                 /* [AS] Avoid buffer overflow */
7922                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7923                     strcat(thinkOutput, " ");
7924                     strcat(thinkOutput, p);
7925                 }
7926
7927                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7928                     strcat(programStats.movelist, " ");
7929                     strcat(programStats.movelist, p);
7930                 }
7931
7932                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7933                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7934                     DisplayMove(currentMove - 1);
7935                 }
7936                 return;
7937             }
7938         }
7939         else {
7940             buf1[0] = NULLCHAR;
7941
7942             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7943                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7944             {
7945                 ChessProgramStats cpstats;
7946
7947                 if (plyext != ' ' && plyext != '\t') {
7948                     time *= 100;
7949                 }
7950
7951                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7952                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7953                     curscore = -curscore;
7954                 }
7955
7956                 cpstats.depth = plylev;
7957                 cpstats.nodes = nodes;
7958                 cpstats.time = time;
7959                 cpstats.score = curscore;
7960                 cpstats.got_only_move = 0;
7961                 cpstats.movelist[0] = '\0';
7962
7963                 if (buf1[0] != NULLCHAR) {
7964                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7965                 }
7966
7967                 cpstats.ok_to_send = 0;
7968                 cpstats.line_is_book = 0;
7969                 cpstats.nr_moves = 0;
7970                 cpstats.moves_left = 0;
7971
7972                 SendProgramStatsToFrontend( cps, &cpstats );
7973             }
7974         }
7975     }
7976 }
7977
7978
7979 /* Parse a game score from the character string "game", and
7980    record it as the history of the current game.  The game
7981    score is NOT assumed to start from the standard position.
7982    The display is not updated in any way.
7983    */
7984 void
7985 ParseGameHistory(game)
7986      char *game;
7987 {
7988     ChessMove moveType;
7989     int fromX, fromY, toX, toY, boardIndex;
7990     char promoChar;
7991     char *p, *q;
7992     char buf[MSG_SIZ];
7993
7994     if (appData.debugMode)
7995       fprintf(debugFP, "Parsing game history: %s\n", game);
7996
7997     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7998     gameInfo.site = StrSave(appData.icsHost);
7999     gameInfo.date = PGNDate();
8000     gameInfo.round = StrSave("-");
8001
8002     /* Parse out names of players */
8003     while (*game == ' ') game++;
8004     p = buf;
8005     while (*game != ' ') *p++ = *game++;
8006     *p = NULLCHAR;
8007     gameInfo.white = StrSave(buf);
8008     while (*game == ' ') game++;
8009     p = buf;
8010     while (*game != ' ' && *game != '\n') *p++ = *game++;
8011     *p = NULLCHAR;
8012     gameInfo.black = StrSave(buf);
8013
8014     /* Parse moves */
8015     boardIndex = blackPlaysFirst ? 1 : 0;
8016     yynewstr(game);
8017     for (;;) {
8018         yyboardindex = boardIndex;
8019         moveType = (ChessMove) yylex();
8020         switch (moveType) {
8021           case IllegalMove:             /* maybe suicide chess, etc. */
8022   if (appData.debugMode) {
8023     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8024     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8025     setbuf(debugFP, NULL);
8026   }
8027           case WhitePromotionChancellor:
8028           case BlackPromotionChancellor:
8029           case WhitePromotionArchbishop:
8030           case BlackPromotionArchbishop:
8031           case WhitePromotionQueen:
8032           case BlackPromotionQueen:
8033           case WhitePromotionRook:
8034           case BlackPromotionRook:
8035           case WhitePromotionBishop:
8036           case BlackPromotionBishop:
8037           case WhitePromotionKnight:
8038           case BlackPromotionKnight:
8039           case WhitePromotionKing:
8040           case BlackPromotionKing:
8041           case NormalMove:
8042           case WhiteCapturesEnPassant:
8043           case BlackCapturesEnPassant:
8044           case WhiteKingSideCastle:
8045           case WhiteQueenSideCastle:
8046           case BlackKingSideCastle:
8047           case BlackQueenSideCastle:
8048           case WhiteKingSideCastleWild:
8049           case WhiteQueenSideCastleWild:
8050           case BlackKingSideCastleWild:
8051           case BlackQueenSideCastleWild:
8052           /* PUSH Fabien */
8053           case WhiteHSideCastleFR:
8054           case WhiteASideCastleFR:
8055           case BlackHSideCastleFR:
8056           case BlackASideCastleFR:
8057           /* POP Fabien */
8058             fromX = currentMoveString[0] - AAA;
8059             fromY = currentMoveString[1] - ONE;
8060             toX = currentMoveString[2] - AAA;
8061             toY = currentMoveString[3] - ONE;
8062             promoChar = currentMoveString[4];
8063             break;
8064           case WhiteDrop:
8065           case BlackDrop:
8066             fromX = moveType == WhiteDrop ?
8067               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8068             (int) CharToPiece(ToLower(currentMoveString[0]));
8069             fromY = DROP_RANK;
8070             toX = currentMoveString[2] - AAA;
8071             toY = currentMoveString[3] - ONE;
8072             promoChar = NULLCHAR;
8073             break;
8074           case AmbiguousMove:
8075             /* bug? */
8076             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8077   if (appData.debugMode) {
8078     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8079     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8080     setbuf(debugFP, NULL);
8081   }
8082             DisplayError(buf, 0);
8083             return;
8084           case ImpossibleMove:
8085             /* bug? */
8086             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8087   if (appData.debugMode) {
8088     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8089     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8090     setbuf(debugFP, NULL);
8091   }
8092             DisplayError(buf, 0);
8093             return;
8094           case (ChessMove) 0:   /* end of file */
8095             if (boardIndex < backwardMostMove) {
8096                 /* Oops, gap.  How did that happen? */
8097                 DisplayError(_("Gap in move list"), 0);
8098                 return;
8099             }
8100             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8101             if (boardIndex > forwardMostMove) {
8102                 forwardMostMove = boardIndex;
8103             }
8104             return;
8105           case ElapsedTime:
8106             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8107                 strcat(parseList[boardIndex-1], " ");
8108                 strcat(parseList[boardIndex-1], yy_text);
8109             }
8110             continue;
8111           case Comment:
8112           case PGNTag:
8113           case NAG:
8114           default:
8115             /* ignore */
8116             continue;
8117           case WhiteWins:
8118           case BlackWins:
8119           case GameIsDrawn:
8120           case GameUnfinished:
8121             if (gameMode == IcsExamining) {
8122                 if (boardIndex < backwardMostMove) {
8123                     /* Oops, gap.  How did that happen? */
8124                     return;
8125                 }
8126                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8127                 return;
8128             }
8129             gameInfo.result = moveType;
8130             p = strchr(yy_text, '{');
8131             if (p == NULL) p = strchr(yy_text, '(');
8132             if (p == NULL) {
8133                 p = yy_text;
8134                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8135             } else {
8136                 q = strchr(p, *p == '{' ? '}' : ')');
8137                 if (q != NULL) *q = NULLCHAR;
8138                 p++;
8139             }
8140             gameInfo.resultDetails = StrSave(p);
8141             continue;
8142         }
8143         if (boardIndex >= forwardMostMove &&
8144             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8145             backwardMostMove = blackPlaysFirst ? 1 : 0;
8146             return;
8147         }
8148         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8149                                  fromY, fromX, toY, toX, promoChar,
8150                                  parseList[boardIndex]);
8151         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8152         /* currentMoveString is set as a side-effect of yylex */
8153         strcpy(moveList[boardIndex], currentMoveString);
8154         strcat(moveList[boardIndex], "\n");
8155         boardIndex++;
8156         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8157         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8158           case MT_NONE:
8159           case MT_STALEMATE:
8160           default:
8161             break;
8162           case MT_CHECK:
8163             if(gameInfo.variant != VariantShogi)
8164                 strcat(parseList[boardIndex - 1], "+");
8165             break;
8166           case MT_CHECKMATE:
8167           case MT_STAINMATE:
8168             strcat(parseList[boardIndex - 1], "#");
8169             break;
8170         }
8171     }
8172 }
8173
8174
8175 /* Apply a move to the given board  */
8176 void
8177 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8178      int fromX, fromY, toX, toY;
8179      int promoChar;
8180      Board board;
8181 {
8182   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8183   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8184
8185     /* [HGM] compute & store e.p. status and castling rights for new position */
8186     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8187     { int i;
8188
8189       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8190       oldEP = (signed char)board[EP_STATUS];
8191       board[EP_STATUS] = EP_NONE;
8192
8193       if( board[toY][toX] != EmptySquare ) 
8194            board[EP_STATUS] = EP_CAPTURE;  
8195
8196       if( board[fromY][fromX] == WhitePawn ) {
8197            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8198                board[EP_STATUS] = EP_PAWN_MOVE;
8199            if( toY-fromY==2) {
8200                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8201                         gameInfo.variant != VariantBerolina || toX < fromX)
8202                       board[EP_STATUS] = toX | berolina;
8203                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8204                   gameInfo.variant != VariantBerolina || toX > fromX) 
8205                  board[EP_STATUS] = toX;
8206            }
8207       } else
8208       if( board[fromY][fromX] == BlackPawn ) {
8209            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8210                board[EP_STATUS] = EP_PAWN_MOVE; 
8211            if( toY-fromY== -2) {
8212                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8213                         gameInfo.variant != VariantBerolina || toX < fromX)
8214                       board[EP_STATUS] = toX | berolina;
8215                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8216                         gameInfo.variant != VariantBerolina || toX > fromX) 
8217                       board[EP_STATUS] = toX;
8218            }
8219        }
8220
8221        for(i=0; i<nrCastlingRights; i++) {
8222            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8223               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8224              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8225        }
8226
8227     }
8228
8229   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8230   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8231        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8232
8233   if (fromX == toX && fromY == toY) return;
8234
8235   if (fromY == DROP_RANK) {
8236         /* must be first */
8237         piece = board[toY][toX] = (ChessSquare) fromX;
8238   } else {
8239      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8240      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8241      if(gameInfo.variant == VariantKnightmate)
8242          king += (int) WhiteUnicorn - (int) WhiteKing;
8243
8244     /* Code added by Tord: */
8245     /* FRC castling assumed when king captures friendly rook. */
8246     if (board[fromY][fromX] == WhiteKing &&
8247              board[toY][toX] == WhiteRook) {
8248       board[fromY][fromX] = EmptySquare;
8249       board[toY][toX] = EmptySquare;
8250       if(toX > fromX) {
8251         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8252       } else {
8253         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8254       }
8255     } else if (board[fromY][fromX] == BlackKing &&
8256                board[toY][toX] == BlackRook) {
8257       board[fromY][fromX] = EmptySquare;
8258       board[toY][toX] = EmptySquare;
8259       if(toX > fromX) {
8260         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8261       } else {
8262         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8263       }
8264     /* End of code added by Tord */
8265
8266     } else if (board[fromY][fromX] == king
8267         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8268         && toY == fromY && toX > fromX+1) {
8269         board[fromY][fromX] = EmptySquare;
8270         board[toY][toX] = king;
8271         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8272         board[fromY][BOARD_RGHT-1] = EmptySquare;
8273     } else if (board[fromY][fromX] == king
8274         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8275                && toY == fromY && toX < fromX-1) {
8276         board[fromY][fromX] = EmptySquare;
8277         board[toY][toX] = king;
8278         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8279         board[fromY][BOARD_LEFT] = EmptySquare;
8280     } else if (board[fromY][fromX] == WhitePawn
8281                && toY >= BOARD_HEIGHT-promoRank
8282                && gameInfo.variant != VariantXiangqi
8283                ) {
8284         /* white pawn promotion */
8285         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8286         if (board[toY][toX] == EmptySquare) {
8287             board[toY][toX] = WhiteQueen;
8288         }
8289         if(gameInfo.variant==VariantBughouse ||
8290            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8291             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8292         board[fromY][fromX] = EmptySquare;
8293     } else if ((fromY == BOARD_HEIGHT-4)
8294                && (toX != fromX)
8295                && gameInfo.variant != VariantXiangqi
8296                && gameInfo.variant != VariantBerolina
8297                && (board[fromY][fromX] == WhitePawn)
8298                && (board[toY][toX] == EmptySquare)) {
8299         board[fromY][fromX] = EmptySquare;
8300         board[toY][toX] = WhitePawn;
8301         captured = board[toY - 1][toX];
8302         board[toY - 1][toX] = EmptySquare;
8303     } else if ((fromY == BOARD_HEIGHT-4)
8304                && (toX == fromX)
8305                && gameInfo.variant == VariantBerolina
8306                && (board[fromY][fromX] == WhitePawn)
8307                && (board[toY][toX] == EmptySquare)) {
8308         board[fromY][fromX] = EmptySquare;
8309         board[toY][toX] = WhitePawn;
8310         if(oldEP & EP_BEROLIN_A) {
8311                 captured = board[fromY][fromX-1];
8312                 board[fromY][fromX-1] = EmptySquare;
8313         }else{  captured = board[fromY][fromX+1];
8314                 board[fromY][fromX+1] = EmptySquare;
8315         }
8316     } else if (board[fromY][fromX] == king
8317         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8318                && toY == fromY && toX > fromX+1) {
8319         board[fromY][fromX] = EmptySquare;
8320         board[toY][toX] = king;
8321         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8322         board[fromY][BOARD_RGHT-1] = EmptySquare;
8323     } else if (board[fromY][fromX] == king
8324         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8325                && toY == fromY && toX < fromX-1) {
8326         board[fromY][fromX] = EmptySquare;
8327         board[toY][toX] = king;
8328         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8329         board[fromY][BOARD_LEFT] = EmptySquare;
8330     } else if (fromY == 7 && fromX == 3
8331                && board[fromY][fromX] == BlackKing
8332                && toY == 7 && toX == 5) {
8333         board[fromY][fromX] = EmptySquare;
8334         board[toY][toX] = BlackKing;
8335         board[fromY][7] = EmptySquare;
8336         board[toY][4] = BlackRook;
8337     } else if (fromY == 7 && fromX == 3
8338                && board[fromY][fromX] == BlackKing
8339                && toY == 7 && toX == 1) {
8340         board[fromY][fromX] = EmptySquare;
8341         board[toY][toX] = BlackKing;
8342         board[fromY][0] = EmptySquare;
8343         board[toY][2] = BlackRook;
8344     } else if (board[fromY][fromX] == BlackPawn
8345                && toY < promoRank
8346                && gameInfo.variant != VariantXiangqi
8347                ) {
8348         /* black pawn promotion */
8349         board[toY][toX] = CharToPiece(ToLower(promoChar));
8350         if (board[toY][toX] == EmptySquare) {
8351             board[toY][toX] = BlackQueen;
8352         }
8353         if(gameInfo.variant==VariantBughouse ||
8354            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8355             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8356         board[fromY][fromX] = EmptySquare;
8357     } else if ((fromY == 3)
8358                && (toX != fromX)
8359                && gameInfo.variant != VariantXiangqi
8360                && gameInfo.variant != VariantBerolina
8361                && (board[fromY][fromX] == BlackPawn)
8362                && (board[toY][toX] == EmptySquare)) {
8363         board[fromY][fromX] = EmptySquare;
8364         board[toY][toX] = BlackPawn;
8365         captured = board[toY + 1][toX];
8366         board[toY + 1][toX] = EmptySquare;
8367     } else if ((fromY == 3)
8368                && (toX == fromX)
8369                && gameInfo.variant == VariantBerolina
8370                && (board[fromY][fromX] == BlackPawn)
8371                && (board[toY][toX] == EmptySquare)) {
8372         board[fromY][fromX] = EmptySquare;
8373         board[toY][toX] = BlackPawn;
8374         if(oldEP & EP_BEROLIN_A) {
8375                 captured = board[fromY][fromX-1];
8376                 board[fromY][fromX-1] = EmptySquare;
8377         }else{  captured = board[fromY][fromX+1];
8378                 board[fromY][fromX+1] = EmptySquare;
8379         }
8380     } else {
8381         board[toY][toX] = board[fromY][fromX];
8382         board[fromY][fromX] = EmptySquare;
8383     }
8384
8385     /* [HGM] now we promote for Shogi, if needed */
8386     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8387         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8388   }
8389
8390     if (gameInfo.holdingsWidth != 0) {
8391
8392       /* !!A lot more code needs to be written to support holdings  */
8393       /* [HGM] OK, so I have written it. Holdings are stored in the */
8394       /* penultimate board files, so they are automaticlly stored   */
8395       /* in the game history.                                       */
8396       if (fromY == DROP_RANK) {
8397         /* Delete from holdings, by decreasing count */
8398         /* and erasing image if necessary            */
8399         p = (int) fromX;
8400         if(p < (int) BlackPawn) { /* white drop */
8401              p -= (int)WhitePawn;
8402                  p = PieceToNumber((ChessSquare)p);
8403              if(p >= gameInfo.holdingsSize) p = 0;
8404              if(--board[p][BOARD_WIDTH-2] <= 0)
8405                   board[p][BOARD_WIDTH-1] = EmptySquare;
8406              if((int)board[p][BOARD_WIDTH-2] < 0)
8407                         board[p][BOARD_WIDTH-2] = 0;
8408         } else {                  /* black drop */
8409              p -= (int)BlackPawn;
8410                  p = PieceToNumber((ChessSquare)p);
8411              if(p >= gameInfo.holdingsSize) p = 0;
8412              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8413                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8414              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8415                         board[BOARD_HEIGHT-1-p][1] = 0;
8416         }
8417       }
8418       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8419           && gameInfo.variant != VariantBughouse        ) {
8420         /* [HGM] holdings: Add to holdings, if holdings exist */
8421         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8422                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8423                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8424         }
8425         p = (int) captured;
8426         if (p >= (int) BlackPawn) {
8427           p -= (int)BlackPawn;
8428           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8429                   /* in Shogi restore piece to its original  first */
8430                   captured = (ChessSquare) (DEMOTED captured);
8431                   p = DEMOTED p;
8432           }
8433           p = PieceToNumber((ChessSquare)p);
8434           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8435           board[p][BOARD_WIDTH-2]++;
8436           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8437         } else {
8438           p -= (int)WhitePawn;
8439           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8440                   captured = (ChessSquare) (DEMOTED captured);
8441                   p = DEMOTED p;
8442           }
8443           p = PieceToNumber((ChessSquare)p);
8444           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8445           board[BOARD_HEIGHT-1-p][1]++;
8446           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8447         }
8448       }
8449     } else if (gameInfo.variant == VariantAtomic) {
8450       if (captured != EmptySquare) {
8451         int y, x;
8452         for (y = toY-1; y <= toY+1; y++) {
8453           for (x = toX-1; x <= toX+1; x++) {
8454             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8455                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8456               board[y][x] = EmptySquare;
8457             }
8458           }
8459         }
8460         board[toY][toX] = EmptySquare;
8461       }
8462     }
8463     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8464         /* [HGM] Shogi promotions */
8465         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8466     }
8467
8468     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8469                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8470         // [HGM] superchess: take promotion piece out of holdings
8471         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8472         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8473             if(!--board[k][BOARD_WIDTH-2])
8474                 board[k][BOARD_WIDTH-1] = EmptySquare;
8475         } else {
8476             if(!--board[BOARD_HEIGHT-1-k][1])
8477                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8478         }
8479     }
8480
8481 }
8482
8483 /* Updates forwardMostMove */
8484 void
8485 MakeMove(fromX, fromY, toX, toY, promoChar)
8486      int fromX, fromY, toX, toY;
8487      int promoChar;
8488 {
8489 //    forwardMostMove++; // [HGM] bare: moved downstream
8490
8491     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8492         int timeLeft; static int lastLoadFlag=0; int king, piece;
8493         piece = boards[forwardMostMove][fromY][fromX];
8494         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8495         if(gameInfo.variant == VariantKnightmate)
8496             king += (int) WhiteUnicorn - (int) WhiteKing;
8497         if(forwardMostMove == 0) {
8498             if(blackPlaysFirst)
8499                 fprintf(serverMoves, "%s;", second.tidy);
8500             fprintf(serverMoves, "%s;", first.tidy);
8501             if(!blackPlaysFirst)
8502                 fprintf(serverMoves, "%s;", second.tidy);
8503         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8504         lastLoadFlag = loadFlag;
8505         // print base move
8506         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8507         // print castling suffix
8508         if( toY == fromY && piece == king ) {
8509             if(toX-fromX > 1)
8510                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8511             if(fromX-toX >1)
8512                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8513         }
8514         // e.p. suffix
8515         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8516              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8517              boards[forwardMostMove][toY][toX] == EmptySquare
8518              && fromX != toX )
8519                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8520         // promotion suffix
8521         if(promoChar != NULLCHAR)
8522                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8523         if(!loadFlag) {
8524             fprintf(serverMoves, "/%d/%d",
8525                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8526             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8527             else                      timeLeft = blackTimeRemaining/1000;
8528             fprintf(serverMoves, "/%d", timeLeft);
8529         }
8530         fflush(serverMoves);
8531     }
8532
8533     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8534       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8535                         0, 1);
8536       return;
8537     }
8538     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8539     if (commentList[forwardMostMove+1] != NULL) {
8540         free(commentList[forwardMostMove+1]);
8541         commentList[forwardMostMove+1] = NULL;
8542     }
8543     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8544     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8545     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8546     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8547     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8548     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8549     gameInfo.result = GameUnfinished;
8550     if (gameInfo.resultDetails != NULL) {
8551         free(gameInfo.resultDetails);
8552         gameInfo.resultDetails = NULL;
8553     }
8554     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8555                               moveList[forwardMostMove - 1]);
8556     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8557                              PosFlags(forwardMostMove - 1),
8558                              fromY, fromX, toY, toX, promoChar,
8559                              parseList[forwardMostMove - 1]);
8560     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8561       case MT_NONE:
8562       case MT_STALEMATE:
8563       default:
8564         break;
8565       case MT_CHECK:
8566         if(gameInfo.variant != VariantShogi)
8567             strcat(parseList[forwardMostMove - 1], "+");
8568         break;
8569       case MT_CHECKMATE:
8570       case MT_STAINMATE:
8571         strcat(parseList[forwardMostMove - 1], "#");
8572         break;
8573     }
8574     if (appData.debugMode) {
8575         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8576     }
8577
8578 }
8579
8580 /* Updates currentMove if not pausing */
8581 void
8582 ShowMove(fromX, fromY, toX, toY)
8583 {
8584     int instant = (gameMode == PlayFromGameFile) ?
8585         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8586
8587     if(appData.noGUI) return;
8588
8589     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8590       {
8591         if (!instant)
8592           {
8593             if (forwardMostMove == currentMove + 1)
8594               {
8595 //TODO
8596 //              AnimateMove(boards[forwardMostMove - 1],
8597 //                          fromX, fromY, toX, toY);
8598               }
8599             if (appData.highlightLastMove)
8600               {
8601                 SetHighlights(fromX, fromY, toX, toY);
8602               }
8603           }
8604         currentMove = forwardMostMove;
8605     }
8606
8607     if (instant) return;
8608
8609     DisplayMove(currentMove - 1);
8610     DrawPosition(FALSE, boards[currentMove]);
8611     DisplayBothClocks();
8612     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8613
8614     return;
8615 }
8616
8617 void SendEgtPath(ChessProgramState *cps)
8618 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8619         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8620
8621         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8622
8623         while(*p) {
8624             char c, *q = name+1, *r, *s;
8625
8626             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8627             while(*p && *p != ',') *q++ = *p++;
8628             *q++ = ':'; *q = 0;
8629             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8630                 strcmp(name, ",nalimov:") == 0 ) {
8631                 // take nalimov path from the menu-changeable option first, if it is defined
8632                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8633                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8634             } else
8635             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8636                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8637                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8638                 s = r = StrStr(s, ":") + 1; // beginning of path info
8639                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8640                 c = *r; *r = 0;             // temporarily null-terminate path info
8641                     *--q = 0;               // strip of trailig ':' from name
8642                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8643                 *r = c;
8644                 SendToProgram(buf,cps);     // send egtbpath command for this format
8645             }
8646             if(*p == ',') p++; // read away comma to position for next format name
8647         }
8648 }
8649
8650 void
8651 InitChessProgram(cps, setup)
8652      ChessProgramState *cps;
8653      int setup; /* [HGM] needed to setup FRC opening position */
8654 {
8655     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8656     if (appData.noChessProgram) return;
8657     hintRequested = FALSE;
8658     bookRequested = FALSE;
8659
8660     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8661     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8662     if(cps->memSize) { /* [HGM] memory */
8663         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8664         SendToProgram(buf, cps);
8665     }
8666     SendEgtPath(cps); /* [HGM] EGT */
8667     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8668         sprintf(buf, "cores %d\n", appData.smpCores);
8669         SendToProgram(buf, cps);
8670     }
8671
8672     SendToProgram(cps->initString, cps);
8673     if (gameInfo.variant != VariantNormal &&
8674         gameInfo.variant != VariantLoadable
8675         /* [HGM] also send variant if board size non-standard */
8676         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8677                                             ) {
8678       char *v = VariantName(gameInfo.variant);
8679       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8680         /* [HGM] in protocol 1 we have to assume all variants valid */
8681         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8682         DisplayFatalError(buf, 0, 1);
8683         return;
8684       }
8685
8686       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8687       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8688       if( gameInfo.variant == VariantXiangqi )
8689            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8690       if( gameInfo.variant == VariantShogi )
8691            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8692       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8693            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8694       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8695                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8696            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8697       if( gameInfo.variant == VariantCourier )
8698            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8699       if( gameInfo.variant == VariantSuper )
8700            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8701       if( gameInfo.variant == VariantGreat )
8702            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8703
8704       if(overruled) {
8705            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8706                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8707            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8708            if(StrStr(cps->variants, b) == NULL) {
8709                // specific sized variant not known, check if general sizing allowed
8710                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8711                    if(StrStr(cps->variants, "boardsize") == NULL) {
8712                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8713                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8714                        DisplayFatalError(buf, 0, 1);
8715                        return;
8716                    }
8717                    /* [HGM] here we really should compare with the maximum supported board size */
8718                }
8719            }
8720       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8721       sprintf(buf, "variant %s\n", b);
8722       SendToProgram(buf, cps);
8723     }
8724     currentlyInitializedVariant = gameInfo.variant;
8725
8726     /* [HGM] send opening position in FRC to first engine */
8727     if(setup) {
8728           SendToProgram("force\n", cps);
8729           SendBoard(cps, 0);
8730           /* engine is now in force mode! Set flag to wake it up after first move. */
8731           setboardSpoiledMachineBlack = 1;
8732     }
8733
8734     if (cps->sendICS) {
8735       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8736       SendToProgram(buf, cps);
8737     }
8738     cps->maybeThinking = FALSE;
8739     cps->offeredDraw = 0;
8740     if (!appData.icsActive) {
8741         SendTimeControl(cps, movesPerSession, timeControl,
8742                         timeIncrement, appData.searchDepth,
8743                         searchTime);
8744     }
8745     if (appData.showThinking
8746         // [HGM] thinking: four options require thinking output to be sent
8747         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8748                                 ) {
8749         SendToProgram("post\n", cps);
8750     }
8751     SendToProgram("hard\n", cps);
8752     if (!appData.ponderNextMove) {
8753         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8754            it without being sure what state we are in first.  "hard"
8755            is not a toggle, so that one is OK.
8756          */
8757         SendToProgram("easy\n", cps);
8758     }
8759     if (cps->usePing) {
8760       sprintf(buf, "ping %d\n", ++cps->lastPing);
8761       SendToProgram(buf, cps);
8762     }
8763     cps->initDone = TRUE;
8764 }
8765
8766
8767 void
8768 StartChessProgram(cps)
8769      ChessProgramState *cps;
8770 {
8771     char buf[MSG_SIZ];
8772     int err;
8773
8774     if (appData.noChessProgram) return;
8775     cps->initDone = FALSE;
8776
8777     if (strcmp(cps->host, "localhost") == 0) {
8778         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8779     } else if (*appData.remoteShell == NULLCHAR) {
8780         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8781     } else {
8782         if (*appData.remoteUser == NULLCHAR) {
8783           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8784                     cps->program);
8785         } else {
8786           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8787                     cps->host, appData.remoteUser, cps->program);
8788         }
8789         err = StartChildProcess(buf, "", &cps->pr);
8790     }
8791
8792     if (err != 0) {
8793         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8794         DisplayFatalError(buf, err, 1);
8795         cps->pr = NoProc;
8796         cps->isr = NULL;
8797         return;
8798     }
8799
8800     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8801     if (cps->protocolVersion > 1) {
8802       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8803       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8804       cps->comboCnt = 0;  //                and values of combo boxes
8805       SendToProgram(buf, cps);
8806     } else {
8807       SendToProgram("xboard\n", cps);
8808     }
8809 }
8810
8811
8812 void
8813 TwoMachinesEventIfReady P((void))
8814 {
8815   if (first.lastPing != first.lastPong) {
8816     DisplayMessage("", _("Waiting for first chess program"));
8817     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8818     return;
8819   }
8820   if (second.lastPing != second.lastPong) {
8821     DisplayMessage("", _("Waiting for second chess program"));
8822     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8823     return;
8824   }
8825   ThawUI();
8826   TwoMachinesEvent();
8827 }
8828
8829 void
8830 NextMatchGame P((void))
8831 {
8832     int index; /* [HGM] autoinc: step load index during match */
8833     Reset(FALSE, TRUE);
8834     if (*appData.loadGameFile != NULLCHAR) {
8835         index = appData.loadGameIndex;
8836         if(index < 0) { // [HGM] autoinc
8837             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8838             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8839         }
8840         LoadGameFromFile(appData.loadGameFile,
8841                          index,
8842                          appData.loadGameFile, FALSE);
8843     } else if (*appData.loadPositionFile != NULLCHAR) {
8844         index = appData.loadPositionIndex;
8845         if(index < 0) { // [HGM] autoinc
8846             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8847             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8848         }
8849         LoadPositionFromFile(appData.loadPositionFile,
8850                              index,
8851                              appData.loadPositionFile);
8852     }
8853     TwoMachinesEventIfReady();
8854 }
8855
8856 void UserAdjudicationEvent( int result )
8857 {
8858     ChessMove gameResult = GameIsDrawn;
8859
8860     if( result > 0 ) {
8861         gameResult = WhiteWins;
8862     }
8863     else if( result < 0 ) {
8864         gameResult = BlackWins;
8865     }
8866
8867     if( gameMode == TwoMachinesPlay ) {
8868         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8869     }
8870 }
8871
8872
8873 // [HGM] save: calculate checksum of game to make games easily identifiable
8874 int StringCheckSum(char *s)
8875 {
8876         int i = 0;
8877         if(s==NULL) return 0;
8878         while(*s) i = i*259 + *s++;
8879         return i;
8880 }
8881
8882 int GameCheckSum()
8883 {
8884         int i, sum=0;
8885         for(i=backwardMostMove; i<forwardMostMove; i++) {
8886                 sum += pvInfoList[i].depth;
8887                 sum += StringCheckSum(parseList[i]);
8888                 sum += StringCheckSum(commentList[i]);
8889                 sum *= 261;
8890         }
8891         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8892         return sum + StringCheckSum(commentList[i]);
8893 } // end of save patch
8894
8895 void
8896 GameEnds(result, resultDetails, whosays)
8897      ChessMove result;
8898      char *resultDetails;
8899      int whosays;
8900 {
8901     GameMode nextGameMode;
8902     int isIcsGame;
8903     char buf[MSG_SIZ];
8904
8905     if(endingGame) return; /* [HGM] crash: forbid recursion */
8906     endingGame = 1;
8907
8908     if (appData.debugMode) {
8909       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8910               result, resultDetails ? resultDetails : "(null)", whosays);
8911     }
8912
8913     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8914         /* If we are playing on ICS, the server decides when the
8915            game is over, but the engine can offer to draw, claim
8916            a draw, or resign.
8917          */
8918 #if ZIPPY
8919         if (appData.zippyPlay && first.initDone) {
8920             if (result == GameIsDrawn) {
8921                 /* In case draw still needs to be claimed */
8922                 SendToICS(ics_prefix);
8923                 SendToICS("draw\n");
8924             } else if (StrCaseStr(resultDetails, "resign")) {
8925                 SendToICS(ics_prefix);
8926                 SendToICS("resign\n");
8927             }
8928         }
8929 #endif
8930         endingGame = 0; /* [HGM] crash */
8931         return;
8932     }
8933
8934     /* If we're loading the game from a file, stop */
8935     if (whosays == GE_FILE) {
8936       (void) StopLoadGameTimer();
8937       gameFileFP = NULL;
8938     }
8939
8940     /* Cancel draw offers */
8941     first.offeredDraw = second.offeredDraw = 0;
8942
8943     /* If this is an ICS game, only ICS can really say it's done;
8944        if not, anyone can. */
8945     isIcsGame = (gameMode == IcsPlayingWhite ||
8946                  gameMode == IcsPlayingBlack ||
8947                  gameMode == IcsObserving    ||
8948                  gameMode == IcsExamining);
8949
8950     if (!isIcsGame || whosays == GE_ICS) {
8951         /* OK -- not an ICS game, or ICS said it was done */
8952         StopClocks();
8953         if (!isIcsGame && !appData.noChessProgram)
8954           SetUserThinkingEnables();
8955
8956         /* [HGM] if a machine claims the game end we verify this claim */
8957         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8958             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8959                 char claimer;
8960                 ChessMove trueResult = (ChessMove) -1;
8961
8962                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8963                                             first.twoMachinesColor[0] :
8964                                             second.twoMachinesColor[0] ;
8965
8966                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8967                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8968                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8969                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8970                 } else
8971                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8972                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8973                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8974                 } else
8975                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8976                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8977                 }
8978
8979                 // now verify win claims, but not in drop games, as we don't understand those yet
8980                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8981                                                  || gameInfo.variant == VariantGreat) &&
8982                     (result == WhiteWins && claimer == 'w' ||
8983                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8984                       if (appData.debugMode) {
8985                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8986                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8987                       }
8988                       if(result != trueResult) {
8989                               sprintf(buf, "False win claim: '%s'", resultDetails);
8990                               result = claimer == 'w' ? BlackWins : WhiteWins;
8991                               resultDetails = buf;
8992                       }
8993                 } else
8994                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8995                     && (forwardMostMove <= backwardMostMove ||
8996                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8997                         (claimer=='b')==(forwardMostMove&1))
8998                                                                                   ) {
8999                       /* [HGM] verify: draws that were not flagged are false claims */
9000                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9001                       result = claimer == 'w' ? BlackWins : WhiteWins;
9002                       resultDetails = buf;
9003                 }
9004                 /* (Claiming a loss is accepted no questions asked!) */
9005             }
9006
9007             /* [HGM] bare: don't allow bare King to win */
9008             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9009                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9010                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9011                && result != GameIsDrawn)
9012             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9013                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9014                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9015                         if(p >= 0 && p <= (int)WhiteKing) k++;
9016                 }
9017                 if (appData.debugMode) {
9018                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9019                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9020                 }
9021                 if(k <= 1) {
9022                         result = GameIsDrawn;
9023                         sprintf(buf, "%s but bare king", resultDetails);
9024                         resultDetails = buf;
9025                 }
9026             }
9027         }
9028
9029         if(serverMoves != NULL && !loadFlag) { char c = '=';
9030             if(result==WhiteWins) c = '+';
9031             if(result==BlackWins) c = '-';
9032             if(resultDetails != NULL)
9033                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9034         }
9035         if (resultDetails != NULL) {
9036             gameInfo.result = result;
9037             gameInfo.resultDetails = StrSave(resultDetails);
9038
9039             /* display last move only if game was not loaded from file */
9040             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9041                 DisplayMove(currentMove - 1);
9042
9043             if (forwardMostMove != 0) {
9044                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9045                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9046                                                                 ) {
9047                     if (*appData.saveGameFile != NULLCHAR) {
9048                         SaveGameToFile(appData.saveGameFile, TRUE);
9049                     } else if (appData.autoSaveGames) {
9050                         AutoSaveGame();
9051                     }
9052                     if (*appData.savePositionFile != NULLCHAR) {
9053                         SavePositionToFile(appData.savePositionFile);
9054                     }
9055                 }
9056             }
9057
9058             /* Tell program how game ended in case it is learning */
9059             /* [HGM] Moved this to after saving the PGN, just in case */
9060             /* engine died and we got here through time loss. In that */
9061             /* case we will get a fatal error writing the pipe, which */
9062             /* would otherwise lose us the PGN.                       */
9063             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9064             /* output during GameEnds should never be fatal anymore   */
9065             if (gameMode == MachinePlaysWhite ||
9066                 gameMode == MachinePlaysBlack ||
9067                 gameMode == TwoMachinesPlay ||
9068                 gameMode == IcsPlayingWhite ||
9069                 gameMode == IcsPlayingBlack ||
9070                 gameMode == BeginningOfGame) {
9071                 char buf[MSG_SIZ];
9072                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9073                         resultDetails);
9074                 if (first.pr != NoProc) {
9075                     SendToProgram(buf, &first);
9076                 }
9077                 if (second.pr != NoProc &&
9078                     gameMode == TwoMachinesPlay) {
9079                     SendToProgram(buf, &second);
9080                 }
9081             }
9082         }
9083
9084         if (appData.icsActive) {
9085             if (appData.quietPlay &&
9086                 (gameMode == IcsPlayingWhite ||
9087                  gameMode == IcsPlayingBlack)) {
9088                 SendToICS(ics_prefix);
9089                 SendToICS("set shout 1\n");
9090             }
9091             nextGameMode = IcsIdle;
9092             ics_user_moved = FALSE;
9093             /* clean up premove.  It's ugly when the game has ended and the
9094              * premove highlights are still on the board.
9095              */
9096             if (gotPremove) {
9097               gotPremove = FALSE;
9098               ClearPremoveHighlights();
9099               DrawPosition(FALSE, boards[currentMove]);
9100             }
9101             if (whosays == GE_ICS) {
9102                 switch (result) {
9103                 case WhiteWins:
9104                     if (gameMode == IcsPlayingWhite)
9105                         PlayIcsWinSound();
9106                     else if(gameMode == IcsPlayingBlack)
9107                         PlayIcsLossSound();
9108                     break;
9109                 case BlackWins:
9110                     if (gameMode == IcsPlayingBlack)
9111                         PlayIcsWinSound();
9112                     else if(gameMode == IcsPlayingWhite)
9113                         PlayIcsLossSound();
9114                     break;
9115                 case GameIsDrawn:
9116                     PlayIcsDrawSound();
9117                     break;
9118                 default:
9119                     PlayIcsUnfinishedSound();
9120                 }
9121             }
9122         } else if (gameMode == EditGame ||
9123                    gameMode == PlayFromGameFile ||
9124                    gameMode == AnalyzeMode ||
9125                    gameMode == AnalyzeFile) {
9126             nextGameMode = gameMode;
9127         } else {
9128             nextGameMode = EndOfGame;
9129         }
9130         pausing = FALSE;
9131         ModeHighlight();
9132     } else {
9133         nextGameMode = gameMode;
9134     }
9135
9136     if (appData.noChessProgram) {
9137         gameMode = nextGameMode;
9138         ModeHighlight();
9139         endingGame = 0; /* [HGM] crash */
9140         return;
9141     }
9142
9143     if (first.reuse) {
9144         /* Put first chess program into idle state */
9145         if (first.pr != NoProc &&
9146             (gameMode == MachinePlaysWhite ||
9147              gameMode == MachinePlaysBlack ||
9148              gameMode == TwoMachinesPlay ||
9149              gameMode == IcsPlayingWhite ||
9150              gameMode == IcsPlayingBlack ||
9151              gameMode == BeginningOfGame)) {
9152             SendToProgram("force\n", &first);
9153             if (first.usePing) {
9154               char buf[MSG_SIZ];
9155               sprintf(buf, "ping %d\n", ++first.lastPing);
9156               SendToProgram(buf, &first);
9157             }
9158         }
9159     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9160         /* Kill off first chess program */
9161         if (first.isr != NULL)
9162           RemoveInputSource(first.isr);
9163         first.isr = NULL;
9164
9165         if (first.pr != NoProc) {
9166             ExitAnalyzeMode();
9167             DoSleep( appData.delayBeforeQuit );
9168             SendToProgram("quit\n", &first);
9169             DoSleep( appData.delayAfterQuit );
9170             DestroyChildProcess(first.pr, first.useSigterm);
9171         }
9172         first.pr = NoProc;
9173     }
9174     if (second.reuse) {
9175         /* Put second chess program into idle state */
9176         if (second.pr != NoProc &&
9177             gameMode == TwoMachinesPlay) {
9178             SendToProgram("force\n", &second);
9179             if (second.usePing) {
9180               char buf[MSG_SIZ];
9181               sprintf(buf, "ping %d\n", ++second.lastPing);
9182               SendToProgram(buf, &second);
9183             }
9184         }
9185     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9186         /* Kill off second chess program */
9187         if (second.isr != NULL)
9188           RemoveInputSource(second.isr);
9189         second.isr = NULL;
9190
9191         if (second.pr != NoProc) {
9192             DoSleep( appData.delayBeforeQuit );
9193             SendToProgram("quit\n", &second);
9194             DoSleep( appData.delayAfterQuit );
9195             DestroyChildProcess(second.pr, second.useSigterm);
9196         }
9197         second.pr = NoProc;
9198     }
9199
9200     if (matchMode && gameMode == TwoMachinesPlay) {
9201         switch (result) {
9202         case WhiteWins:
9203           if (first.twoMachinesColor[0] == 'w') {
9204             first.matchWins++;
9205           } else {
9206             second.matchWins++;
9207           }
9208           break;
9209         case BlackWins:
9210           if (first.twoMachinesColor[0] == 'b') {
9211             first.matchWins++;
9212           } else {
9213             second.matchWins++;
9214           }
9215           break;
9216         default:
9217           break;
9218         }
9219         if (matchGame < appData.matchGames) {
9220             char *tmp;
9221             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9222                 tmp = first.twoMachinesColor;
9223                 first.twoMachinesColor = second.twoMachinesColor;
9224                 second.twoMachinesColor = tmp;
9225             }
9226             gameMode = nextGameMode;
9227             matchGame++;
9228             if(appData.matchPause>10000 || appData.matchPause<10)
9229                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9230             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9231             endingGame = 0; /* [HGM] crash */
9232             return;
9233         } else {
9234             char buf[MSG_SIZ];
9235             gameMode = nextGameMode;
9236             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9237                     first.tidy, second.tidy,
9238                     first.matchWins, second.matchWins,
9239                     appData.matchGames - (first.matchWins + second.matchWins));
9240             DisplayFatalError(buf, 0, 0);
9241         }
9242     }
9243     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9244         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9245       ExitAnalyzeMode();
9246     gameMode = nextGameMode;
9247     ModeHighlight();
9248     endingGame = 0;  /* [HGM] crash */
9249 }
9250
9251 /* Assumes program was just initialized (initString sent).
9252    Leaves program in force mode. */
9253 void
9254 FeedMovesToProgram(cps, upto)
9255      ChessProgramState *cps;
9256      int upto;
9257 {
9258     int i;
9259
9260     if (appData.debugMode)
9261       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9262               startedFromSetupPosition ? "position and " : "",
9263               backwardMostMove, upto, cps->which);
9264     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9265         // [HGM] variantswitch: make engine aware of new variant
9266         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9267                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9268         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9269         SendToProgram(buf, cps);
9270         currentlyInitializedVariant = gameInfo.variant;
9271     }
9272     SendToProgram("force\n", cps);
9273     if (startedFromSetupPosition) {
9274         SendBoard(cps, backwardMostMove);
9275     if (appData.debugMode) {
9276         fprintf(debugFP, "feedMoves\n");
9277     }
9278     }
9279     for (i = backwardMostMove; i < upto; i++) {
9280         SendMoveToProgram(i, cps);
9281     }
9282 }
9283
9284
9285 void
9286 ResurrectChessProgram()
9287 {
9288      /* The chess program may have exited.
9289         If so, restart it and feed it all the moves made so far. */
9290
9291     if (appData.noChessProgram || first.pr != NoProc) return;
9292
9293     StartChessProgram(&first);
9294     InitChessProgram(&first, FALSE);
9295     FeedMovesToProgram(&first, currentMove);
9296
9297     if (!first.sendTime) {
9298         /* can't tell gnuchess what its clock should read,
9299            so we bow to its notion. */
9300         ResetClocks();
9301         timeRemaining[0][currentMove] = whiteTimeRemaining;
9302         timeRemaining[1][currentMove] = blackTimeRemaining;
9303     }
9304
9305     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9306                 appData.icsEngineAnalyze) && first.analysisSupport) {
9307       SendToProgram("analyze\n", &first);
9308       first.analyzing = TRUE;
9309     }
9310 }
9311
9312 /*
9313  * Button procedures
9314  */
9315 void
9316 Reset(redraw, init)
9317      int redraw, init;
9318 {
9319     int i;
9320
9321     if (appData.debugMode) {
9322         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9323                 redraw, init, gameMode);
9324     }
9325     CleanupTail(); // [HGM] vari: delete any stored variations
9326     pausing = pauseExamInvalid = FALSE;
9327     startedFromSetupPosition = blackPlaysFirst = FALSE;
9328     firstMove = TRUE;
9329     whiteFlag = blackFlag = FALSE;
9330     userOfferedDraw = FALSE;
9331     hintRequested = bookRequested = FALSE;
9332     first.maybeThinking = FALSE;
9333     second.maybeThinking = FALSE;
9334     first.bookSuspend = FALSE; // [HGM] book
9335     second.bookSuspend = FALSE;
9336     thinkOutput[0] = NULLCHAR;
9337     lastHint[0] = NULLCHAR;
9338     ClearGameInfo(&gameInfo);
9339     gameInfo.variant = StringToVariant(appData.variant);
9340     ics_user_moved = ics_clock_paused = FALSE;
9341     ics_getting_history = H_FALSE;
9342     ics_gamenum = -1;
9343     white_holding[0] = black_holding[0] = NULLCHAR;
9344     ClearProgramStats();
9345     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9346
9347     ResetFrontEnd();
9348     ClearHighlights();
9349     flipView = appData.flipView;
9350     ClearPremoveHighlights();
9351     gotPremove = FALSE;
9352     alarmSounded = FALSE;
9353
9354     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9355     if(appData.serverMovesName != NULL) {
9356         /* [HGM] prepare to make moves file for broadcasting */
9357         clock_t t = clock();
9358         if(serverMoves != NULL) fclose(serverMoves);
9359         serverMoves = fopen(appData.serverMovesName, "r");
9360         if(serverMoves != NULL) {
9361             fclose(serverMoves);
9362             /* delay 15 sec before overwriting, so all clients can see end */
9363             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9364         }
9365         serverMoves = fopen(appData.serverMovesName, "w");
9366     }
9367
9368     ExitAnalyzeMode();
9369     gameMode = BeginningOfGame;
9370     ModeHighlight();
9371
9372     if(appData.icsActive) gameInfo.variant = VariantNormal;
9373     currentMove = forwardMostMove = backwardMostMove = 0;
9374     InitPosition(redraw);
9375     for (i = 0; i < MAX_MOVES; i++) {
9376         if (commentList[i] != NULL) {
9377             free(commentList[i]);
9378             commentList[i] = NULL;
9379         }
9380     }
9381
9382     ResetClocks();
9383     timeRemaining[0][0] = whiteTimeRemaining;
9384     timeRemaining[1][0] = blackTimeRemaining;
9385     if (first.pr == NULL) {
9386         StartChessProgram(&first);
9387     }
9388     if (init) {
9389             InitChessProgram(&first, startedFromSetupPosition);
9390     }
9391
9392     DisplayTitle("");
9393     DisplayMessage("", "");
9394     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9395     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9396     return;
9397 }
9398
9399 void
9400 AutoPlayGameLoop()
9401 {
9402     for (;;) {
9403         if (!AutoPlayOneMove())
9404           return;
9405         if (matchMode || appData.timeDelay == 0)
9406           continue;
9407         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9408           return;
9409         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9410         break;
9411     }
9412 }
9413
9414
9415 int
9416 AutoPlayOneMove()
9417 {
9418     int fromX, fromY, toX, toY;
9419
9420     if (appData.debugMode) {
9421       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9422     }
9423
9424     if (gameMode != PlayFromGameFile)
9425       return FALSE;
9426
9427     if (currentMove >= forwardMostMove) {
9428       gameMode = EditGame;
9429       ModeHighlight();
9430
9431       /* [AS] Clear current move marker at the end of a game */
9432       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9433
9434       return FALSE;
9435     }
9436
9437     toX = moveList[currentMove][2] - AAA;
9438     toY = moveList[currentMove][3] - ONE;
9439
9440     if (moveList[currentMove][1] == '@') {
9441         if (appData.highlightLastMove) {
9442             SetHighlights(-1, -1, toX, toY);
9443         }
9444     } else {
9445         fromX = moveList[currentMove][0] - AAA;
9446         fromY = moveList[currentMove][1] - ONE;
9447
9448         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9449
9450         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9451
9452         if (appData.highlightLastMove) {
9453             SetHighlights(fromX, fromY, toX, toY);
9454         }
9455     }
9456     DisplayMove(currentMove);
9457     SendMoveToProgram(currentMove++, &first);
9458     DisplayBothClocks();
9459     DrawPosition(FALSE, boards[currentMove]);
9460     // [HGM] PV info: always display, routine tests if empty
9461     DisplayComment(currentMove - 1, commentList[currentMove]);
9462     return TRUE;
9463 }
9464
9465
9466 int
9467 LoadGameOneMove(readAhead)
9468      ChessMove readAhead;
9469 {
9470     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9471     char promoChar = NULLCHAR;
9472     ChessMove moveType;
9473     char move[MSG_SIZ];
9474     char *p, *q;
9475
9476     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9477         gameMode != AnalyzeMode && gameMode != Training) {
9478         gameFileFP = NULL;
9479         return FALSE;
9480     }
9481
9482     yyboardindex = forwardMostMove;
9483     if (readAhead != (ChessMove)0) {
9484       moveType = readAhead;
9485     } else {
9486       if (gameFileFP == NULL)
9487           return FALSE;
9488       moveType = (ChessMove) yylex();
9489     }
9490
9491     done = FALSE;
9492     switch (moveType) {
9493       case Comment:
9494         if (appData.debugMode)
9495           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9496         p = yy_text;
9497
9498         /* append the comment but don't display it */
9499         AppendComment(currentMove, p, FALSE);
9500         return TRUE;
9501
9502       case WhiteCapturesEnPassant:
9503       case BlackCapturesEnPassant:
9504       case WhitePromotionChancellor:
9505       case BlackPromotionChancellor:
9506       case WhitePromotionArchbishop:
9507       case BlackPromotionArchbishop:
9508       case WhitePromotionCentaur:
9509       case BlackPromotionCentaur:
9510       case WhitePromotionQueen:
9511       case BlackPromotionQueen:
9512       case WhitePromotionRook:
9513       case BlackPromotionRook:
9514       case WhitePromotionBishop:
9515       case BlackPromotionBishop:
9516       case WhitePromotionKnight:
9517       case BlackPromotionKnight:
9518       case WhitePromotionKing:
9519       case BlackPromotionKing:
9520       case NormalMove:
9521       case WhiteKingSideCastle:
9522       case WhiteQueenSideCastle:
9523       case BlackKingSideCastle:
9524       case BlackQueenSideCastle:
9525       case WhiteKingSideCastleWild:
9526       case WhiteQueenSideCastleWild:
9527       case BlackKingSideCastleWild:
9528       case BlackQueenSideCastleWild:
9529       /* PUSH Fabien */
9530       case WhiteHSideCastleFR:
9531       case WhiteASideCastleFR:
9532       case BlackHSideCastleFR:
9533       case BlackASideCastleFR:
9534       /* POP Fabien */
9535         if (appData.debugMode)
9536           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9537         fromX = currentMoveString[0] - AAA;
9538         fromY = currentMoveString[1] - ONE;
9539         toX = currentMoveString[2] - AAA;
9540         toY = currentMoveString[3] - ONE;
9541         promoChar = currentMoveString[4];
9542         break;
9543
9544       case WhiteDrop:
9545       case BlackDrop:
9546         if (appData.debugMode)
9547           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9548         fromX = moveType == WhiteDrop ?
9549           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9550         (int) CharToPiece(ToLower(currentMoveString[0]));
9551         fromY = DROP_RANK;
9552         toX = currentMoveString[2] - AAA;
9553         toY = currentMoveString[3] - ONE;
9554         break;
9555
9556       case WhiteWins:
9557       case BlackWins:
9558       case GameIsDrawn:
9559       case GameUnfinished:
9560         if (appData.debugMode)
9561           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9562         p = strchr(yy_text, '{');
9563         if (p == NULL) p = strchr(yy_text, '(');
9564         if (p == NULL) {
9565             p = yy_text;
9566             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9567         } else {
9568             q = strchr(p, *p == '{' ? '}' : ')');
9569             if (q != NULL) *q = NULLCHAR;
9570             p++;
9571         }
9572         GameEnds(moveType, p, GE_FILE);
9573         done = TRUE;
9574         if (cmailMsgLoaded) {
9575             ClearHighlights();
9576             flipView = WhiteOnMove(currentMove);
9577             if (moveType == GameUnfinished) flipView = !flipView;
9578             if (appData.debugMode)
9579               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9580         }
9581         break;
9582
9583       case (ChessMove) 0:       /* end of file */
9584         if (appData.debugMode)
9585           fprintf(debugFP, "Parser hit end of file\n");
9586         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9587           case MT_NONE:
9588           case MT_CHECK:
9589             break;
9590           case MT_CHECKMATE:
9591           case MT_STAINMATE:
9592             if (WhiteOnMove(currentMove)) {
9593                 GameEnds(BlackWins, "Black mates", GE_FILE);
9594             } else {
9595                 GameEnds(WhiteWins, "White mates", GE_FILE);
9596             }
9597             break;
9598           case MT_STALEMATE:
9599             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9600             break;
9601         }
9602         done = TRUE;
9603         break;
9604
9605       case MoveNumberOne:
9606         if (lastLoadGameStart == GNUChessGame) {
9607             /* GNUChessGames have numbers, but they aren't move numbers */
9608             if (appData.debugMode)
9609               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9610                       yy_text, (int) moveType);
9611             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9612         }
9613         /* else fall thru */
9614
9615       case XBoardGame:
9616       case GNUChessGame:
9617       case PGNTag:
9618         /* Reached start of next game in file */
9619         if (appData.debugMode)
9620           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9621         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9622           case MT_NONE:
9623           case MT_CHECK:
9624             break;
9625           case MT_CHECKMATE:
9626           case MT_STAINMATE:
9627             if (WhiteOnMove(currentMove)) {
9628                 GameEnds(BlackWins, "Black mates", GE_FILE);
9629             } else {
9630                 GameEnds(WhiteWins, "White mates", GE_FILE);
9631             }
9632             break;
9633           case MT_STALEMATE:
9634             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9635             break;
9636         }
9637         done = TRUE;
9638         break;
9639
9640       case PositionDiagram:     /* should not happen; ignore */
9641       case ElapsedTime:         /* ignore */
9642       case NAG:                 /* ignore */
9643         if (appData.debugMode)
9644           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9645                   yy_text, (int) moveType);
9646         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9647
9648       case IllegalMove:
9649         if (appData.testLegality) {
9650             if (appData.debugMode)
9651               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9652             sprintf(move, _("Illegal move: %d.%s%s"),
9653                     (forwardMostMove / 2) + 1,
9654                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9655             DisplayError(move, 0);
9656             done = TRUE;
9657         } else {
9658             if (appData.debugMode)
9659               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9660                       yy_text, currentMoveString);
9661             fromX = currentMoveString[0] - AAA;
9662             fromY = currentMoveString[1] - ONE;
9663             toX = currentMoveString[2] - AAA;
9664             toY = currentMoveString[3] - ONE;
9665             promoChar = currentMoveString[4];
9666         }
9667         break;
9668
9669       case AmbiguousMove:
9670         if (appData.debugMode)
9671           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9672         sprintf(move, _("Ambiguous move: %d.%s%s"),
9673                 (forwardMostMove / 2) + 1,
9674                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9675         DisplayError(move, 0);
9676         done = TRUE;
9677         break;
9678
9679       default:
9680       case ImpossibleMove:
9681         if (appData.debugMode)
9682           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9683         sprintf(move, _("Illegal move: %d.%s%s"),
9684                 (forwardMostMove / 2) + 1,
9685                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9686         DisplayError(move, 0);
9687         done = TRUE;
9688         break;
9689     }
9690
9691     if (done) {
9692         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9693             DrawPosition(FALSE, boards[currentMove]);
9694             DisplayBothClocks();
9695             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9696               DisplayComment(currentMove - 1, commentList[currentMove]);
9697         }
9698         (void) StopLoadGameTimer();
9699         gameFileFP = NULL;
9700         cmailOldMove = forwardMostMove;
9701         return FALSE;
9702     } else {
9703         /* currentMoveString is set as a side-effect of yylex */
9704         strcat(currentMoveString, "\n");
9705         strcpy(moveList[forwardMostMove], currentMoveString);
9706
9707         thinkOutput[0] = NULLCHAR;
9708         MakeMove(fromX, fromY, toX, toY, promoChar);
9709         currentMove = forwardMostMove;
9710         return TRUE;
9711     }
9712 }
9713
9714 /* Load the nth game from the given file */
9715 int
9716 LoadGameFromFile(filename, n, title, useList)
9717      char *filename;
9718      int n;
9719      char *title;
9720      /*Boolean*/ int useList;
9721 {
9722     FILE *f;
9723     char buf[MSG_SIZ];
9724
9725     if (strcmp(filename, "-") == 0) {
9726         f = stdin;
9727         title = "stdin";
9728     } else {
9729         f = fopen(filename, "rb");
9730         if (f == NULL) {
9731           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9732             DisplayError(buf, errno);
9733             return FALSE;
9734         }
9735     }
9736     if (fseek(f, 0, 0) == -1) {
9737         /* f is not seekable; probably a pipe */
9738         useList = FALSE;
9739     }
9740     if (useList && n == 0) {
9741         int error = GameListBuild(f);
9742         if (error) {
9743             DisplayError(_("Cannot build game list"), error);
9744         } else if (!ListEmpty(&gameList) &&
9745                    ((ListGame *) gameList.tailPred)->number > 1) {
9746           // TODO convert to GTK
9747           //        GameListPopUp(f, title);
9748             return TRUE;
9749         }
9750         GameListDestroy();
9751         n = 1;
9752     }
9753     if (n == 0) n = 1;
9754     return LoadGame(f, n, title, FALSE);
9755 }
9756
9757
9758 void
9759 MakeRegisteredMove()
9760 {
9761     int fromX, fromY, toX, toY;
9762     char promoChar;
9763     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9764         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9765           case CMAIL_MOVE:
9766           case CMAIL_DRAW:
9767             if (appData.debugMode)
9768               fprintf(debugFP, "Restoring %s for game %d\n",
9769                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9770
9771             thinkOutput[0] = NULLCHAR;
9772             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9773             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9774             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9775             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9776             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9777             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9778             MakeMove(fromX, fromY, toX, toY, promoChar);
9779             ShowMove(fromX, fromY, toX, toY);
9780             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9781               case MT_NONE:
9782               case MT_CHECK:
9783                 break;
9784
9785               case MT_CHECKMATE:
9786               case MT_STAINMATE:
9787                 if (WhiteOnMove(currentMove)) {
9788                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9789                 } else {
9790                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9791                 }
9792                 break;
9793
9794               case MT_STALEMATE:
9795                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9796                 break;
9797             }
9798
9799             break;
9800
9801           case CMAIL_RESIGN:
9802             if (WhiteOnMove(currentMove)) {
9803                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9804             } else {
9805                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9806             }
9807             break;
9808
9809           case CMAIL_ACCEPT:
9810             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9811             break;
9812
9813           default:
9814             break;
9815         }
9816     }
9817
9818     return;
9819 }
9820
9821 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9822 int
9823 CmailLoadGame(f, gameNumber, title, useList)
9824      FILE *f;
9825      int gameNumber;
9826      char *title;
9827      int useList;
9828 {
9829     int retVal;
9830
9831     if (gameNumber > nCmailGames) {
9832         DisplayError(_("No more games in this message"), 0);
9833         return FALSE;
9834     }
9835     if (f == lastLoadGameFP) {
9836         int offset = gameNumber - lastLoadGameNumber;
9837         if (offset == 0) {
9838             cmailMsg[0] = NULLCHAR;
9839             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9840                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9841                 nCmailMovesRegistered--;
9842             }
9843             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9844             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9845                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9846             }
9847         } else {
9848             if (! RegisterMove()) return FALSE;
9849         }
9850     }
9851
9852     retVal = LoadGame(f, gameNumber, title, useList);
9853
9854     /* Make move registered during previous look at this game, if any */
9855     MakeRegisteredMove();
9856
9857     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9858         commentList[currentMove]
9859           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9860         DisplayComment(currentMove - 1, commentList[currentMove]);
9861     }
9862
9863     return retVal;
9864 }
9865
9866 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9867 int
9868 ReloadGame(offset)
9869      int offset;
9870 {
9871     int gameNumber = lastLoadGameNumber + offset;
9872     if (lastLoadGameFP == NULL) {
9873         DisplayError(_("No game has been loaded yet"), 0);
9874         return FALSE;
9875     }
9876     if (gameNumber <= 0) {
9877         DisplayError(_("Can't back up any further"), 0);
9878         return FALSE;
9879     }
9880     if (cmailMsgLoaded) {
9881         return CmailLoadGame(lastLoadGameFP, gameNumber,
9882                              lastLoadGameTitle, lastLoadGameUseList);
9883     } else {
9884         return LoadGame(lastLoadGameFP, gameNumber,
9885                         lastLoadGameTitle, lastLoadGameUseList);
9886     }
9887 }
9888
9889
9890
9891 /* Load the nth game from open file f */
9892 int
9893 LoadGame(f, gameNumber, title, useList)
9894      FILE *f;
9895      int gameNumber;
9896      char *title;
9897      int useList;
9898 {
9899     ChessMove cm;
9900     char buf[MSG_SIZ];
9901     int gn = gameNumber;
9902     ListGame *lg = NULL;
9903     int numPGNTags = 0;
9904     int err;
9905     GameMode oldGameMode;
9906     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9907
9908     if (appData.debugMode)
9909         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9910
9911     if (gameMode == Training )
9912         SetTrainingModeOff();
9913
9914     oldGameMode = gameMode;
9915     if (gameMode != BeginningOfGame) 
9916       {
9917         Reset(FALSE, TRUE);
9918       };
9919
9920     gameFileFP = f;
9921     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9922       {
9923         fclose(lastLoadGameFP);
9924       };
9925
9926     if (useList) 
9927       {
9928         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9929         
9930         if (lg) 
9931           {
9932             fseek(f, lg->offset, 0);
9933             GameListHighlight(gameNumber);
9934             gn = 1;
9935           }
9936         else 
9937           {
9938             DisplayError(_("Game number out of range"), 0);
9939             return FALSE;
9940           };
9941       } 
9942     else 
9943       {
9944         GameListDestroy();
9945         if (fseek(f, 0, 0) == -1) 
9946           {
9947             if (f == lastLoadGameFP ?
9948                 gameNumber == lastLoadGameNumber + 1 :
9949                 gameNumber == 1) 
9950               {
9951                 gn = 1;
9952               } 
9953             else 
9954               {
9955                 DisplayError(_("Can't seek on game file"), 0);
9956                 return FALSE;
9957               };
9958           };
9959       };
9960
9961     lastLoadGameFP      = f;
9962     lastLoadGameNumber  = gameNumber;
9963     strcpy(lastLoadGameTitle, title);
9964     lastLoadGameUseList = useList;
9965
9966     yynewfile(f);
9967
9968     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9969       {
9970         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9971                  lg->gameInfo.black);
9972         DisplayTitle(buf);
9973       } 
9974     else if (*title != NULLCHAR) 
9975       {
9976         if (gameNumber > 1) 
9977           {
9978             sprintf(buf, "%s %d", title, gameNumber);
9979             DisplayTitle(buf);
9980           } 
9981         else 
9982           {
9983             DisplayTitle(title);
9984           };
9985       };
9986
9987     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9988       {
9989         gameMode = PlayFromGameFile;
9990         ModeHighlight();
9991       };
9992
9993     currentMove = forwardMostMove = backwardMostMove = 0;
9994     CopyBoard(boards[0], initialPosition);
9995     StopClocks();
9996
9997     /*
9998      * Skip the first gn-1 games in the file.
9999      * Also skip over anything that precedes an identifiable
10000      * start of game marker, to avoid being confused by
10001      * garbage at the start of the file.  Currently
10002      * recognized start of game markers are the move number "1",
10003      * the pattern "gnuchess .* game", the pattern
10004      * "^[#;%] [^ ]* game file", and a PGN tag block.
10005      * A game that starts with one of the latter two patterns
10006      * will also have a move number 1, possibly
10007      * following a position diagram.
10008      * 5-4-02: Let's try being more lenient and allowing a game to
10009      * start with an unnumbered move.  Does that break anything?
10010      */
10011     cm = lastLoadGameStart = (ChessMove) 0;
10012     while (gn > 0) {
10013         yyboardindex = forwardMostMove;
10014         cm = (ChessMove) yylex();
10015         switch (cm) {
10016           case (ChessMove) 0:
10017             if (cmailMsgLoaded) {
10018                 nCmailGames = CMAIL_MAX_GAMES - gn;
10019             } else {
10020                 Reset(TRUE, TRUE);
10021                 DisplayError(_("Game not found in file"), 0);
10022             }
10023             return FALSE;
10024
10025           case GNUChessGame:
10026           case XBoardGame:
10027             gn--;
10028             lastLoadGameStart = cm;
10029             break;
10030
10031           case MoveNumberOne:
10032             switch (lastLoadGameStart) {
10033               case GNUChessGame:
10034               case XBoardGame:
10035               case PGNTag:
10036                 break;
10037               case MoveNumberOne:
10038               case (ChessMove) 0:
10039                 gn--;           /* count this game */
10040                 lastLoadGameStart = cm;
10041                 break;
10042               default:
10043                 /* impossible */
10044                 break;
10045             }
10046             break;
10047
10048           case PGNTag:
10049             switch (lastLoadGameStart) {
10050               case GNUChessGame:
10051               case PGNTag:
10052               case MoveNumberOne:
10053               case (ChessMove) 0:
10054                 gn--;           /* count this game */
10055                 lastLoadGameStart = cm;
10056                 break;
10057               case XBoardGame:
10058                 lastLoadGameStart = cm; /* game counted already */
10059                 break;
10060               default:
10061                 /* impossible */
10062                 break;
10063             }
10064             if (gn > 0) {
10065                 do {
10066                     yyboardindex = forwardMostMove;
10067                     cm = (ChessMove) yylex();
10068                 } while (cm == PGNTag || cm == Comment);
10069             }
10070             break;
10071
10072           case WhiteWins:
10073           case BlackWins:
10074           case GameIsDrawn:
10075             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10076                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10077                     != CMAIL_OLD_RESULT) {
10078                     nCmailResults ++ ;
10079                     cmailResult[  CMAIL_MAX_GAMES
10080                                 - gn - 1] = CMAIL_OLD_RESULT;
10081                 }
10082             }
10083             break;
10084
10085           case NormalMove:
10086             /* Only a NormalMove can be at the start of a game
10087              * without a position diagram. */
10088             if (lastLoadGameStart == (ChessMove) 0) {
10089               gn--;
10090               lastLoadGameStart = MoveNumberOne;
10091             }
10092             break;
10093
10094           default:
10095             break;
10096         }
10097     }
10098
10099     if (appData.debugMode)
10100       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10101
10102     if (cm == XBoardGame) {
10103         /* Skip any header junk before position diagram and/or move 1 */
10104         for (;;) {
10105             yyboardindex = forwardMostMove;
10106             cm = (ChessMove) yylex();
10107
10108             if (cm == (ChessMove) 0 ||
10109                 cm == GNUChessGame || cm == XBoardGame) {
10110                 /* Empty game; pretend end-of-file and handle later */
10111                 cm = (ChessMove) 0;
10112                 break;
10113             }
10114
10115             if (cm == MoveNumberOne || cm == PositionDiagram ||
10116                 cm == PGNTag || cm == Comment)
10117               break;
10118         }
10119     } else if (cm == GNUChessGame) {
10120         if (gameInfo.event != NULL) {
10121             free(gameInfo.event);
10122         }
10123         gameInfo.event = StrSave(yy_text);
10124     }
10125
10126     startedFromSetupPosition = FALSE;
10127     while (cm == PGNTag) {
10128         if (appData.debugMode)
10129           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10130         err = ParsePGNTag(yy_text, &gameInfo);
10131         if (!err) numPGNTags++;
10132
10133         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10134         if(gameInfo.variant != oldVariant) {
10135             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10136             InitPosition(TRUE);
10137             oldVariant = gameInfo.variant;
10138             if (appData.debugMode)
10139               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10140         }
10141
10142
10143         if (gameInfo.fen != NULL) {
10144           Board initial_position;
10145           startedFromSetupPosition = TRUE;
10146           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10147             Reset(TRUE, TRUE);
10148             DisplayError(_("Bad FEN position in file"), 0);
10149             return FALSE;
10150           }
10151           CopyBoard(boards[0], initial_position);
10152           if (blackPlaysFirst) {
10153             currentMove = forwardMostMove = backwardMostMove = 1;
10154             CopyBoard(boards[1], initial_position);
10155             strcpy(moveList[0], "");
10156             strcpy(parseList[0], "");
10157             timeRemaining[0][1] = whiteTimeRemaining;
10158             timeRemaining[1][1] = blackTimeRemaining;
10159             if (commentList[0] != NULL) {
10160               commentList[1] = commentList[0];
10161               commentList[0] = NULL;
10162             }
10163           } else {
10164             currentMove = forwardMostMove = backwardMostMove = 0;
10165           }
10166           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10167           {   int i;
10168               initialRulePlies = FENrulePlies;
10169               for( i=0; i< nrCastlingRights; i++ )
10170                   initialRights[i] = initial_position[CASTLING][i];
10171           }
10172           yyboardindex = forwardMostMove;
10173           free(gameInfo.fen);
10174           gameInfo.fen = NULL;
10175         }
10176
10177         yyboardindex = forwardMostMove;
10178         cm = (ChessMove) yylex();
10179
10180         /* Handle comments interspersed among the tags */
10181         while (cm == Comment) {
10182             char *p;
10183             if (appData.debugMode)
10184               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10185             p = yy_text;
10186             AppendComment(currentMove, p, FALSE);
10187             yyboardindex = forwardMostMove;
10188             cm = (ChessMove) yylex();
10189         }
10190     }
10191
10192     /* don't rely on existence of Event tag since if game was
10193      * pasted from clipboard the Event tag may not exist
10194      */
10195     if (numPGNTags > 0){
10196         char *tags;
10197         if (gameInfo.variant == VariantNormal) {
10198           gameInfo.variant = StringToVariant(gameInfo.event);
10199         }
10200         if (!matchMode) {
10201           if( appData.autoDisplayTags ) {
10202             tags = PGNTags(&gameInfo);
10203             TagsPopUp(tags, CmailMsg());
10204             free(tags);
10205           }
10206         }
10207     } else {
10208         /* Make something up, but don't display it now */
10209         SetGameInfo();
10210         TagsPopDown();
10211     }
10212
10213     if (cm == PositionDiagram) {
10214         int i, j;
10215         char *p;
10216         Board initial_position;
10217
10218         if (appData.debugMode)
10219           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10220
10221         if (!startedFromSetupPosition) {
10222             p = yy_text;
10223             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10224               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10225                 switch (*p) {
10226                   case '[':
10227                   case '-':
10228                   case ' ':
10229                   case '\t':
10230                   case '\n':
10231                   case '\r':
10232                     break;
10233                   default:
10234                     initial_position[i][j++] = CharToPiece(*p);
10235                     break;
10236                 }
10237             while (*p == ' ' || *p == '\t' ||
10238                    *p == '\n' || *p == '\r') p++;
10239
10240             if (strncmp(p, "black", strlen("black"))==0)
10241               blackPlaysFirst = TRUE;
10242             else
10243               blackPlaysFirst = FALSE;
10244             startedFromSetupPosition = TRUE;
10245
10246             CopyBoard(boards[0], initial_position);
10247             if (blackPlaysFirst) {
10248                 currentMove = forwardMostMove = backwardMostMove = 1;
10249                 CopyBoard(boards[1], initial_position);
10250                 strcpy(moveList[0], "");
10251                 strcpy(parseList[0], "");
10252                 timeRemaining[0][1] = whiteTimeRemaining;
10253                 timeRemaining[1][1] = blackTimeRemaining;
10254                 if (commentList[0] != NULL) {
10255                     commentList[1] = commentList[0];
10256                     commentList[0] = NULL;
10257                 }
10258             } else {
10259                 currentMove = forwardMostMove = backwardMostMove = 0;
10260             }
10261         }
10262         yyboardindex = forwardMostMove;
10263         cm = (ChessMove) yylex();
10264     }
10265
10266     if (first.pr == NoProc) {
10267         StartChessProgram(&first);
10268     }
10269     InitChessProgram(&first, FALSE);
10270     SendToProgram("force\n", &first);
10271     if (startedFromSetupPosition) {
10272         SendBoard(&first, forwardMostMove);
10273     if (appData.debugMode) {
10274         fprintf(debugFP, "Load Game\n");
10275     }
10276         DisplayBothClocks();
10277     }
10278
10279     /* [HGM] server: flag to write setup moves in broadcast file as one */
10280     loadFlag = appData.suppressLoadMoves;
10281
10282     while (cm == Comment) {
10283         char *p;
10284         if (appData.debugMode)
10285           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10286         p = yy_text;
10287         AppendComment(currentMove, p, FALSE);
10288         yyboardindex = forwardMostMove;
10289         cm = (ChessMove) yylex();
10290     }
10291
10292     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10293         cm == WhiteWins || cm == BlackWins ||
10294         cm == GameIsDrawn || cm == GameUnfinished) {
10295         DisplayMessage("", _("No moves in game"));
10296         if (cmailMsgLoaded) {
10297             if (appData.debugMode)
10298               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10299             ClearHighlights();
10300             flipView = FALSE;
10301         }
10302         DrawPosition(FALSE, boards[currentMove]);
10303         DisplayBothClocks();
10304         gameMode = EditGame;
10305         ModeHighlight();
10306         gameFileFP = NULL;
10307         cmailOldMove = 0;
10308         return TRUE;
10309     }
10310
10311     // [HGM] PV info: routine tests if comment empty
10312     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10313         DisplayComment(currentMove - 1, commentList[currentMove]);
10314     }
10315     if (!matchMode && appData.timeDelay != 0)
10316       DrawPosition(FALSE, boards[currentMove]);
10317
10318     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10319       programStats.ok_to_send = 1;
10320     }
10321
10322     /* if the first token after the PGN tags is a move
10323      * and not move number 1, retrieve it from the parser
10324      */
10325     if (cm != MoveNumberOne)
10326         LoadGameOneMove(cm);
10327
10328     /* load the remaining moves from the file */
10329     while (LoadGameOneMove((ChessMove)0)) {
10330       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10331       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10332     }
10333
10334     /* rewind to the start of the game */
10335     currentMove = backwardMostMove;
10336
10337     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10338
10339     if (oldGameMode == AnalyzeFile ||
10340         oldGameMode == AnalyzeMode) {
10341       AnalyzeFileEvent();
10342     }
10343
10344     if (matchMode || appData.timeDelay == 0) {
10345       ToEndEvent();
10346       gameMode = EditGame;
10347       ModeHighlight();
10348     } else if (appData.timeDelay > 0) {
10349       AutoPlayGameLoop();
10350     }
10351
10352     if (appData.debugMode)
10353         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10354
10355     loadFlag = 0; /* [HGM] true game starts */
10356     return TRUE;
10357 }
10358
10359 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10360 int
10361 ReloadPosition(offset)
10362      int offset;
10363 {
10364     int positionNumber = lastLoadPositionNumber + offset;
10365     if (lastLoadPositionFP == NULL) {
10366         DisplayError(_("No position has been loaded yet"), 0);
10367         return FALSE;
10368     }
10369     if (positionNumber <= 0) {
10370         DisplayError(_("Can't back up any further"), 0);
10371         return FALSE;
10372     }
10373     return LoadPosition(lastLoadPositionFP, positionNumber,
10374                         lastLoadPositionTitle);
10375 }
10376
10377 /* Load the nth position from the given file */
10378 int
10379 LoadPositionFromFile(filename, n, title)
10380      char *filename;
10381      int n;
10382      char *title;
10383 {
10384     FILE *f;
10385     char buf[MSG_SIZ];
10386
10387     if (strcmp(filename, "-") == 0) {
10388         return LoadPosition(stdin, n, "stdin");
10389     } else {
10390         f = fopen(filename, "rb");
10391         if (f == NULL) {
10392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10393             DisplayError(buf, errno);
10394             return FALSE;
10395         } else {
10396             return LoadPosition(f, n, title);
10397         }
10398     }
10399 }
10400
10401 /* Load the nth position from the given open file, and close it */
10402 int
10403 LoadPosition(f, positionNumber, title)
10404      FILE *f;
10405      int positionNumber;
10406      char *title;
10407 {
10408     char *p, line[MSG_SIZ];
10409     Board initial_position;
10410     int i, j, fenMode, pn;
10411
10412     if (gameMode == Training )
10413         SetTrainingModeOff();
10414
10415     if (gameMode != BeginningOfGame) {
10416         Reset(FALSE, TRUE);
10417     }
10418     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10419         fclose(lastLoadPositionFP);
10420     }
10421     if (positionNumber == 0) positionNumber = 1;
10422     lastLoadPositionFP = f;
10423     lastLoadPositionNumber = positionNumber;
10424     strcpy(lastLoadPositionTitle, title);
10425     if (first.pr == NoProc) {
10426       StartChessProgram(&first);
10427       InitChessProgram(&first, FALSE);
10428     }
10429     pn = positionNumber;
10430     if (positionNumber < 0) {
10431         /* Negative position number means to seek to that byte offset */
10432         if (fseek(f, -positionNumber, 0) == -1) {
10433             DisplayError(_("Can't seek on position file"), 0);
10434             return FALSE;
10435         };
10436         pn = 1;
10437     } else {
10438         if (fseek(f, 0, 0) == -1) {
10439             if (f == lastLoadPositionFP ?
10440                 positionNumber == lastLoadPositionNumber + 1 :
10441                 positionNumber == 1) {
10442                 pn = 1;
10443             } else {
10444                 DisplayError(_("Can't seek on position file"), 0);
10445                 return FALSE;
10446             }
10447         }
10448     }
10449     /* See if this file is FEN or old-style xboard */
10450     if (fgets(line, MSG_SIZ, f) == NULL) {
10451         DisplayError(_("Position not found in file"), 0);
10452         return FALSE;
10453     }
10454     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10455     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10456
10457     if (pn >= 2) {
10458         if (fenMode || line[0] == '#') pn--;
10459         while (pn > 0) {
10460             /* skip positions before number pn */
10461             if (fgets(line, MSG_SIZ, f) == NULL) {
10462                 Reset(TRUE, TRUE);
10463                 DisplayError(_("Position not found in file"), 0);
10464                 return FALSE;
10465             }
10466             if (fenMode || line[0] == '#') pn--;
10467         }
10468     }
10469
10470     if (fenMode) {
10471         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10472             DisplayError(_("Bad FEN position in file"), 0);
10473             return FALSE;
10474         }
10475     } else {
10476         (void) fgets(line, MSG_SIZ, f);
10477         (void) fgets(line, MSG_SIZ, f);
10478
10479         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10480             (void) fgets(line, MSG_SIZ, f);
10481             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10482                 if (*p == ' ')
10483                   continue;
10484                 initial_position[i][j++] = CharToPiece(*p);
10485             }
10486         }
10487
10488         blackPlaysFirst = FALSE;
10489         if (!feof(f)) {
10490             (void) fgets(line, MSG_SIZ, f);
10491             if (strncmp(line, "black", strlen("black"))==0)
10492               blackPlaysFirst = TRUE;
10493         }
10494     }
10495     startedFromSetupPosition = TRUE;
10496
10497     SendToProgram("force\n", &first);
10498     CopyBoard(boards[0], initial_position);
10499     if (blackPlaysFirst) {
10500         currentMove = forwardMostMove = backwardMostMove = 1;
10501         strcpy(moveList[0], "");
10502         strcpy(parseList[0], "");
10503         CopyBoard(boards[1], initial_position);
10504         DisplayMessage("", _("Black to play"));
10505     } else {
10506         currentMove = forwardMostMove = backwardMostMove = 0;
10507         DisplayMessage("", _("White to play"));
10508     }
10509     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10510     SendBoard(&first, forwardMostMove);
10511     if (appData.debugMode) {
10512 int i, j;
10513   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10514   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10515         fprintf(debugFP, "Load Position\n");
10516     }
10517
10518     if (positionNumber > 1) {
10519         sprintf(line, "%s %d", title, positionNumber);
10520         DisplayTitle(line);
10521     } else {
10522         DisplayTitle(title);
10523     }
10524     gameMode = EditGame;
10525     ModeHighlight();
10526     ResetClocks();
10527     timeRemaining[0][1] = whiteTimeRemaining;
10528     timeRemaining[1][1] = blackTimeRemaining;
10529     DrawPosition(FALSE, boards[currentMove]);
10530
10531     return TRUE;
10532 }
10533
10534
10535 void
10536 CopyPlayerNameIntoFileName(dest, src)
10537      char **dest, *src;
10538 {
10539     while (*src != NULLCHAR && *src != ',') {
10540         if (*src == ' ') {
10541             *(*dest)++ = '_';
10542             src++;
10543         } else {
10544             *(*dest)++ = *src++;
10545         }
10546     }
10547 }
10548
10549 char *DefaultFileName(ext)
10550      char *ext;
10551 {
10552     static char def[MSG_SIZ];
10553     char *p;
10554
10555     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10556         p = def;
10557         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10558         *p++ = '-';
10559         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10560         *p++ = '.';
10561         strcpy(p, ext);
10562     } else {
10563         def[0] = NULLCHAR;
10564     }
10565     return def;
10566 }
10567
10568 /* Save the current game to the given file */
10569 int
10570 SaveGameToFile(filename, append)
10571      char *filename;
10572      int append;
10573 {
10574     FILE *f;
10575     char buf[MSG_SIZ];
10576
10577     if (strcmp(filename, "-") == 0) {
10578         return SaveGame(stdout, 0, NULL);
10579     } else {
10580         f = fopen(filename, append ? "a" : "w");
10581         if (f == NULL) {
10582             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10583             DisplayError(buf, errno);
10584             return FALSE;
10585         } else {
10586             return SaveGame(f, 0, NULL);
10587         }
10588     }
10589 }
10590
10591 char *
10592 SavePart(str)
10593      char *str;
10594 {
10595     static char buf[MSG_SIZ];
10596     char *p;
10597
10598     p = strchr(str, ' ');
10599     if (p == NULL) return str;
10600     strncpy(buf, str, p - str);
10601     buf[p - str] = NULLCHAR;
10602     return buf;
10603 }
10604
10605 #define PGN_MAX_LINE 75
10606
10607 #define PGN_SIDE_WHITE  0
10608 #define PGN_SIDE_BLACK  1
10609
10610 /* [AS] */
10611 static int FindFirstMoveOutOfBook( int side )
10612 {
10613     int result = -1;
10614
10615     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10616         int index = backwardMostMove;
10617         int has_book_hit = 0;
10618
10619         if( (index % 2) != side ) {
10620             index++;
10621         }
10622
10623         while( index < forwardMostMove ) {
10624             /* Check to see if engine is in book */
10625             int depth = pvInfoList[index].depth;
10626             int score = pvInfoList[index].score;
10627             int in_book = 0;
10628
10629             if( depth <= 2 ) {
10630                 in_book = 1;
10631             }
10632             else if( score == 0 && depth == 63 ) {
10633                 in_book = 1; /* Zappa */
10634             }
10635             else if( score == 2 && depth == 99 ) {
10636                 in_book = 1; /* Abrok */
10637             }
10638
10639             has_book_hit += in_book;
10640
10641             if( ! in_book ) {
10642                 result = index;
10643
10644                 break;
10645             }
10646
10647             index += 2;
10648         }
10649     }
10650
10651     return result;
10652 }
10653
10654 /* [AS] */
10655 void GetOutOfBookInfo( char * buf )
10656 {
10657     int oob[2];
10658     int i;
10659     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10660
10661     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10662     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10663
10664     *buf = '\0';
10665
10666     if( oob[0] >= 0 || oob[1] >= 0 ) {
10667         for( i=0; i<2; i++ ) {
10668             int idx = oob[i];
10669
10670             if( idx >= 0 ) {
10671                 if( i > 0 && oob[0] >= 0 ) {
10672                     strcat( buf, "   " );
10673                 }
10674
10675                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10676                 sprintf( buf+strlen(buf), "%s%.2f",
10677                     pvInfoList[idx].score >= 0 ? "+" : "",
10678                     pvInfoList[idx].score / 100.0 );
10679             }
10680         }
10681     }
10682 }
10683
10684 /* Save game in PGN style and close the file */
10685 int
10686 SaveGamePGN(f)
10687      FILE *f;
10688 {
10689     int i, offset, linelen, newblock;
10690     time_t tm;
10691 //    char *movetext;
10692     char numtext[32];
10693     int movelen, numlen, blank;
10694     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10695
10696     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10697
10698     tm = time((time_t *) NULL);
10699
10700     PrintPGNTags(f, &gameInfo);
10701
10702     if (backwardMostMove > 0 || startedFromSetupPosition) {
10703         char *fen = PositionToFEN(backwardMostMove, NULL);
10704         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10705         fprintf(f, "\n{--------------\n");
10706         PrintPosition(f, backwardMostMove);
10707         fprintf(f, "--------------}\n");
10708         free(fen);
10709     }
10710     else {
10711         /* [AS] Out of book annotation */
10712         if( appData.saveOutOfBookInfo ) {
10713             char buf[64];
10714
10715             GetOutOfBookInfo( buf );
10716
10717             if( buf[0] != '\0' ) {
10718                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10719             }
10720         }
10721
10722         fprintf(f, "\n");
10723     }
10724
10725     i = backwardMostMove;
10726     linelen = 0;
10727     newblock = TRUE;
10728
10729     while (i < forwardMostMove) {
10730         /* Print comments preceding this move */
10731         if (commentList[i] != NULL) {
10732             if (linelen > 0) fprintf(f, "\n");
10733             fprintf(f, "%s", commentList[i]);
10734             linelen = 0;
10735             newblock = TRUE;
10736         }
10737
10738         /* Format move number */
10739         if ((i % 2) == 0) {
10740             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10741         } else {
10742             if (newblock) {
10743                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10744             } else {
10745                 numtext[0] = NULLCHAR;
10746             }
10747         }
10748         numlen = strlen(numtext);
10749         newblock = FALSE;
10750
10751         /* Print move number */
10752         blank = linelen > 0 && numlen > 0;
10753         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10754             fprintf(f, "\n");
10755             linelen = 0;
10756             blank = 0;
10757         }
10758         if (blank) {
10759             fprintf(f, " ");
10760             linelen++;
10761         }
10762         fprintf(f, "%s", numtext);
10763         linelen += numlen;
10764
10765         /* Get move */
10766         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10767         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10768
10769         /* Print move */
10770         blank = linelen > 0 && movelen > 0;
10771         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10772             fprintf(f, "\n");
10773             linelen = 0;
10774             blank = 0;
10775         }
10776         if (blank) {
10777             fprintf(f, " ");
10778             linelen++;
10779         }
10780         fprintf(f, "%s", move_buffer);
10781         linelen += movelen;
10782
10783         /* [AS] Add PV info if present */
10784         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10785             /* [HGM] add time */
10786             char buf[MSG_SIZ]; int seconds;
10787
10788             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10789
10790             if( seconds <= 0) buf[0] = 0; else
10791             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10792                 seconds = (seconds + 4)/10; // round to full seconds
10793                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10794                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10795             }
10796
10797             sprintf( move_buffer, "{%s%.2f/%d%s}",
10798                 pvInfoList[i].score >= 0 ? "+" : "",
10799                 pvInfoList[i].score / 100.0,
10800                 pvInfoList[i].depth,
10801                 buf );
10802
10803             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10804
10805             /* Print score/depth */
10806             blank = linelen > 0 && movelen > 0;
10807             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10808                 fprintf(f, "\n");
10809                 linelen = 0;
10810                 blank = 0;
10811             }
10812             if (blank) {
10813                 fprintf(f, " ");
10814                 linelen++;
10815             }
10816             fprintf(f, "%s", move_buffer);
10817             linelen += movelen;
10818         }
10819
10820         i++;
10821     }
10822
10823     /* Start a new line */
10824     if (linelen > 0) fprintf(f, "\n");
10825
10826     /* Print comments after last move */
10827     if (commentList[i] != NULL) {
10828         fprintf(f, "%s\n", commentList[i]);
10829     }
10830
10831     /* Print result */
10832     if (gameInfo.resultDetails != NULL &&
10833         gameInfo.resultDetails[0] != NULLCHAR) {
10834         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10835                 PGNResult(gameInfo.result));
10836     } else {
10837         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10838     }
10839
10840     fclose(f);
10841     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10842     return TRUE;
10843 }
10844
10845 /* Save game in old style and close the file */
10846 int
10847 SaveGameOldStyle(f)
10848      FILE *f;
10849 {
10850     int i, offset;
10851     time_t tm;
10852
10853     tm = time((time_t *) NULL);
10854
10855     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10856     PrintOpponents(f);
10857
10858     if (backwardMostMove > 0 || startedFromSetupPosition) {
10859         fprintf(f, "\n[--------------\n");
10860         PrintPosition(f, backwardMostMove);
10861         fprintf(f, "--------------]\n");
10862     } else {
10863         fprintf(f, "\n");
10864     }
10865
10866     i = backwardMostMove;
10867     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10868
10869     while (i < forwardMostMove) {
10870         if (commentList[i] != NULL) {
10871             fprintf(f, "[%s]\n", commentList[i]);
10872         }
10873
10874         if ((i % 2) == 1) {
10875             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10876             i++;
10877         } else {
10878             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10879             i++;
10880             if (commentList[i] != NULL) {
10881                 fprintf(f, "\n");
10882                 continue;
10883             }
10884             if (i >= forwardMostMove) {
10885                 fprintf(f, "\n");
10886                 break;
10887             }
10888             fprintf(f, "%s\n", parseList[i]);
10889             i++;
10890         }
10891     }
10892
10893     if (commentList[i] != NULL) {
10894         fprintf(f, "[%s]\n", commentList[i]);
10895     }
10896
10897     /* This isn't really the old style, but it's close enough */
10898     if (gameInfo.resultDetails != NULL &&
10899         gameInfo.resultDetails[0] != NULLCHAR) {
10900         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10901                 gameInfo.resultDetails);
10902     } else {
10903         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10904     }
10905
10906     fclose(f);
10907     return TRUE;
10908 }
10909
10910 /* Save the current game to open file f and close the file */
10911 int
10912 SaveGame(f, dummy, dummy2)
10913      FILE *f;
10914      int dummy;
10915      char *dummy2;
10916 {
10917     if (gameMode == EditPosition) EditPositionDone(TRUE);
10918     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10919     if (appData.oldSaveStyle)
10920       return SaveGameOldStyle(f);
10921     else
10922       return SaveGamePGN(f);
10923 }
10924
10925 /* Save the current position to the given file */
10926 int
10927 SavePositionToFile(filename)
10928      char *filename;
10929 {
10930     FILE *f;
10931     char buf[MSG_SIZ];
10932
10933     if (strcmp(filename, "-") == 0) {
10934         return SavePosition(stdout, 0, NULL);
10935     } else {
10936         f = fopen(filename, "a");
10937         if (f == NULL) {
10938             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10939             DisplayError(buf, errno);
10940             return FALSE;
10941         } else {
10942             SavePosition(f, 0, NULL);
10943             return TRUE;
10944         }
10945     }
10946 }
10947
10948 /* Save the current position to the given open file and close the file */
10949 int
10950 SavePosition(f, dummy, dummy2)
10951      FILE *f;
10952      int dummy;
10953      char *dummy2;
10954 {
10955     time_t tm;
10956     char *fen;
10957     if (gameMode == EditPosition) EditPositionDone(TRUE);
10958     if (appData.oldSaveStyle) {
10959         tm = time((time_t *) NULL);
10960
10961         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10962         PrintOpponents(f);
10963         fprintf(f, "[--------------\n");
10964         PrintPosition(f, currentMove);
10965         fprintf(f, "--------------]\n");
10966     } else {
10967         fen = PositionToFEN(currentMove, NULL);
10968         fprintf(f, "%s\n", fen);
10969         free(fen);
10970     }
10971     fclose(f);
10972     return TRUE;
10973 }
10974
10975 void
10976 ReloadCmailMsgEvent(unregister)
10977      int unregister;
10978 {
10979 #if !WIN32
10980     static char *inFilename = NULL;
10981     static char *outFilename;
10982     int i;
10983     struct stat inbuf, outbuf;
10984     int status;
10985
10986     /* Any registered moves are unregistered if unregister is set, */
10987     /* i.e. invoked by the signal handler */
10988     if (unregister) {
10989         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10990             cmailMoveRegistered[i] = FALSE;
10991             if (cmailCommentList[i] != NULL) {
10992                 free(cmailCommentList[i]);
10993                 cmailCommentList[i] = NULL;
10994             }
10995         }
10996         nCmailMovesRegistered = 0;
10997     }
10998
10999     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11000         cmailResult[i] = CMAIL_NOT_RESULT;
11001     }
11002     nCmailResults = 0;
11003
11004     if (inFilename == NULL) {
11005         /* Because the filenames are static they only get malloced once  */
11006         /* and they never get freed                                      */
11007         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11008         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11009
11010         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11011         sprintf(outFilename, "%s.out", appData.cmailGameName);
11012     }
11013
11014     status = stat(outFilename, &outbuf);
11015     if (status < 0) {
11016         cmailMailedMove = FALSE;
11017     } else {
11018         status = stat(inFilename, &inbuf);
11019         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11020     }
11021
11022     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11023        counts the games, notes how each one terminated, etc.
11024
11025        It would be nice to remove this kludge and instead gather all
11026        the information while building the game list.  (And to keep it
11027        in the game list nodes instead of having a bunch of fixed-size
11028        parallel arrays.)  Note this will require getting each game's
11029        termination from the PGN tags, as the game list builder does
11030        not process the game moves.  --mann
11031        */
11032     cmailMsgLoaded = TRUE;
11033     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11034
11035     /* Load first game in the file or popup game menu */
11036     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11037
11038 #endif /* !WIN32 */
11039     return;
11040 }
11041
11042 int
11043 RegisterMove()
11044 {
11045     FILE *f;
11046     char string[MSG_SIZ];
11047
11048     if (   cmailMailedMove
11049         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11050         return TRUE;            /* Allow free viewing  */
11051     }
11052
11053     /* Unregister move to ensure that we don't leave RegisterMove        */
11054     /* with the move registered when the conditions for registering no   */
11055     /* longer hold                                                       */
11056     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11057         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11058         nCmailMovesRegistered --;
11059
11060         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11061           {
11062               free(cmailCommentList[lastLoadGameNumber - 1]);
11063               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11064           }
11065     }
11066
11067     if (cmailOldMove == -1) {
11068         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11069         return FALSE;
11070     }
11071
11072     if (currentMove > cmailOldMove + 1) {
11073         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11074         return FALSE;
11075     }
11076
11077     if (currentMove < cmailOldMove) {
11078         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11079         return FALSE;
11080     }
11081
11082     if (forwardMostMove > currentMove) {
11083         /* Silently truncate extra moves */
11084         TruncateGame();
11085     }
11086
11087     if (   (currentMove == cmailOldMove + 1)
11088         || (   (currentMove == cmailOldMove)
11089             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11090                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11091         if (gameInfo.result != GameUnfinished) {
11092             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11093         }
11094
11095         if (commentList[currentMove] != NULL) {
11096             cmailCommentList[lastLoadGameNumber - 1]
11097               = StrSave(commentList[currentMove]);
11098         }
11099         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11100
11101         if (appData.debugMode)
11102           fprintf(debugFP, "Saving %s for game %d\n",
11103                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11104
11105         sprintf(string,
11106                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11107
11108         f = fopen(string, "w");
11109         if (appData.oldSaveStyle) {
11110             SaveGameOldStyle(f); /* also closes the file */
11111
11112             sprintf(string, "%s.pos.out", appData.cmailGameName);
11113             f = fopen(string, "w");
11114             SavePosition(f, 0, NULL); /* also closes the file */
11115         } else {
11116             fprintf(f, "{--------------\n");
11117             PrintPosition(f, currentMove);
11118             fprintf(f, "--------------}\n\n");
11119
11120             SaveGame(f, 0, NULL); /* also closes the file*/
11121         }
11122
11123         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11124         nCmailMovesRegistered ++;
11125     } else if (nCmailGames == 1) {
11126         DisplayError(_("You have not made a move yet"), 0);
11127         return FALSE;
11128     }
11129
11130     return TRUE;
11131 }
11132
11133 void
11134 MailMoveEvent()
11135 {
11136 #if !WIN32
11137     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11138     FILE *commandOutput;
11139     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11140     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11141     int nBuffers;
11142     int i;
11143     int archived;
11144     char *arcDir;
11145
11146     if (! cmailMsgLoaded) {
11147         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11148         return;
11149     }
11150
11151     if (nCmailGames == nCmailResults) {
11152         DisplayError(_("No unfinished games"), 0);
11153         return;
11154     }
11155
11156 #if CMAIL_PROHIBIT_REMAIL
11157     if (cmailMailedMove) {
11158         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);
11159         DisplayError(msg, 0);
11160         return;
11161     }
11162 #endif
11163
11164     if (! (cmailMailedMove || RegisterMove())) return;
11165
11166     if (   cmailMailedMove
11167         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11168         sprintf(string, partCommandString,
11169                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11170         commandOutput = popen(string, "r");
11171
11172         if (commandOutput == NULL) {
11173             DisplayError(_("Failed to invoke cmail"), 0);
11174         } else {
11175             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11176                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11177             }
11178             if (nBuffers > 1) {
11179                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11180                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11181                 nBytes = MSG_SIZ - 1;
11182             } else {
11183                 (void) memcpy(msg, buffer, nBytes);
11184             }
11185             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11186
11187             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11188                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11189
11190                 archived = TRUE;
11191                 for (i = 0; i < nCmailGames; i ++) {
11192                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11193                         archived = FALSE;
11194                     }
11195                 }
11196                 if (   archived
11197                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11198                         != NULL)) {
11199                     sprintf(buffer, "%s/%s.%s.archive",
11200                             arcDir,
11201                             appData.cmailGameName,
11202                             gameInfo.date);
11203                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11204                     cmailMsgLoaded = FALSE;
11205                 }
11206             }
11207
11208             DisplayInformation(msg);
11209             pclose(commandOutput);
11210         }
11211     } else {
11212         if ((*cmailMsg) != '\0') {
11213             DisplayInformation(cmailMsg);
11214         }
11215     }
11216
11217     return;
11218 #endif /* !WIN32 */
11219 }
11220
11221 char *
11222 CmailMsg()
11223 {
11224 #if WIN32
11225     return NULL;
11226 #else
11227     int  prependComma = 0;
11228     char number[5];
11229     char string[MSG_SIZ];       /* Space for game-list */
11230     int  i;
11231
11232     if (!cmailMsgLoaded) return "";
11233
11234     if (cmailMailedMove) {
11235         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11236     } else {
11237         /* Create a list of games left */
11238         sprintf(string, "[");
11239         for (i = 0; i < nCmailGames; i ++) {
11240             if (! (   cmailMoveRegistered[i]
11241                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11242                 if (prependComma) {
11243                     sprintf(number, ",%d", i + 1);
11244                 } else {
11245                     sprintf(number, "%d", i + 1);
11246                     prependComma = 1;
11247                 }
11248
11249                 strcat(string, number);
11250             }
11251         }
11252         strcat(string, "]");
11253
11254         if (nCmailMovesRegistered + nCmailResults == 0) {
11255             switch (nCmailGames) {
11256               case 1:
11257                 sprintf(cmailMsg,
11258                         _("Still need to make move for game\n"));
11259                 break;
11260
11261               case 2:
11262                 sprintf(cmailMsg,
11263                         _("Still need to make moves for both games\n"));
11264                 break;
11265
11266               default:
11267                 sprintf(cmailMsg,
11268                         _("Still need to make moves for all %d games\n"),
11269                         nCmailGames);
11270                 break;
11271             }
11272         } else {
11273             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11274               case 1:
11275                 sprintf(cmailMsg,
11276                         _("Still need to make a move for game %s\n"),
11277                         string);
11278                 break;
11279
11280               case 0:
11281                 if (nCmailResults == nCmailGames) {
11282                     sprintf(cmailMsg, _("No unfinished games\n"));
11283                 } else {
11284                     sprintf(cmailMsg, _("Ready to send mail\n"));
11285                 }
11286                 break;
11287
11288               default:
11289                 sprintf(cmailMsg,
11290                         _("Still need to make moves for games %s\n"),
11291                         string);
11292             }
11293         }
11294     }
11295     return cmailMsg;
11296 #endif /* WIN32 */
11297 }
11298
11299 void
11300 ResetGameEvent()
11301 {
11302     if (gameMode == Training)
11303       SetTrainingModeOff();
11304
11305     Reset(TRUE, TRUE);
11306     cmailMsgLoaded = FALSE;
11307     if (appData.icsActive) {
11308       SendToICS(ics_prefix);
11309       SendToICS("refresh\n");
11310     }
11311 }
11312
11313 void
11314 ExitEvent(status)
11315      int status;
11316 {
11317     exiting++;
11318     if (exiting > 2) {
11319       /* Give up on clean exit */
11320       exit(status);
11321     }
11322     if (exiting > 1) {
11323       /* Keep trying for clean exit */
11324       return;
11325     }
11326
11327     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11328
11329     if (telnetISR != NULL) {
11330       RemoveInputSource(telnetISR);
11331     }
11332     if (icsPR != NoProc) {
11333       DestroyChildProcess(icsPR, TRUE);
11334     }
11335
11336     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11337     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11338
11339     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11340     /* make sure this other one finishes before killing it!                  */
11341     if(endingGame) { int count = 0;
11342         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11343         while(endingGame && count++ < 10) DoSleep(1);
11344         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11345     }
11346
11347     /* Kill off chess programs */
11348     if (first.pr != NoProc) {
11349         ExitAnalyzeMode();
11350
11351         DoSleep( appData.delayBeforeQuit );
11352         SendToProgram("quit\n", &first);
11353         DoSleep( appData.delayAfterQuit );
11354         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11355     }
11356     if (second.pr != NoProc) {
11357         DoSleep( appData.delayBeforeQuit );
11358         SendToProgram("quit\n", &second);
11359         DoSleep( appData.delayAfterQuit );
11360         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11361     }
11362     if (first.isr != NULL) {
11363         RemoveInputSource(first.isr);
11364     }
11365     if (second.isr != NULL) {
11366         RemoveInputSource(second.isr);
11367     }
11368
11369     ShutDownFrontEnd();
11370     exit(status);
11371 }
11372
11373 void
11374 PauseEvent()
11375 {
11376     if (appData.debugMode)
11377         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11378     if (pausing) {
11379         pausing = FALSE;
11380         ModeHighlight();
11381         if (gameMode == MachinePlaysWhite ||
11382             gameMode == MachinePlaysBlack) {
11383             StartClocks();
11384         } else {
11385             DisplayBothClocks();
11386         }
11387         if (gameMode == PlayFromGameFile) {
11388             if (appData.timeDelay >= 0)
11389                 AutoPlayGameLoop();
11390         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11391             Reset(FALSE, TRUE);
11392             SendToICS(ics_prefix);
11393             SendToICS("refresh\n");
11394         } else if (currentMove < forwardMostMove) {
11395             ForwardInner(forwardMostMove);
11396         }
11397         pauseExamInvalid = FALSE;
11398     } else {
11399         switch (gameMode) {
11400           default:
11401             return;
11402           case IcsExamining:
11403             pauseExamForwardMostMove = forwardMostMove;
11404             pauseExamInvalid = FALSE;
11405             /* fall through */
11406           case IcsObserving:
11407           case IcsPlayingWhite:
11408           case IcsPlayingBlack:
11409             pausing = TRUE;
11410             ModeHighlight();
11411             return;
11412           case PlayFromGameFile:
11413             (void) StopLoadGameTimer();
11414             pausing = TRUE;
11415             ModeHighlight();
11416             break;
11417           case BeginningOfGame:
11418             if (appData.icsActive) return;
11419             /* else fall through */
11420           case MachinePlaysWhite:
11421           case MachinePlaysBlack:
11422           case TwoMachinesPlay:
11423             if (forwardMostMove == 0)
11424               return;           /* don't pause if no one has moved */
11425             if ((gameMode == MachinePlaysWhite &&
11426                  !WhiteOnMove(forwardMostMove)) ||
11427                 (gameMode == MachinePlaysBlack &&
11428                  WhiteOnMove(forwardMostMove))) {
11429                 StopClocks();
11430             }
11431             pausing = TRUE;
11432             ModeHighlight();
11433             break;
11434         }
11435     }
11436 }
11437
11438 void
11439 EditCommentEvent()
11440 {
11441     char title[MSG_SIZ];
11442
11443     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11444         strcpy(title, _("Edit comment"));
11445     } else {
11446         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11447                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11448                 parseList[currentMove - 1]);
11449     }
11450
11451     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11452 }
11453
11454
11455 void
11456 EditTagsEvent()
11457 {
11458     char *tags = PGNTags(&gameInfo);
11459     EditTagsPopUp(tags);
11460     free(tags);
11461 }
11462
11463 void
11464 AnalyzeModeEvent()
11465 {
11466     if (appData.noChessProgram || gameMode == AnalyzeMode)
11467       return;
11468
11469     if (gameMode != AnalyzeFile) {
11470         if (!appData.icsEngineAnalyze) {
11471                EditGameEvent();
11472                if (gameMode != EditGame) return;
11473         }
11474         ResurrectChessProgram();
11475         SendToProgram("analyze\n", &first);
11476         first.analyzing = TRUE;
11477         /*first.maybeThinking = TRUE;*/
11478         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11479         EngineOutputPopUp();
11480     }
11481     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11482     pausing = FALSE;
11483     ModeHighlight();
11484     SetGameInfo();
11485
11486     StartAnalysisClock();
11487     GetTimeMark(&lastNodeCountTime);
11488     lastNodeCount = 0;
11489 }
11490
11491 void
11492 AnalyzeFileEvent()
11493 {
11494     if (appData.noChessProgram || gameMode == AnalyzeFile)
11495       return;
11496
11497     if (gameMode != AnalyzeMode) {
11498         EditGameEvent();
11499         if (gameMode != EditGame) return;
11500         ResurrectChessProgram();
11501         SendToProgram("analyze\n", &first);
11502         first.analyzing = TRUE;
11503         /*first.maybeThinking = TRUE;*/
11504         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11505         EngineOutputPopUp();
11506     }
11507     gameMode = AnalyzeFile;
11508     pausing = FALSE;
11509     ModeHighlight();
11510     SetGameInfo();
11511
11512     StartAnalysisClock();
11513     GetTimeMark(&lastNodeCountTime);
11514     lastNodeCount = 0;
11515 }
11516
11517 void
11518 MachineWhiteEvent()
11519 {
11520     char buf[MSG_SIZ];
11521     char *bookHit = NULL;
11522
11523     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11524       return;
11525
11526
11527     if (gameMode == PlayFromGameFile ||
11528         gameMode == TwoMachinesPlay  ||
11529         gameMode == Training         ||
11530         gameMode == AnalyzeMode      ||
11531         gameMode == EndOfGame)
11532         EditGameEvent();
11533
11534     if (gameMode == EditPosition) 
11535         EditPositionDone(TRUE);
11536
11537     if (!WhiteOnMove(currentMove)) {
11538         DisplayError(_("It is not White's turn"), 0);
11539         return;
11540     }
11541
11542     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11543       ExitAnalyzeMode();
11544
11545     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11546         gameMode == AnalyzeFile)
11547         TruncateGame();
11548
11549     ResurrectChessProgram();    /* in case it isn't running */
11550     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11551         gameMode = MachinePlaysWhite;
11552         ResetClocks();
11553     } else
11554     gameMode = MachinePlaysWhite;
11555     pausing = FALSE;
11556     ModeHighlight();
11557     SetGameInfo();
11558     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11559     DisplayTitle(buf);
11560     if (first.sendName) {
11561       sprintf(buf, "name %s\n", gameInfo.black);
11562       SendToProgram(buf, &first);
11563     }
11564     if (first.sendTime) {
11565       if (first.useColors) {
11566         SendToProgram("black\n", &first); /*gnu kludge*/
11567       }
11568       SendTimeRemaining(&first, TRUE);
11569     }
11570     if (first.useColors) {
11571       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11572     }
11573     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11574     SetMachineThinkingEnables();
11575     first.maybeThinking = TRUE;
11576     StartClocks();
11577     firstMove = FALSE;
11578
11579     if (appData.autoFlipView && !flipView) {
11580       flipView = !flipView;
11581       DrawPosition(FALSE, NULL);
11582       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11583     }
11584
11585     if(bookHit) { // [HGM] book: simulate book reply
11586         static char bookMove[MSG_SIZ]; // a bit generous?
11587
11588         programStats.nodes = programStats.depth = programStats.time =
11589         programStats.score = programStats.got_only_move = 0;
11590         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11591
11592         strcpy(bookMove, "move ");
11593         strcat(bookMove, bookHit);
11594         HandleMachineMove(bookMove, &first);
11595     }
11596 }
11597
11598 void
11599 MachineBlackEvent()
11600 {
11601   char buf[MSG_SIZ];
11602   char *bookHit = NULL;
11603   
11604   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11605     return;
11606   
11607   
11608   if (gameMode == PlayFromGameFile 
11609       || gameMode == TwoMachinesPlay  
11610       || gameMode == Training     
11611       || gameMode == AnalyzeMode
11612       || gameMode == EndOfGame)
11613     EditGameEvent();
11614   
11615   if (gameMode == EditPosition) 
11616     EditPositionDone(TRUE);
11617   
11618   if (WhiteOnMove(currentMove)) 
11619     {
11620       DisplayError(_("It is not Black's turn"), 0);
11621       return;
11622     }
11623   
11624   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11625     ExitAnalyzeMode();
11626   
11627   if (gameMode == EditGame || gameMode == AnalyzeMode 
11628       || gameMode == AnalyzeFile)
11629     TruncateGame();
11630   
11631   ResurrectChessProgram();      /* in case it isn't running */
11632   gameMode = MachinePlaysBlack;
11633   pausing  = FALSE;
11634   ModeHighlight();
11635   SetGameInfo();
11636   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11637   DisplayTitle(buf);
11638   if (first.sendName) 
11639     {
11640       sprintf(buf, "name %s\n", gameInfo.white);
11641       SendToProgram(buf, &first);
11642     }
11643   if (first.sendTime) 
11644     {
11645       if (first.useColors) 
11646         {
11647           SendToProgram("white\n", &first); /*gnu kludge*/
11648         }
11649       SendTimeRemaining(&first, FALSE);
11650     }
11651   if (first.useColors) 
11652     {
11653       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11654     }
11655   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11656   SetMachineThinkingEnables();
11657   first.maybeThinking = TRUE;
11658   StartClocks();
11659   
11660   if (appData.autoFlipView && flipView) 
11661     {
11662       flipView = !flipView;
11663       DrawPosition(FALSE, NULL);
11664       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11665     }
11666   if(bookHit) 
11667     { // [HGM] book: simulate book reply
11668       static char bookMove[MSG_SIZ]; // a bit generous?
11669       
11670       programStats.nodes = programStats.depth = programStats.time 
11671         = programStats.score = programStats.got_only_move = 0;
11672       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11673       
11674       strcpy(bookMove, "move ");
11675       strcat(bookMove, bookHit);
11676       HandleMachineMove(bookMove, &first);
11677     }
11678   return;
11679 }
11680
11681
11682 void
11683 DisplayTwoMachinesTitle()
11684 {
11685     char buf[MSG_SIZ];
11686     if (appData.matchGames > 0) {
11687         if (first.twoMachinesColor[0] == 'w') {
11688             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689                     gameInfo.white, gameInfo.black,
11690                     first.matchWins, second.matchWins,
11691                     matchGame - 1 - (first.matchWins + second.matchWins));
11692         } else {
11693             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11694                     gameInfo.white, gameInfo.black,
11695                     second.matchWins, first.matchWins,
11696                     matchGame - 1 - (first.matchWins + second.matchWins));
11697         }
11698     } else {
11699         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11700     }
11701     DisplayTitle(buf);
11702 }
11703
11704 void
11705 TwoMachinesEvent P((void))
11706 {
11707     int i;
11708     char buf[MSG_SIZ];
11709     ChessProgramState *onmove;
11710     char *bookHit = NULL;
11711
11712     if (appData.noChessProgram) return;
11713
11714     switch (gameMode) {
11715       case TwoMachinesPlay:
11716         return;
11717       case MachinePlaysWhite:
11718       case MachinePlaysBlack:
11719         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11720             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11721             return;
11722         }
11723         /* fall through */
11724       case BeginningOfGame:
11725       case PlayFromGameFile:
11726       case EndOfGame:
11727         EditGameEvent();
11728         if (gameMode != EditGame) return;
11729         break;
11730       case EditPosition:
11731         EditPositionDone(TRUE);
11732         break;
11733       case AnalyzeMode:
11734       case AnalyzeFile:
11735         ExitAnalyzeMode();
11736         break;
11737       case EditGame:
11738       default:
11739         break;
11740     }
11741
11742 //    forwardMostMove = currentMove;
11743     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11744     ResurrectChessProgram();    /* in case first program isn't running */
11745
11746     if (second.pr == NULL) {
11747         StartChessProgram(&second);
11748         if (second.protocolVersion == 1) {
11749           TwoMachinesEventIfReady();
11750         } else {
11751           /* kludge: allow timeout for initial "feature" command */
11752           FreezeUI();
11753           DisplayMessage("", _("Starting second chess program"));
11754           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11755         }
11756         return;
11757     }
11758     DisplayMessage("", "");
11759     InitChessProgram(&second, FALSE);
11760     SendToProgram("force\n", &second);
11761     if (startedFromSetupPosition) {
11762         SendBoard(&second, backwardMostMove);
11763     if (appData.debugMode) {
11764         fprintf(debugFP, "Two Machines\n");
11765     }
11766     }
11767     for (i = backwardMostMove; i < forwardMostMove; i++) {
11768         SendMoveToProgram(i, &second);
11769     }
11770
11771     gameMode = TwoMachinesPlay;
11772     pausing = FALSE;
11773     ModeHighlight();
11774     SetGameInfo();
11775     DisplayTwoMachinesTitle();
11776     firstMove = TRUE;
11777     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11778         onmove = &first;
11779     } else {
11780         onmove = &second;
11781     }
11782
11783     SendToProgram(first.computerString, &first);
11784     if (first.sendName) {
11785       sprintf(buf, "name %s\n", second.tidy);
11786       SendToProgram(buf, &first);
11787     }
11788     SendToProgram(second.computerString, &second);
11789     if (second.sendName) {
11790       sprintf(buf, "name %s\n", first.tidy);
11791       SendToProgram(buf, &second);
11792     }
11793
11794     ResetClocks();
11795     if (!first.sendTime || !second.sendTime) {
11796         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11797         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11798     }
11799     if (onmove->sendTime) {
11800       if (onmove->useColors) {
11801         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11802       }
11803       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11804     }
11805     if (onmove->useColors) {
11806       SendToProgram(onmove->twoMachinesColor, onmove);
11807     }
11808     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11809 //    SendToProgram("go\n", onmove);
11810     onmove->maybeThinking = TRUE;
11811     SetMachineThinkingEnables();
11812
11813     StartClocks();
11814
11815     if(bookHit) { // [HGM] book: simulate book reply
11816         static char bookMove[MSG_SIZ]; // a bit generous?
11817
11818         programStats.nodes = programStats.depth = programStats.time =
11819         programStats.score = programStats.got_only_move = 0;
11820         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11821
11822         strcpy(bookMove, "move ");
11823         strcat(bookMove, bookHit);
11824         savedMessage = bookMove; // args for deferred call
11825         savedState = onmove;
11826         ScheduleDelayedEvent(DeferredBookMove, 1);
11827     }
11828 }
11829
11830 void
11831 TrainingEvent()
11832 {
11833     if (gameMode == Training) {
11834       SetTrainingModeOff();
11835       gameMode = PlayFromGameFile;
11836       DisplayMessage("", _("Training mode off"));
11837     } else {
11838       gameMode = Training;
11839       animateTraining = appData.animate;
11840
11841       /* make sure we are not already at the end of the game */
11842       if (currentMove < forwardMostMove) {
11843         SetTrainingModeOn();
11844         DisplayMessage("", _("Training mode on"));
11845       } else {
11846         gameMode = PlayFromGameFile;
11847         DisplayError(_("Already at end of game"), 0);
11848       }
11849     }
11850     ModeHighlight();
11851 }
11852
11853 void
11854 IcsClientEvent()
11855 {
11856     if (!appData.icsActive) return;
11857     switch (gameMode) {
11858       case IcsPlayingWhite:
11859       case IcsPlayingBlack:
11860       case IcsObserving:
11861       case IcsIdle:
11862       case BeginningOfGame:
11863       case IcsExamining:
11864         return;
11865
11866       case EditGame:
11867         break;
11868
11869       case EditPosition:
11870         EditPositionDone(TRUE);
11871         break;
11872
11873       case AnalyzeMode:
11874       case AnalyzeFile:
11875         ExitAnalyzeMode();
11876         break;
11877
11878       default:
11879         EditGameEvent();
11880         break;
11881     }
11882
11883     gameMode = IcsIdle;
11884     ModeHighlight();
11885     return;
11886 }
11887
11888
11889 void
11890 EditGameEvent()
11891 {
11892     int i;
11893
11894     switch (gameMode) {
11895       case Training:
11896         SetTrainingModeOff();
11897         break;
11898       case MachinePlaysWhite:
11899       case MachinePlaysBlack:
11900       case BeginningOfGame:
11901         SendToProgram("force\n", &first);
11902         SetUserThinkingEnables();
11903         break;
11904       case PlayFromGameFile:
11905         (void) StopLoadGameTimer();
11906         if (gameFileFP != NULL) {
11907             gameFileFP = NULL;
11908         }
11909         break;
11910       case EditPosition:
11911         EditPositionDone(TRUE);
11912         break;
11913       case AnalyzeMode:
11914       case AnalyzeFile:
11915         ExitAnalyzeMode();
11916         SendToProgram("force\n", &first);
11917         break;
11918       case TwoMachinesPlay:
11919         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11920         ResurrectChessProgram();
11921         SetUserThinkingEnables();
11922         break;
11923       case EndOfGame:
11924         ResurrectChessProgram();
11925         break;
11926       case IcsPlayingBlack:
11927       case IcsPlayingWhite:
11928         DisplayError(_("Warning: You are still playing a game"), 0);
11929         break;
11930       case IcsObserving:
11931         DisplayError(_("Warning: You are still observing a game"), 0);
11932         break;
11933       case IcsExamining:
11934         DisplayError(_("Warning: You are still examining a game"), 0);
11935         break;
11936       case IcsIdle:
11937         break;
11938       case EditGame:
11939       default:
11940         return;
11941     }
11942
11943     pausing = FALSE;
11944     StopClocks();
11945     first.offeredDraw = second.offeredDraw = 0;
11946
11947     if (gameMode == PlayFromGameFile) {
11948         whiteTimeRemaining = timeRemaining[0][currentMove];
11949         blackTimeRemaining = timeRemaining[1][currentMove];
11950         DisplayTitle("");
11951     }
11952
11953     if (gameMode == MachinePlaysWhite ||
11954         gameMode == MachinePlaysBlack ||
11955         gameMode == TwoMachinesPlay ||
11956         gameMode == EndOfGame) {
11957         i = forwardMostMove;
11958         while (i > currentMove) {
11959             SendToProgram("undo\n", &first);
11960             i--;
11961         }
11962         whiteTimeRemaining = timeRemaining[0][currentMove];
11963         blackTimeRemaining = timeRemaining[1][currentMove];
11964         DisplayBothClocks();
11965         if (whiteFlag || blackFlag) {
11966             whiteFlag = blackFlag = 0;
11967         }
11968         DisplayTitle("");
11969     }
11970
11971     gameMode = EditGame;
11972     ModeHighlight();
11973     SetGameInfo();
11974 }
11975
11976
11977 void
11978 EditPositionEvent()
11979 {
11980     if (gameMode == EditPosition) {
11981         EditGameEvent();
11982         return;
11983     }
11984
11985     EditGameEvent();
11986     if (gameMode != EditGame) return;
11987
11988     gameMode = EditPosition;
11989     ModeHighlight();
11990     SetGameInfo();
11991     if (currentMove > 0)
11992       CopyBoard(boards[0], boards[currentMove]);
11993
11994     blackPlaysFirst = !WhiteOnMove(currentMove);
11995     ResetClocks();
11996     currentMove = forwardMostMove = backwardMostMove = 0;
11997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11998     DisplayMove(-1);
11999 }
12000
12001 void
12002 ExitAnalyzeMode()
12003 {
12004     /* [DM] icsEngineAnalyze - possible call from other functions */
12005     if (appData.icsEngineAnalyze) {
12006         appData.icsEngineAnalyze = FALSE;
12007
12008         DisplayMessage("",_("Close ICS engine analyze..."));
12009     }
12010     if (first.analysisSupport && first.analyzing) {
12011       SendToProgram("exit\n", &first);
12012       first.analyzing = FALSE;
12013     }
12014     thinkOutput[0] = NULLCHAR;
12015 }
12016
12017 void
12018 EditPositionDone(Boolean fakeRights)
12019 {
12020     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12021
12022     startedFromSetupPosition = TRUE;
12023     InitChessProgram(&first, FALSE);
12024     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12025       boards[0][EP_STATUS] = EP_NONE;
12026       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12027     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12028         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12029         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12030       } else boards[0][CASTLING][2] = NoRights;
12031     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12032         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12033         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12034       } else boards[0][CASTLING][5] = NoRights;
12035     }
12036     SendToProgram("force\n", &first);
12037     if (blackPlaysFirst) {
12038         strcpy(moveList[0], "");
12039         strcpy(parseList[0], "");
12040         currentMove = forwardMostMove = backwardMostMove = 1;
12041         CopyBoard(boards[1], boards[0]);
12042     } else {
12043         currentMove = forwardMostMove = backwardMostMove = 0;
12044     }
12045     SendBoard(&first, forwardMostMove);
12046     if (appData.debugMode) {
12047         fprintf(debugFP, "EditPosDone\n");
12048     }
12049     DisplayTitle("");
12050     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12051     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12052     gameMode = EditGame;
12053     ModeHighlight();
12054     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12055     ClearHighlights(); /* [AS] */
12056 }
12057
12058 /* Pause for `ms' milliseconds */
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12060 void
12061 TimeDelay(ms)
12062      long ms;
12063 {
12064     TimeMark m1, m2;
12065
12066     GetTimeMark(&m1);
12067     do {
12068         GetTimeMark(&m2);
12069     } while (SubtractTimeMarks(&m2, &m1) < ms);
12070 }
12071
12072 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12073 void
12074 SendMultiLineToICS(buf)
12075      char *buf;
12076 {
12077     char temp[MSG_SIZ+1], *p;
12078     int len;
12079
12080     len = strlen(buf);
12081     if (len > MSG_SIZ)
12082       len = MSG_SIZ;
12083
12084     strncpy(temp, buf, len);
12085     temp[len] = 0;
12086
12087     p = temp;
12088     while (*p) {
12089         if (*p == '\n' || *p == '\r')
12090           *p = ' ';
12091         ++p;
12092     }
12093
12094     strcat(temp, "\n");
12095     SendToICS(temp);
12096     SendToPlayer(temp, strlen(temp));
12097 }
12098
12099 void
12100 SetWhiteToPlayEvent()
12101 {
12102     if (gameMode == EditPosition) {
12103         blackPlaysFirst = FALSE;
12104         DisplayBothClocks();    /* works because currentMove is 0 */
12105     } else if (gameMode == IcsExamining) {
12106         SendToICS(ics_prefix);
12107         SendToICS("tomove white\n");
12108     }
12109 }
12110
12111 void
12112 SetBlackToPlayEvent()
12113 {
12114     if (gameMode == EditPosition) {
12115         blackPlaysFirst = TRUE;
12116         currentMove = 1;        /* kludge */
12117         DisplayBothClocks();
12118         currentMove = 0;
12119     } else if (gameMode == IcsExamining) {
12120         SendToICS(ics_prefix);
12121         SendToICS("tomove black\n");
12122     }
12123 }
12124
12125 void
12126 EditPositionMenuEvent(selection, x, y)
12127      ChessSquare selection;
12128      int x, y;
12129 {
12130     char buf[MSG_SIZ];
12131     ChessSquare piece = boards[0][y][x];
12132
12133     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12134
12135     switch (selection) {
12136       case ClearBoard:
12137         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12138             SendToICS(ics_prefix);
12139             SendToICS("bsetup clear\n");
12140         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12141             SendToICS(ics_prefix);
12142             SendToICS("clearboard\n");
12143         } else {
12144             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12145                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12146                 for (y = 0; y < BOARD_HEIGHT; y++) {
12147                     if (gameMode == IcsExamining) {
12148                         if (boards[currentMove][y][x] != EmptySquare) {
12149                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12150                                     AAA + x, ONE + y);
12151                             SendToICS(buf);
12152                         }
12153                     } else {
12154                         boards[0][y][x] = p;
12155                     }
12156                 }
12157             }
12158         }
12159         if (gameMode == EditPosition) {
12160             DrawPosition(FALSE, boards[0]);
12161         }
12162         break;
12163
12164       case WhitePlay:
12165         SetWhiteToPlayEvent();
12166         break;
12167
12168       case BlackPlay:
12169         SetBlackToPlayEvent();
12170         break;
12171
12172       case EmptySquare:
12173         if (gameMode == IcsExamining) {
12174             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12175             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12176             SendToICS(buf);
12177         } else {
12178             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12179                 if(x == BOARD_LEFT-2) {
12180                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12181                     boards[0][y][1] = 0;
12182                 } else
12183                 if(x == BOARD_RGHT+1) {
12184                     if(y >= gameInfo.holdingsSize) break;
12185                     boards[0][y][BOARD_WIDTH-2] = 0;
12186                 } else break;
12187             }
12188             boards[0][y][x] = EmptySquare;
12189             DrawPosition(FALSE, boards[0]);
12190         }
12191         break;
12192
12193       case PromotePiece:
12194         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12195            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12196             selection = (ChessSquare) (PROMOTED piece);
12197         } else if(piece == EmptySquare) selection = WhiteSilver;
12198         else selection = (ChessSquare)((int)piece - 1);
12199         goto defaultlabel;
12200
12201       case DemotePiece:
12202         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12203            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12204             selection = (ChessSquare) (DEMOTED piece);
12205         } else if(piece == EmptySquare) selection = BlackSilver;
12206         else selection = (ChessSquare)((int)piece + 1);
12207         goto defaultlabel;
12208
12209       case WhiteQueen:
12210       case BlackQueen:
12211         if(gameInfo.variant == VariantShatranj ||
12212            gameInfo.variant == VariantXiangqi  ||
12213            gameInfo.variant == VariantCourier  ||
12214            gameInfo.variant == VariantMakruk     )
12215             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12216         goto defaultlabel;
12217
12218       case WhiteKing:
12219       case BlackKing:
12220         if(gameInfo.variant == VariantXiangqi)
12221             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12222         if(gameInfo.variant == VariantKnightmate)
12223             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12224       default:
12225         defaultlabel:
12226         if (gameMode == IcsExamining) {
12227             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12228             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12229                     PieceToChar(selection), AAA + x, ONE + y);
12230             SendToICS(buf);
12231         } else {
12232             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12233                 int n;
12234                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12235                     n = PieceToNumber(selection - BlackPawn);
12236                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12237                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12238                     boards[0][BOARD_HEIGHT-1-n][1]++;
12239                 } else
12240                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12241                     n = PieceToNumber(selection);
12242                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12243                     boards[0][n][BOARD_WIDTH-1] = selection;
12244                     boards[0][n][BOARD_WIDTH-2]++;
12245                 }
12246             } else
12247             boards[0][y][x] = selection;
12248             DrawPosition(TRUE, boards[0]);
12249         }
12250         break;
12251     }
12252 }
12253
12254
12255 void
12256 DropMenuEvent(selection, x, y)
12257      ChessSquare selection;
12258      int x, y;
12259 {
12260     ChessMove moveType;
12261
12262     switch (gameMode) {
12263       case IcsPlayingWhite:
12264       case MachinePlaysBlack:
12265         if (!WhiteOnMove(currentMove)) {
12266             DisplayMoveError(_("It is Black's turn"));
12267             return;
12268         }
12269         moveType = WhiteDrop;
12270         break;
12271       case IcsPlayingBlack:
12272       case MachinePlaysWhite:
12273         if (WhiteOnMove(currentMove)) {
12274             DisplayMoveError(_("It is White's turn"));
12275             return;
12276         }
12277         moveType = BlackDrop;
12278         break;
12279       case EditGame:
12280         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12281         break;
12282       default:
12283         return;
12284     }
12285
12286     if (moveType == BlackDrop && selection < BlackPawn) {
12287       selection = (ChessSquare) ((int) selection
12288                                  + (int) BlackPawn - (int) WhitePawn);
12289     }
12290     if (boards[currentMove][y][x] != EmptySquare) {
12291         DisplayMoveError(_("That square is occupied"));
12292         return;
12293     }
12294
12295     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12296 }
12297
12298 void
12299 AcceptEvent()
12300 {
12301     /* Accept a pending offer of any kind from opponent */
12302
12303     if (appData.icsActive) {
12304         SendToICS(ics_prefix);
12305         SendToICS("accept\n");
12306     } else if (cmailMsgLoaded) {
12307         if (currentMove == cmailOldMove &&
12308             commentList[cmailOldMove] != NULL &&
12309             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12310                    "Black offers a draw" : "White offers a draw")) {
12311             TruncateGame();
12312             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12314         } else {
12315             DisplayError(_("There is no pending offer on this move"), 0);
12316             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12317         }
12318     } else {
12319         /* Not used for offers from chess program */
12320     }
12321 }
12322
12323 void
12324 DeclineEvent()
12325 {
12326     /* Decline a pending offer of any kind from opponent */
12327
12328     if (appData.icsActive) {
12329         SendToICS(ics_prefix);
12330         SendToICS("decline\n");
12331     } else if (cmailMsgLoaded) {
12332         if (currentMove == cmailOldMove &&
12333             commentList[cmailOldMove] != NULL &&
12334             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12335                    "Black offers a draw" : "White offers a draw")) {
12336 #ifdef NOTDEF
12337             AppendComment(cmailOldMove, "Draw declined", TRUE);
12338             DisplayComment(cmailOldMove - 1, "Draw declined");
12339 #endif /*NOTDEF*/
12340         } else {
12341             DisplayError(_("There is no pending offer on this move"), 0);
12342         }
12343     } else {
12344         /* Not used for offers from chess program */
12345     }
12346 }
12347
12348 void
12349 RematchEvent()
12350 {
12351     /* Issue ICS rematch command */
12352     if (appData.icsActive) {
12353         SendToICS(ics_prefix);
12354         SendToICS("rematch\n");
12355     }
12356 }
12357
12358 void
12359 CallFlagEvent()
12360 {
12361     /* Call your opponent's flag (claim a win on time) */
12362     if (appData.icsActive) {
12363         SendToICS(ics_prefix);
12364         SendToICS("flag\n");
12365     } else {
12366         switch (gameMode) {
12367           default:
12368             return;
12369           case MachinePlaysWhite:
12370             if (whiteFlag) {
12371                 if (blackFlag)
12372                   GameEnds(GameIsDrawn, "Both players ran out of time",
12373                            GE_PLAYER);
12374                 else
12375                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12376             } else {
12377                 DisplayError(_("Your opponent is not out of time"), 0);
12378             }
12379             break;
12380           case MachinePlaysBlack:
12381             if (blackFlag) {
12382                 if (whiteFlag)
12383                   GameEnds(GameIsDrawn, "Both players ran out of time",
12384                            GE_PLAYER);
12385                 else
12386                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12387             } else {
12388                 DisplayError(_("Your opponent is not out of time"), 0);
12389             }
12390             break;
12391         }
12392     }
12393 }
12394
12395 void
12396 DrawEvent()
12397 {
12398     /* Offer draw or accept pending draw offer from opponent */
12399
12400     if (appData.icsActive) {
12401         /* Note: tournament rules require draw offers to be
12402            made after you make your move but before you punch
12403            your clock.  Currently ICS doesn't let you do that;
12404            instead, you immediately punch your clock after making
12405            a move, but you can offer a draw at any time. */
12406
12407         SendToICS(ics_prefix);
12408         SendToICS("draw\n");
12409         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12410     } else if (cmailMsgLoaded) {
12411         if (currentMove == cmailOldMove &&
12412             commentList[cmailOldMove] != NULL &&
12413             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12414                    "Black offers a draw" : "White offers a draw")) {
12415             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12416             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12417         } else if (currentMove == cmailOldMove + 1) {
12418             char *offer = WhiteOnMove(cmailOldMove) ?
12419               "White offers a draw" : "Black offers a draw";
12420             AppendComment(currentMove, offer, TRUE);
12421             DisplayComment(currentMove - 1, offer);
12422             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12423         } else {
12424             DisplayError(_("You must make your move before offering a draw"), 0);
12425             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12426         }
12427     } else if (first.offeredDraw) {
12428         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12429     } else {
12430         if (first.sendDrawOffers) {
12431             SendToProgram("draw\n", &first);
12432             userOfferedDraw = TRUE;
12433         }
12434     }
12435 }
12436
12437 void
12438 AdjournEvent()
12439 {
12440     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12441
12442     if (appData.icsActive) {
12443         SendToICS(ics_prefix);
12444         SendToICS("adjourn\n");
12445     } else {
12446         /* Currently GNU Chess doesn't offer or accept Adjourns */
12447     }
12448 }
12449
12450
12451 void
12452 AbortEvent()
12453 {
12454     /* Offer Abort or accept pending Abort offer from opponent */
12455
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("abort\n");
12459     } else {
12460         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12461     }
12462 }
12463
12464 void
12465 ResignEvent()
12466 {
12467     /* Resign.  You can do this even if it's not your turn. */
12468
12469     if (appData.icsActive) {
12470         SendToICS(ics_prefix);
12471         SendToICS("resign\n");
12472     } else {
12473         switch (gameMode) {
12474           case MachinePlaysWhite:
12475             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12476             break;
12477           case MachinePlaysBlack:
12478             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12479             break;
12480           case EditGame:
12481             if (cmailMsgLoaded) {
12482                 TruncateGame();
12483                 if (WhiteOnMove(cmailOldMove)) {
12484                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12485                 } else {
12486                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12487                 }
12488                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12489             }
12490             break;
12491           default:
12492             break;
12493         }
12494     }
12495 }
12496
12497
12498 void
12499 StopObservingEvent()
12500 {
12501     /* Stop observing current games */
12502     SendToICS(ics_prefix);
12503     SendToICS("unobserve\n");
12504 }
12505
12506 void
12507 StopExaminingEvent()
12508 {
12509     /* Stop observing current game */
12510     SendToICS(ics_prefix);
12511     SendToICS("unexamine\n");
12512 }
12513
12514 void
12515 ForwardInner(target)
12516      int target;
12517 {
12518     int limit;
12519
12520     if (appData.debugMode)
12521         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12522                 target, currentMove, forwardMostMove);
12523
12524     if (gameMode == EditPosition)
12525       return;
12526
12527     if (gameMode == PlayFromGameFile && !pausing)
12528       PauseEvent();
12529
12530     if (gameMode == IcsExamining && pausing)
12531       limit = pauseExamForwardMostMove;
12532     else
12533       limit = forwardMostMove;
12534
12535     if (target > limit) target = limit;
12536
12537     if (target > 0 && moveList[target - 1][0]) {
12538         int fromX, fromY, toX, toY;
12539         toX = moveList[target - 1][2] - AAA;
12540         toY = moveList[target - 1][3] - ONE;
12541         if (moveList[target - 1][1] == '@') {
12542             if (appData.highlightLastMove) {
12543                 SetHighlights(-1, -1, toX, toY);
12544             }
12545         } else {
12546             fromX = moveList[target - 1][0] - AAA;
12547             fromY = moveList[target - 1][1] - ONE;
12548             if (target == currentMove + 1) {
12549                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12550             }
12551             if (appData.highlightLastMove) {
12552                 SetHighlights(fromX, fromY, toX, toY);
12553             }
12554         }
12555     }
12556     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12557         gameMode == Training || gameMode == PlayFromGameFile ||
12558         gameMode == AnalyzeFile) {
12559         while (currentMove < target) {
12560             SendMoveToProgram(currentMove++, &first);
12561         }
12562     } else {
12563         currentMove = target;
12564     }
12565
12566     if (gameMode == EditGame || gameMode == EndOfGame) {
12567         whiteTimeRemaining = timeRemaining[0][currentMove];
12568         blackTimeRemaining = timeRemaining[1][currentMove];
12569     }
12570     DisplayBothClocks();
12571     DisplayMove(currentMove - 1);
12572     DrawPosition(FALSE, boards[currentMove]);
12573     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12574     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12575         DisplayComment(currentMove - 1, commentList[currentMove]);
12576     }
12577 }
12578
12579
12580 void
12581 ForwardEvent()
12582 {
12583     if (gameMode == IcsExamining && !pausing) {
12584         SendToICS(ics_prefix);
12585         SendToICS("forward\n");
12586     } else {
12587         ForwardInner(currentMove + 1);
12588     }
12589 }
12590
12591 void
12592 ToEndEvent()
12593 {
12594     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12595         /* to optimze, we temporarily turn off analysis mode while we feed
12596          * the remaining moves to the engine. Otherwise we get analysis output
12597          * after each move.
12598          */
12599         if (first.analysisSupport) {
12600           SendToProgram("exit\nforce\n", &first);
12601           first.analyzing = FALSE;
12602         }
12603     }
12604
12605     if (gameMode == IcsExamining && !pausing) {
12606         SendToICS(ics_prefix);
12607         SendToICS("forward 999999\n");
12608     } else {
12609         ForwardInner(forwardMostMove);
12610     }
12611
12612     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12613         /* we have fed all the moves, so reactivate analysis mode */
12614         SendToProgram("analyze\n", &first);
12615         first.analyzing = TRUE;
12616         /*first.maybeThinking = TRUE;*/
12617         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12618     }
12619 }
12620
12621 void
12622 BackwardInner(target)
12623      int target;
12624 {
12625     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12626
12627     if (appData.debugMode)
12628         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12629                 target, currentMove, forwardMostMove);
12630
12631     if (gameMode == EditPosition) return;
12632     if (currentMove <= backwardMostMove) {
12633         ClearHighlights();
12634         DrawPosition(full_redraw, boards[currentMove]);
12635         return;
12636     }
12637     if (gameMode == PlayFromGameFile && !pausing)
12638       PauseEvent();
12639
12640     if (moveList[target][0]) {
12641         int fromX, fromY, toX, toY;
12642         toX = moveList[target][2] - AAA;
12643         toY = moveList[target][3] - ONE;
12644         if (moveList[target][1] == '@') {
12645             if (appData.highlightLastMove) {
12646                 SetHighlights(-1, -1, toX, toY);
12647             }
12648         } else {
12649             fromX = moveList[target][0] - AAA;
12650             fromY = moveList[target][1] - ONE;
12651             if (target == currentMove - 1) {
12652                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12653             }
12654             if (appData.highlightLastMove) {
12655                 SetHighlights(fromX, fromY, toX, toY);
12656             }
12657         }
12658     }
12659     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12660         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12661         while (currentMove > target) {
12662             SendToProgram("undo\n", &first);
12663             currentMove--;
12664         }
12665     } else {
12666         currentMove = target;
12667     }
12668
12669     if (gameMode == EditGame || gameMode == EndOfGame) {
12670         whiteTimeRemaining = timeRemaining[0][currentMove];
12671         blackTimeRemaining = timeRemaining[1][currentMove];
12672     }
12673     DisplayBothClocks();
12674     DisplayMove(currentMove - 1);
12675     DrawPosition(full_redraw, boards[currentMove]);
12676     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12677     // [HGM] PV info: routine tests if comment empty
12678     DisplayComment(currentMove - 1, commentList[currentMove]);
12679 }
12680
12681 void
12682 BackwardEvent()
12683 {
12684     if (gameMode == IcsExamining && !pausing) {
12685         SendToICS(ics_prefix);
12686         SendToICS("backward\n");
12687     } else {
12688         BackwardInner(currentMove - 1);
12689     }
12690 }
12691
12692 void
12693 ToStartEvent()
12694 {
12695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12696         /* to optimize, we temporarily turn off analysis mode while we undo
12697          * all the moves. Otherwise we get analysis output after each undo.
12698          */
12699         if (first.analysisSupport) {
12700           SendToProgram("exit\nforce\n", &first);
12701           first.analyzing = FALSE;
12702         }
12703     }
12704
12705     if (gameMode == IcsExamining && !pausing) {
12706         SendToICS(ics_prefix);
12707         SendToICS("backward 999999\n");
12708     } else {
12709         BackwardInner(backwardMostMove);
12710     }
12711
12712     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12713         /* we have fed all the moves, so reactivate analysis mode */
12714         SendToProgram("analyze\n", &first);
12715         first.analyzing = TRUE;
12716         /*first.maybeThinking = TRUE;*/
12717         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12718     }
12719 }
12720
12721 void
12722 ToNrEvent(int to)
12723 {
12724   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12725   if (to >= forwardMostMove) to = forwardMostMove;
12726   if (to <= backwardMostMove) to = backwardMostMove;
12727   if (to < currentMove) {
12728     BackwardInner(to);
12729   } else {
12730     ForwardInner(to);
12731   }
12732 }
12733
12734 void
12735 RevertEvent()
12736 {
12737     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12738         return;
12739     }
12740     if (gameMode != IcsExamining) {
12741         DisplayError(_("You are not examining a game"), 0);
12742         return;
12743     }
12744     if (pausing) {
12745         DisplayError(_("You can't revert while pausing"), 0);
12746         return;
12747     }
12748     SendToICS(ics_prefix);
12749     SendToICS("revert\n");
12750 }
12751
12752 void
12753 RetractMoveEvent()
12754 {
12755     switch (gameMode) {
12756       case MachinePlaysWhite:
12757       case MachinePlaysBlack:
12758         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12759             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12760             return;
12761         }
12762         if (forwardMostMove < 2) return;
12763         currentMove = forwardMostMove = forwardMostMove - 2;
12764         whiteTimeRemaining = timeRemaining[0][currentMove];
12765         blackTimeRemaining = timeRemaining[1][currentMove];
12766         DisplayBothClocks();
12767         DisplayMove(currentMove - 1);
12768         ClearHighlights();/*!! could figure this out*/
12769         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12770         SendToProgram("remove\n", &first);
12771         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12772         break;
12773
12774       case BeginningOfGame:
12775       default:
12776         break;
12777
12778       case IcsPlayingWhite:
12779       case IcsPlayingBlack:
12780         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12781             SendToICS(ics_prefix);
12782             SendToICS("takeback 2\n");
12783         } else {
12784             SendToICS(ics_prefix);
12785             SendToICS("takeback 1\n");
12786         }
12787         break;
12788     }
12789 }
12790
12791 void
12792 MoveNowEvent()
12793 {
12794     ChessProgramState *cps;
12795
12796     switch (gameMode) {
12797       case MachinePlaysWhite:
12798         if (!WhiteOnMove(forwardMostMove)) {
12799             DisplayError(_("It is your turn"), 0);
12800             return;
12801         }
12802         cps = &first;
12803         break;
12804       case MachinePlaysBlack:
12805         if (WhiteOnMove(forwardMostMove)) {
12806             DisplayError(_("It is your turn"), 0);
12807             return;
12808         }
12809         cps = &first;
12810         break;
12811       case TwoMachinesPlay:
12812         if (WhiteOnMove(forwardMostMove) ==
12813             (first.twoMachinesColor[0] == 'w')) {
12814             cps = &first;
12815         } else {
12816             cps = &second;
12817         }
12818         break;
12819       case BeginningOfGame:
12820       default:
12821         return;
12822     }
12823     SendToProgram("?\n", cps);
12824 }
12825
12826 void
12827 TruncateGameEvent()
12828 {
12829     EditGameEvent();
12830     if (gameMode != EditGame) return;
12831     TruncateGame();
12832 }
12833
12834 void
12835 TruncateGame()
12836 {
12837     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12838     if (forwardMostMove > currentMove) {
12839         if (gameInfo.resultDetails != NULL) {
12840             free(gameInfo.resultDetails);
12841             gameInfo.resultDetails = NULL;
12842             gameInfo.result = GameUnfinished;
12843         }
12844         forwardMostMove = currentMove;
12845         HistorySet(parseList, backwardMostMove, forwardMostMove,
12846                    currentMove-1);
12847     }
12848 }
12849
12850 void
12851 HintEvent()
12852 {
12853     if (appData.noChessProgram) return;
12854     switch (gameMode) {
12855       case MachinePlaysWhite:
12856         if (WhiteOnMove(forwardMostMove)) {
12857             DisplayError(_("Wait until your turn"), 0);
12858             return;
12859         }
12860         break;
12861       case BeginningOfGame:
12862       case MachinePlaysBlack:
12863         if (!WhiteOnMove(forwardMostMove)) {
12864             DisplayError(_("Wait until your turn"), 0);
12865             return;
12866         }
12867         break;
12868       default:
12869         DisplayError(_("No hint available"), 0);
12870         return;
12871     }
12872     SendToProgram("hint\n", &first);
12873     hintRequested = TRUE;
12874 }
12875
12876 void
12877 BookEvent()
12878 {
12879     if (appData.noChessProgram) return;
12880     switch (gameMode) {
12881       case MachinePlaysWhite:
12882         if (WhiteOnMove(forwardMostMove)) {
12883             DisplayError(_("Wait until your turn"), 0);
12884             return;
12885         }
12886         break;
12887       case BeginningOfGame:
12888       case MachinePlaysBlack:
12889         if (!WhiteOnMove(forwardMostMove)) {
12890             DisplayError(_("Wait until your turn"), 0);
12891             return;
12892         }
12893         break;
12894       case EditPosition:
12895         EditPositionDone(TRUE);
12896         break;
12897       case TwoMachinesPlay:
12898         return;
12899       default:
12900         break;
12901     }
12902     SendToProgram("bk\n", &first);
12903     bookOutput[0] = NULLCHAR;
12904     bookRequested = TRUE;
12905 }
12906
12907 void
12908 AboutGameEvent()
12909 {
12910     char *tags = PGNTags(&gameInfo);
12911     TagsPopUp(tags, CmailMsg());
12912     free(tags);
12913 }
12914
12915 /* end button procedures */
12916
12917 void
12918 PrintPosition(fp, move)
12919      FILE *fp;
12920      int move;
12921 {
12922     int i, j;
12923
12924     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12925         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12926             char c = PieceToChar(boards[move][i][j]);
12927             fputc(c == 'x' ? '.' : c, fp);
12928             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12929         }
12930     }
12931     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12932       fprintf(fp, "white to play\n");
12933     else
12934       fprintf(fp, "black to play\n");
12935 }
12936
12937 void
12938 PrintOpponents(fp)
12939      FILE *fp;
12940 {
12941     if (gameInfo.white != NULL) {
12942         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12943     } else {
12944         fprintf(fp, "\n");
12945     }
12946 }
12947
12948 /* Find last component of program's own name, using some heuristics */
12949 void
12950 TidyProgramName(prog, host, buf)
12951      char *prog, *host, buf[MSG_SIZ];
12952 {
12953     char *p, *q;
12954     int local = (strcmp(host, "localhost") == 0);
12955     while (!local && (p = strchr(prog, ';')) != NULL) {
12956         p++;
12957         while (*p == ' ') p++;
12958         prog = p;
12959     }
12960     if (*prog == '"' || *prog == '\'') {
12961         q = strchr(prog + 1, *prog);
12962     } else {
12963         q = strchr(prog, ' ');
12964     }
12965     if (q == NULL) q = prog + strlen(prog);
12966     p = q;
12967     while (p >= prog && *p != '/' && *p != '\\') p--;
12968     p++;
12969     if(p == prog && *p == '"') p++;
12970     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12971     memcpy(buf, p, q - p);
12972     buf[q - p] = NULLCHAR;
12973     if (!local) {
12974         strcat(buf, "@");
12975         strcat(buf, host);
12976     }
12977 }
12978
12979 char *
12980 TimeControlTagValue()
12981 {
12982     char buf[MSG_SIZ];
12983     if (!appData.clockMode) {
12984         strcpy(buf, "-");
12985     } else if (movesPerSession > 0) {
12986         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12987     } else if (timeIncrement == 0) {
12988         sprintf(buf, "%ld", timeControl/1000);
12989     } else {
12990         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12991     }
12992     return StrSave(buf);
12993 }
12994
12995 void
12996 SetGameInfo()
12997 {
12998     /* This routine is used only for certain modes */
12999     VariantClass v = gameInfo.variant;
13000     ChessMove r = GameUnfinished;
13001     char *p = NULL;
13002
13003     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13004         r = gameInfo.result; 
13005         p = gameInfo.resultDetails; 
13006         gameInfo.resultDetails = NULL;
13007     }
13008     ClearGameInfo(&gameInfo);
13009     gameInfo.variant = v;
13010
13011     switch (gameMode) {
13012       case MachinePlaysWhite:
13013         gameInfo.event = StrSave( appData.pgnEventHeader );
13014         gameInfo.site = StrSave(HostName());
13015         gameInfo.date = PGNDate();
13016         gameInfo.round = StrSave("-");
13017         gameInfo.white = StrSave(first.tidy);
13018         gameInfo.black = StrSave(UserName());
13019         gameInfo.timeControl = TimeControlTagValue();
13020         break;
13021
13022       case MachinePlaysBlack:
13023         gameInfo.event = StrSave( appData.pgnEventHeader );
13024         gameInfo.site = StrSave(HostName());
13025         gameInfo.date = PGNDate();
13026         gameInfo.round = StrSave("-");
13027         gameInfo.white = StrSave(UserName());
13028         gameInfo.black = StrSave(first.tidy);
13029         gameInfo.timeControl = TimeControlTagValue();
13030         break;
13031
13032       case TwoMachinesPlay:
13033         gameInfo.event = StrSave( appData.pgnEventHeader );
13034         gameInfo.site = StrSave(HostName());
13035         gameInfo.date = PGNDate();
13036         if (matchGame > 0) {
13037             char buf[MSG_SIZ];
13038             sprintf(buf, "%d", matchGame);
13039             gameInfo.round = StrSave(buf);
13040         } else {
13041             gameInfo.round = StrSave("-");
13042         }
13043         if (first.twoMachinesColor[0] == 'w') {
13044             gameInfo.white = StrSave(first.tidy);
13045             gameInfo.black = StrSave(second.tidy);
13046         } else {
13047             gameInfo.white = StrSave(second.tidy);
13048             gameInfo.black = StrSave(first.tidy);
13049         }
13050         gameInfo.timeControl = TimeControlTagValue();
13051         break;
13052
13053       case EditGame:
13054         gameInfo.event = StrSave("Edited game");
13055         gameInfo.site = StrSave(HostName());
13056         gameInfo.date = PGNDate();
13057         gameInfo.round = StrSave("-");
13058         gameInfo.white = StrSave("-");
13059         gameInfo.black = StrSave("-");
13060         gameInfo.result = r;
13061         gameInfo.resultDetails = p;
13062         break;
13063
13064       case EditPosition:
13065         gameInfo.event = StrSave("Edited position");
13066         gameInfo.site = StrSave(HostName());
13067         gameInfo.date = PGNDate();
13068         gameInfo.round = StrSave("-");
13069         gameInfo.white = StrSave("-");
13070         gameInfo.black = StrSave("-");
13071         break;
13072
13073       case IcsPlayingWhite:
13074       case IcsPlayingBlack:
13075       case IcsObserving:
13076       case IcsExamining:
13077         break;
13078
13079       case PlayFromGameFile:
13080         gameInfo.event = StrSave("Game from non-PGN file");
13081         gameInfo.site = StrSave(HostName());
13082         gameInfo.date = PGNDate();
13083         gameInfo.round = StrSave("-");
13084         gameInfo.white = StrSave("?");
13085         gameInfo.black = StrSave("?");
13086         break;
13087
13088       default:
13089         break;
13090     }
13091 }
13092
13093 void
13094 ReplaceComment(index, text)
13095      int index;
13096      char *text;
13097 {
13098     int len;
13099
13100     while (*text == '\n') text++;
13101     len = strlen(text);
13102     while (len > 0 && text[len - 1] == '\n') len--;
13103
13104     if (commentList[index] != NULL)
13105       free(commentList[index]);
13106
13107     if (len == 0) {
13108         commentList[index] = NULL;
13109         return;
13110     }
13111   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13112       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13113       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13114     commentList[index] = (char *) malloc(len + 2);
13115     strncpy(commentList[index], text, len);
13116     commentList[index][len] = '\n';
13117     commentList[index][len + 1] = NULLCHAR;
13118   } else { 
13119     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13120     char *p;
13121     commentList[index] = (char *) malloc(len + 6);
13122     strcpy(commentList[index], "{\n");
13123     strncpy(commentList[index]+2, text, len);
13124     commentList[index][len+2] = NULLCHAR;
13125     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13126     strcat(commentList[index], "\n}\n");
13127   }
13128 }
13129
13130 void
13131 CrushCRs(text)
13132      char *text;
13133 {
13134   char *p = text;
13135   char *q = text;
13136   char ch;
13137
13138   do {
13139     ch = *p++;
13140     if (ch == '\r') continue;
13141     *q++ = ch;
13142   } while (ch != '\0');
13143 }
13144
13145 void
13146 AppendComment(index, text, addBraces)
13147      int index;
13148      char *text;
13149      Boolean addBraces; // [HGM] braces: tells if we should add {}
13150 {
13151     int oldlen, len;
13152     char *old;
13153
13154 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13155     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13156
13157     CrushCRs(text);
13158     while (*text == '\n') text++;
13159     len = strlen(text);
13160     while (len > 0 && text[len - 1] == '\n') len--;
13161
13162     if (len == 0) return;
13163
13164     if (commentList[index] != NULL) {
13165         old = commentList[index];
13166         oldlen = strlen(old);
13167         while(commentList[index][oldlen-1] ==  '\n')
13168           commentList[index][--oldlen] = NULLCHAR;
13169         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13170         strcpy(commentList[index], old);
13171         free(old);
13172         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13173         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13174           if(addBraces) addBraces = FALSE; else { text++; len--; }
13175           while (*text == '\n') { text++; len--; }
13176           commentList[index][--oldlen] = NULLCHAR;
13177       }
13178         if(addBraces) strcat(commentList[index], "\n{\n");
13179         else          strcat(commentList[index], "\n");
13180         strcat(commentList[index], text);
13181         if(addBraces) strcat(commentList[index], "\n}\n");
13182         else          strcat(commentList[index], "\n");
13183     } else {
13184         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13185         if(addBraces)
13186              strcpy(commentList[index], "{\n");
13187         else commentList[index][0] = NULLCHAR;
13188         strcat(commentList[index], text);
13189         strcat(commentList[index], "\n");
13190         if(addBraces) strcat(commentList[index], "}\n");
13191     }
13192 }
13193
13194 static char * FindStr( char * text, char * sub_text )
13195 {
13196     char * result = strstr( text, sub_text );
13197
13198     if( result != NULL ) {
13199         result += strlen( sub_text );
13200     }
13201
13202     return result;
13203 }
13204
13205 /* [AS] Try to extract PV info from PGN comment */
13206 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13207 char *GetInfoFromComment( int index, char * text )
13208 {
13209     char * sep = text;
13210
13211     if( text != NULL && index > 0 ) {
13212         int score = 0;
13213         int depth = 0;
13214         int time = -1, sec = 0, deci;
13215         char * s_eval = FindStr( text, "[%eval " );
13216         char * s_emt = FindStr( text, "[%emt " );
13217
13218         if( s_eval != NULL || s_emt != NULL ) {
13219             /* New style */
13220             char delim;
13221
13222             if( s_eval != NULL ) {
13223                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13224                     return text;
13225                 }
13226
13227                 if( delim != ']' ) {
13228                     return text;
13229                 }
13230             }
13231
13232             if( s_emt != NULL ) {
13233             }
13234                 return text;
13235         }
13236         else {
13237             /* We expect something like: [+|-]nnn.nn/dd */
13238             int score_lo = 0;
13239
13240             if(*text != '{') return text; // [HGM] braces: must be normal comment
13241
13242             sep = strchr( text, '/' );
13243             if( sep == NULL || sep < (text+4) ) {
13244                 return text;
13245             }
13246
13247             time = -1; sec = -1; deci = -1;
13248             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13249                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13250                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13251                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13252                 return text;
13253             }
13254
13255             if( score_lo < 0 || score_lo >= 100 ) {
13256                 return text;
13257             }
13258
13259             if(sec >= 0) time = 600*time + 10*sec; else
13260             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13261
13262             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13263
13264             /* [HGM] PV time: now locate end of PV info */
13265             while( *++sep >= '0' && *sep <= '9'); // strip depth
13266             if(time >= 0)
13267             while( *++sep >= '0' && *sep <= '9'); // strip time
13268             if(sec >= 0)
13269             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13270             if(deci >= 0)
13271             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13272             while(*sep == ' ') sep++;
13273         }
13274
13275         if( depth <= 0 ) {
13276             return text;
13277         }
13278
13279         if( time < 0 ) {
13280             time = -1;
13281         }
13282
13283         pvInfoList[index-1].depth = depth;
13284         pvInfoList[index-1].score = score;
13285         pvInfoList[index-1].time  = 10*time; // centi-sec
13286         if(*sep == '}') *sep = 0; else *--sep = '{';
13287     }
13288     return sep;
13289 }
13290
13291 void
13292 SendToProgram(message, cps)
13293      char *message;
13294      ChessProgramState *cps;
13295 {
13296     int count, outCount, error;
13297     char buf[MSG_SIZ];
13298
13299     if (cps->pr == NULL) return;
13300     Attention(cps);
13301
13302     if (appData.debugMode) {
13303         TimeMark now;
13304         GetTimeMark(&now);
13305         fprintf(debugFP, "%ld >%-6s: %s",
13306                 SubtractTimeMarks(&now, &programStartTime),
13307                 cps->which, message);
13308     }
13309
13310     count = strlen(message);
13311     outCount = OutputToProcess(cps->pr, message, count, &error);
13312     if (outCount < count && !exiting
13313                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13314         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13315         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13316             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13317                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13318                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13319             } else {
13320                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13321             }
13322             gameInfo.resultDetails = StrSave(buf);
13323         }
13324         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13325     }
13326 }
13327
13328 void
13329 ReceiveFromProgram(isr, closure, message, count, error)
13330      InputSourceRef isr;
13331      VOIDSTAR closure;
13332      char *message;
13333      int count;
13334      int error;
13335 {
13336     char *end_str;
13337     char buf[MSG_SIZ];
13338     ChessProgramState *cps = (ChessProgramState *)closure;
13339
13340     if (isr != cps->isr) return; /* Killed intentionally */
13341     if (count <= 0) {
13342         if (count == 0) {
13343             sprintf(buf,
13344                     _("Error: %s chess program (%s) exited unexpectedly"),
13345                     cps->which, cps->program);
13346         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13347                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13348                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13349                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13350                 } else {
13351                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13352                 }
13353                 gameInfo.resultDetails = StrSave(buf);
13354             }
13355             RemoveInputSource(cps->isr);
13356             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13357         } else {
13358             sprintf(buf,
13359                     _("Error reading from %s chess program (%s)"),
13360                     cps->which, cps->program);
13361             RemoveInputSource(cps->isr);
13362
13363             /* [AS] Program is misbehaving badly... kill it */
13364             if( count == -2 ) {
13365                 DestroyChildProcess( cps->pr, 9 );
13366                 cps->pr = NoProc;
13367             }
13368
13369             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13370         }
13371         return;
13372     }
13373
13374     if ((end_str = strchr(message, '\r')) != NULL)
13375       *end_str = NULLCHAR;
13376     if ((end_str = strchr(message, '\n')) != NULL)
13377       *end_str = NULLCHAR;
13378
13379     if (appData.debugMode) {
13380         TimeMark now; int print = 1;
13381         char *quote = ""; char c; int i;
13382
13383         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13384                 char start = message[0];
13385                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13386                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13387                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13388                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13389                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13390                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13391                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13392                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13393                         { quote = "# "; print = (appData.engineComments == 2); }
13394                 message[0] = start; // restore original message
13395         }
13396         if(print) {
13397                 GetTimeMark(&now);
13398                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13399                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13400                         quote,
13401                         message);
13402         }
13403     }
13404
13405     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13406     if (appData.icsEngineAnalyze) {
13407         if (strstr(message, "whisper") != NULL ||
13408              strstr(message, "kibitz") != NULL ||
13409             strstr(message, "tellics") != NULL) return;
13410     }
13411
13412     HandleMachineMove(message, cps);
13413 }
13414
13415
13416 void
13417 SendTimeControl(cps, mps, tc, inc, sd, st)
13418      ChessProgramState *cps;
13419      int mps, inc, sd, st;
13420      long tc;
13421 {
13422     char buf[MSG_SIZ];
13423     int seconds;
13424
13425     if( timeControl_2 > 0 ) {
13426         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13427             tc = timeControl_2;
13428         }
13429     }
13430     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13431     inc /= cps->timeOdds;
13432     st  /= cps->timeOdds;
13433
13434     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13435
13436     if (st > 0) {
13437       /* Set exact time per move, normally using st command */
13438       if (cps->stKludge) {
13439         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13440         seconds = st % 60;
13441         if (seconds == 0) {
13442           sprintf(buf, "level 1 %d\n", st/60);
13443         } else {
13444           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13445         }
13446       } else {
13447         sprintf(buf, "st %d\n", st);
13448       }
13449     } else {
13450       /* Set conventional or incremental time control, using level command */
13451       if (seconds == 0) {
13452         /* Note old gnuchess bug -- minutes:seconds used to not work.
13453            Fixed in later versions, but still avoid :seconds
13454            when seconds is 0. */
13455         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13456       } else {
13457         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13458                 seconds, inc/1000);
13459       }
13460     }
13461     SendToProgram(buf, cps);
13462
13463     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13464     /* Orthogonally, limit search to given depth */
13465     if (sd > 0) {
13466       if (cps->sdKludge) {
13467         sprintf(buf, "depth\n%d\n", sd);
13468       } else {
13469         sprintf(buf, "sd %d\n", sd);
13470       }
13471       SendToProgram(buf, cps);
13472     }
13473
13474     if(cps->nps > 0) { /* [HGM] nps */
13475         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13476         else {
13477                 sprintf(buf, "nps %d\n", cps->nps);
13478               SendToProgram(buf, cps);
13479         }
13480     }
13481 }
13482
13483 ChessProgramState *WhitePlayer()
13484 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13485 {
13486     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13487        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13488         return &second;
13489     return &first;
13490 }
13491
13492 void
13493 SendTimeRemaining(cps, machineWhite)
13494      ChessProgramState *cps;
13495      int /*boolean*/ machineWhite;
13496 {
13497     char message[MSG_SIZ];
13498     long time, otime;
13499
13500     /* Note: this routine must be called when the clocks are stopped
13501        or when they have *just* been set or switched; otherwise
13502        it will be off by the time since the current tick started.
13503     */
13504     if (machineWhite) {
13505         time = whiteTimeRemaining / 10;
13506         otime = blackTimeRemaining / 10;
13507     } else {
13508         time = blackTimeRemaining / 10;
13509         otime = whiteTimeRemaining / 10;
13510     }
13511     /* [HGM] translate opponent's time by time-odds factor */
13512     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13513     if (appData.debugMode) {
13514         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13515     }
13516
13517     if (time <= 0) time = 1;
13518     if (otime <= 0) otime = 1;
13519
13520     sprintf(message, "time %ld\n", time);
13521     SendToProgram(message, cps);
13522
13523     sprintf(message, "otim %ld\n", otime);
13524     SendToProgram(message, cps);
13525 }
13526
13527 int
13528 BoolFeature(p, name, loc, cps)
13529      char **p;
13530      char *name;
13531      int *loc;
13532      ChessProgramState *cps;
13533 {
13534   char buf[MSG_SIZ];
13535   int len = strlen(name);
13536   int val;
13537   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13538     (*p) += len + 1;
13539     sscanf(*p, "%d", &val);
13540     *loc = (val != 0);
13541     while (**p && **p != ' ') (*p)++;
13542     sprintf(buf, "accepted %s\n", name);
13543     SendToProgram(buf, cps);
13544     return TRUE;
13545   }
13546   return FALSE;
13547 }
13548
13549 int
13550 IntFeature(p, name, loc, cps)
13551      char **p;
13552      char *name;
13553      int *loc;
13554      ChessProgramState *cps;
13555 {
13556   char buf[MSG_SIZ];
13557   int len = strlen(name);
13558   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13559     (*p) += len + 1;
13560     sscanf(*p, "%d", loc);
13561     while (**p && **p != ' ') (*p)++;
13562     sprintf(buf, "accepted %s\n", name);
13563     SendToProgram(buf, cps);
13564     return TRUE;
13565   }
13566   return FALSE;
13567 }
13568
13569 int
13570 StringFeature(p, name, loc, cps)
13571      char **p;
13572      char *name;
13573      char loc[];
13574      ChessProgramState *cps;
13575 {
13576   char buf[MSG_SIZ];
13577   int len = strlen(name);
13578   if (strncmp((*p), name, len) == 0
13579       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13580     (*p) += len + 2;
13581     sscanf(*p, "%[^\"]", loc);
13582     while (**p && **p != '\"') (*p)++;
13583     if (**p == '\"') (*p)++;
13584     sprintf(buf, "accepted %s\n", name);
13585     SendToProgram(buf, cps);
13586     return TRUE;
13587   }
13588   return FALSE;
13589 }
13590
13591 int
13592 ParseOption(Option *opt, ChessProgramState *cps)
13593 // [HGM] options: process the string that defines an engine option, and determine
13594 // name, type, default value, and allowed value range
13595 {
13596         char *p, *q, buf[MSG_SIZ];
13597         int n, min = (-1)<<31, max = 1<<31, def;
13598
13599         if(p = strstr(opt->name, " -spin ")) {
13600             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13601             if(max < min) max = min; // enforce consistency
13602             if(def < min) def = min;
13603             if(def > max) def = max;
13604             opt->value = def;
13605             opt->min = min;
13606             opt->max = max;
13607             opt->type = Spin;
13608         } else if((p = strstr(opt->name, " -slider "))) {
13609             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13610             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13611             if(max < min) max = min; // enforce consistency
13612             if(def < min) def = min;
13613             if(def > max) def = max;
13614             opt->value = def;
13615             opt->min = min;
13616             opt->max = max;
13617             opt->type = Spin; // Slider;
13618         } else if((p = strstr(opt->name, " -string "))) {
13619             opt->textValue = p+9;
13620             opt->type = TextBox;
13621         } else if((p = strstr(opt->name, " -file "))) {
13622             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13623             opt->textValue = p+7;
13624             opt->type = TextBox; // FileName;
13625         } else if((p = strstr(opt->name, " -path "))) {
13626             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13627             opt->textValue = p+7;
13628             opt->type = TextBox; // PathName;
13629         } else if(p = strstr(opt->name, " -check ")) {
13630             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13631             opt->value = (def != 0);
13632             opt->type = CheckBox;
13633         } else if(p = strstr(opt->name, " -combo ")) {
13634             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13635             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13636             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13637             opt->value = n = 0;
13638             while(q = StrStr(q, " /// ")) {
13639                 n++; *q = 0;    // count choices, and null-terminate each of them
13640                 q += 5;
13641                 if(*q == '*') { // remember default, which is marked with * prefix
13642                     q++;
13643                     opt->value = n;
13644                 }
13645                 cps->comboList[cps->comboCnt++] = q;
13646             }
13647             cps->comboList[cps->comboCnt++] = NULL;
13648             opt->max = n + 1;
13649             opt->type = ComboBox;
13650         } else if(p = strstr(opt->name, " -button")) {
13651             opt->type = Button;
13652         } else if(p = strstr(opt->name, " -save")) {
13653             opt->type = SaveButton;
13654         } else return FALSE;
13655         *p = 0; // terminate option name
13656         // now look if the command-line options define a setting for this engine option.
13657         if(cps->optionSettings && cps->optionSettings[0])
13658             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13659         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13660                 sprintf(buf, "option %s", p);
13661                 if(p = strstr(buf, ",")) *p = 0;
13662                 strcat(buf, "\n");
13663                 SendToProgram(buf, cps);
13664         }
13665         return TRUE;
13666 }
13667
13668 void
13669 FeatureDone(cps, val)
13670      ChessProgramState* cps;
13671      int val;
13672 {
13673   DelayedEventCallback cb = GetDelayedEvent();
13674   if ((cb == InitBackEnd3 && cps == &first) ||
13675       (cb == TwoMachinesEventIfReady && cps == &second)) {
13676     CancelDelayedEvent();
13677     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13678   }
13679   cps->initDone = val;
13680 }
13681
13682 /* Parse feature command from engine */
13683 void
13684 ParseFeatures(args, cps)
13685      char* args;
13686      ChessProgramState *cps;
13687 {
13688   char *p = args;
13689   char *q;
13690   int val;
13691   char buf[MSG_SIZ];
13692
13693   for (;;) {
13694     while (*p == ' ') p++;
13695     if (*p == NULLCHAR) return;
13696
13697     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13698     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13699     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13700     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13701     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13702     if (BoolFeature(&p, "reuse", &val, cps)) {
13703       /* Engine can disable reuse, but can't enable it if user said no */
13704       if (!val) cps->reuse = FALSE;
13705       continue;
13706     }
13707     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13708     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13709       if (gameMode == TwoMachinesPlay) {
13710         DisplayTwoMachinesTitle();
13711       } else {
13712         DisplayTitle("");
13713       }
13714       continue;
13715     }
13716     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13717     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13718     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13719     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13720     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13721     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13722     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13723     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13724     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13725     if (IntFeature(&p, "done", &val, cps)) {
13726       FeatureDone(cps, val);
13727       continue;
13728     }
13729     /* Added by Tord: */
13730     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13731     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13732     /* End of additions by Tord */
13733
13734     /* [HGM] added features: */
13735     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13736     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13737     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13738     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13739     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13740     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13741     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13742         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13743             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13744             SendToProgram(buf, cps);
13745             continue;
13746         }
13747         if(cps->nrOptions >= MAX_OPTIONS) {
13748             cps->nrOptions--;
13749             sprintf(buf, "%s engine has too many options\n", cps->which);
13750             DisplayError(buf, 0);
13751         }
13752         continue;
13753     }
13754     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13755     /* End of additions by HGM */
13756
13757     /* unknown feature: complain and skip */
13758     q = p;
13759     while (*q && *q != '=') q++;
13760     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13761     SendToProgram(buf, cps);
13762     p = q;
13763     if (*p == '=') {
13764       p++;
13765       if (*p == '\"') {
13766         p++;
13767         while (*p && *p != '\"') p++;
13768         if (*p == '\"') p++;
13769       } else {
13770         while (*p && *p != ' ') p++;
13771       }
13772     }
13773   }
13774
13775 }
13776
13777 void
13778 PeriodicUpdatesEvent(newState)
13779      int newState;
13780 {
13781     if (newState == appData.periodicUpdates)
13782       return;
13783
13784     appData.periodicUpdates=newState;
13785
13786     /* Display type changes, so update it now */
13787 //    DisplayAnalysis();
13788
13789     /* Get the ball rolling again... */
13790     if (newState) {
13791         AnalysisPeriodicEvent(1);
13792         StartAnalysisClock();
13793     }
13794 }
13795
13796 void
13797 PonderNextMoveEvent(newState)
13798      int newState;
13799 {
13800     if (newState == appData.ponderNextMove) return;
13801     if (gameMode == EditPosition) EditPositionDone(TRUE);
13802     if (newState) {
13803         SendToProgram("hard\n", &first);
13804         if (gameMode == TwoMachinesPlay) {
13805             SendToProgram("hard\n", &second);
13806         }
13807     } else {
13808         SendToProgram("easy\n", &first);
13809         thinkOutput[0] = NULLCHAR;
13810         if (gameMode == TwoMachinesPlay) {
13811             SendToProgram("easy\n", &second);
13812         }
13813     }
13814     appData.ponderNextMove = newState;
13815 }
13816
13817 void
13818 NewSettingEvent(option, command, value)
13819      char *command;
13820      int option, value;
13821 {
13822     char buf[MSG_SIZ];
13823
13824     if (gameMode == EditPosition) EditPositionDone(TRUE);
13825     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13826     SendToProgram(buf, &first);
13827     if (gameMode == TwoMachinesPlay) {
13828         SendToProgram(buf, &second);
13829     }
13830 }
13831
13832 void
13833 ShowThinkingEvent()
13834 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13835 {
13836     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13837     int newState = appData.showThinking
13838         // [HGM] thinking: other features now need thinking output as well
13839         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13840
13841     if (oldState == newState) return;
13842     oldState = newState;
13843     if (gameMode == EditPosition) EditPositionDone(TRUE);
13844     if (oldState) {
13845         SendToProgram("post\n", &first);
13846         if (gameMode == TwoMachinesPlay) {
13847             SendToProgram("post\n", &second);
13848         }
13849     } else {
13850         SendToProgram("nopost\n", &first);
13851         thinkOutput[0] = NULLCHAR;
13852         if (gameMode == TwoMachinesPlay) {
13853             SendToProgram("nopost\n", &second);
13854         }
13855     }
13856 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13857 }
13858
13859 void
13860 AskQuestionEvent(title, question, replyPrefix, which)
13861      char *title; char *question; char *replyPrefix; char *which;
13862 {
13863   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13864   if (pr == NoProc) return;
13865   AskQuestion(title, question, replyPrefix, pr);
13866 }
13867
13868 void
13869 DisplayMove(moveNumber)
13870      int moveNumber;
13871 {
13872     char message[MSG_SIZ];
13873     char res[MSG_SIZ];
13874     char cpThinkOutput[MSG_SIZ];
13875
13876     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13877
13878     if (moveNumber == forwardMostMove - 1 ||
13879         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13880
13881         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13882
13883         if (strchr(cpThinkOutput, '\n')) {
13884             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13885         }
13886     } else {
13887         *cpThinkOutput = NULLCHAR;
13888     }
13889
13890     /* [AS] Hide thinking from human user */
13891     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13892         *cpThinkOutput = NULLCHAR;
13893         if( thinkOutput[0] != NULLCHAR ) {
13894             int i;
13895
13896             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13897                 cpThinkOutput[i] = '.';
13898             }
13899             cpThinkOutput[i] = NULLCHAR;
13900             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13901         }
13902     }
13903
13904     if (moveNumber == forwardMostMove - 1 &&
13905         gameInfo.resultDetails != NULL) {
13906         if (gameInfo.resultDetails[0] == NULLCHAR) {
13907             sprintf(res, " %s", PGNResult(gameInfo.result));
13908         } else {
13909             sprintf(res, " {%s} %s",
13910                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13911         }
13912     } else {
13913         res[0] = NULLCHAR;
13914     }
13915
13916     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13917         DisplayMessage(res, cpThinkOutput);
13918     } else {
13919         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13920                 WhiteOnMove(moveNumber) ? " " : ".. ",
13921                 parseList[moveNumber], res);
13922         DisplayMessage(message, cpThinkOutput);
13923     }
13924 }
13925
13926 void
13927 DisplayComment(moveNumber, text)
13928      int moveNumber;
13929      char *text;
13930 {
13931     char title[MSG_SIZ];
13932     char buf[8000]; // comment can be long!
13933     int score, depth;
13934     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13935       strcpy(title, "Comment");
13936     } else {
13937       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13938               WhiteOnMove(moveNumber) ? " " : ".. ",
13939               parseList[moveNumber]);
13940     }
13941     // [HGM] PV info: display PV info together with (or as) comment
13942     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13943       if(text == NULL) text = "";                                           
13944       score = pvInfoList[moveNumber].score;
13945       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13946               depth, (pvInfoList[moveNumber].time+50)/100, text);
13947       text = buf;
13948     }
13949     if (text != NULL && (appData.autoDisplayComment || commentUp))
13950       CommentPopUp(title, text);
13951 }
13952
13953 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13954  * might be busy thinking or pondering.  It can be omitted if your
13955  * gnuchess is configured to stop thinking immediately on any user
13956  * input.  However, that gnuchess feature depends on the FIONREAD
13957  * ioctl, which does not work properly on some flavors of Unix.
13958  */
13959 void
13960 Attention(cps)
13961      ChessProgramState *cps;
13962 {
13963 #if ATTENTION
13964     if (!cps->useSigint) return;
13965     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13966     switch (gameMode) {
13967       case MachinePlaysWhite:
13968       case MachinePlaysBlack:
13969       case TwoMachinesPlay:
13970       case IcsPlayingWhite:
13971       case IcsPlayingBlack:
13972       case AnalyzeMode:
13973       case AnalyzeFile:
13974         /* Skip if we know it isn't thinking */
13975         if (!cps->maybeThinking) return;
13976         if (appData.debugMode)
13977           fprintf(debugFP, "Interrupting %s\n", cps->which);
13978         InterruptChildProcess(cps->pr);
13979         cps->maybeThinking = FALSE;
13980         break;
13981       default:
13982         break;
13983     }
13984 #endif /*ATTENTION*/
13985 }
13986
13987 int
13988 CheckFlags()
13989 {
13990     if (whiteTimeRemaining <= 0) {
13991         if (!whiteFlag) {
13992             whiteFlag = TRUE;
13993             if (appData.icsActive) {
13994                 if (appData.autoCallFlag &&
13995                     gameMode == IcsPlayingBlack && !blackFlag) {
13996                   SendToICS(ics_prefix);
13997                   SendToICS("flag\n");
13998                 }
13999             } else {
14000                 if (blackFlag) {
14001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14002                 } else {
14003                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14004                     if (appData.autoCallFlag) {
14005                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14006                         return TRUE;
14007                     }
14008                 }
14009             }
14010         }
14011     }
14012     if (blackTimeRemaining <= 0) {
14013         if (!blackFlag) {
14014             blackFlag = TRUE;
14015             if (appData.icsActive) {
14016                 if (appData.autoCallFlag &&
14017                     gameMode == IcsPlayingWhite && !whiteFlag) {
14018                   SendToICS(ics_prefix);
14019                   SendToICS("flag\n");
14020                 }
14021             } else {
14022                 if (whiteFlag) {
14023                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14024                 } else {
14025                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14026                     if (appData.autoCallFlag) {
14027                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14028                         return TRUE;
14029                     }
14030                 }
14031             }
14032         }
14033     }
14034     return FALSE;
14035 }
14036
14037 void
14038 CheckTimeControl()
14039 {
14040     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14041         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14042
14043     /*
14044      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14045      */
14046     if ( !WhiteOnMove(forwardMostMove) )
14047         /* White made time control */
14048         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14049         /* [HGM] time odds: correct new time quota for time odds! */
14050                                             / WhitePlayer()->timeOdds;
14051       else
14052         /* Black made time control */
14053         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14054                                             / WhitePlayer()->other->timeOdds;
14055 }
14056
14057 void
14058 DisplayBothClocks()
14059 {
14060     int wom = gameMode == EditPosition ?
14061       !blackPlaysFirst : WhiteOnMove(currentMove);
14062     DisplayWhiteClock(whiteTimeRemaining, wom);
14063     DisplayBlackClock(blackTimeRemaining, !wom);
14064 }
14065
14066
14067 /* Timekeeping seems to be a portability nightmare.  I think everyone
14068    has ftime(), but I'm really not sure, so I'm including some ifdefs
14069    to use other calls if you don't.  Clocks will be less accurate if
14070    you have neither ftime nor gettimeofday.
14071 */
14072
14073 /* VS 2008 requires the #include outside of the function */
14074 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14075 #include <sys/timeb.h>
14076 #endif
14077
14078 /* Get the current time as a TimeMark */
14079 void
14080 GetTimeMark(tm)
14081      TimeMark *tm;
14082 {
14083 #if HAVE_GETTIMEOFDAY
14084
14085     struct timeval timeVal;
14086     struct timezone timeZone;
14087
14088     gettimeofday(&timeVal, &timeZone);
14089     tm->sec = (long) timeVal.tv_sec;
14090     tm->ms = (int) (timeVal.tv_usec / 1000L);
14091
14092 #else /*!HAVE_GETTIMEOFDAY*/
14093 #if HAVE_FTIME
14094
14095 // include <sys/timeb.h> / moved to just above start of function
14096     struct timeb timeB;
14097
14098     ftime(&timeB);
14099     tm->sec = (long) timeB.time;
14100     tm->ms = (int) timeB.millitm;
14101
14102 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14103     tm->sec = (long) time(NULL);
14104     tm->ms = 0;
14105 #endif
14106 #endif
14107 }
14108
14109 /* Return the difference in milliseconds between two
14110    time marks.  We assume the difference will fit in a long!
14111 */
14112 long
14113 SubtractTimeMarks(tm2, tm1)
14114      TimeMark *tm2, *tm1;
14115 {
14116     return 1000L*(tm2->sec - tm1->sec) +
14117            (long) (tm2->ms - tm1->ms);
14118 }
14119
14120
14121 /*
14122  * Code to manage the game clocks.
14123  *
14124  * In tournament play, black starts the clock and then white makes a move.
14125  * We give the human user a slight advantage if he is playing white---the
14126  * clocks don't run until he makes his first move, so it takes zero time.
14127  * Also, we don't account for network lag, so we could get out of sync
14128  * with GNU Chess's clock -- but then, referees are always right.
14129  */
14130
14131 static TimeMark tickStartTM;
14132 static long intendedTickLength;
14133
14134 long
14135 NextTickLength(timeRemaining)
14136      long timeRemaining;
14137 {
14138     long nominalTickLength, nextTickLength;
14139
14140     if (timeRemaining > 0L && timeRemaining <= 10000L)
14141       nominalTickLength = 100L;
14142     else
14143       nominalTickLength = 1000L;
14144     nextTickLength = timeRemaining % nominalTickLength;
14145     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14146
14147     return nextTickLength;
14148 }
14149
14150 /* Adjust clock one minute up or down */
14151 void
14152 AdjustClock(Boolean which, int dir)
14153 {
14154     if(which) blackTimeRemaining += 60000*dir;
14155     else      whiteTimeRemaining += 60000*dir;
14156     DisplayBothClocks();
14157 }
14158
14159 /* Stop clocks and reset to a fresh time control */
14160 void
14161 ResetClocks()
14162 {
14163     (void) StopClockTimer();
14164     if (appData.icsActive) {
14165         whiteTimeRemaining = blackTimeRemaining = 0;
14166     } else if (searchTime) {
14167         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14168         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14169     } else { /* [HGM] correct new time quote for time odds */
14170         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14171         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14172     }
14173     if (whiteFlag || blackFlag) {
14174         DisplayTitle("");
14175         whiteFlag = blackFlag = FALSE;
14176     }
14177     DisplayBothClocks();
14178 }
14179
14180 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14181
14182 /* Decrement running clock by amount of time that has passed */
14183 void
14184 DecrementClocks()
14185 {
14186     long timeRemaining;
14187     long lastTickLength, fudge;
14188     TimeMark now;
14189
14190     if (!appData.clockMode) return;
14191     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14192
14193     GetTimeMark(&now);
14194
14195     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14196
14197     /* Fudge if we woke up a little too soon */
14198     fudge = intendedTickLength - lastTickLength;
14199     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14200
14201     if (WhiteOnMove(forwardMostMove)) {
14202         if(whiteNPS >= 0) lastTickLength = 0;
14203         timeRemaining = whiteTimeRemaining -= lastTickLength;
14204         DisplayWhiteClock(whiteTimeRemaining - fudge,
14205                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14206     } else {
14207         if(blackNPS >= 0) lastTickLength = 0;
14208         timeRemaining = blackTimeRemaining -= lastTickLength;
14209         DisplayBlackClock(blackTimeRemaining - fudge,
14210                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14211     }
14212
14213     if (CheckFlags()) return;
14214
14215     tickStartTM = now;
14216     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14217     StartClockTimer(intendedTickLength);
14218
14219     /* if the time remaining has fallen below the alarm threshold, sound the
14220      * alarm. if the alarm has sounded and (due to a takeback or time control
14221      * with increment) the time remaining has increased to a level above the
14222      * threshold, reset the alarm so it can sound again.
14223      */
14224
14225     if (appData.icsActive && appData.icsAlarm) {
14226
14227         /* make sure we are dealing with the user's clock */
14228         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14229                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14230            )) return;
14231
14232         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14233             alarmSounded = FALSE;
14234         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14235             PlayAlarmSound();
14236             alarmSounded = TRUE;
14237         }
14238     }
14239 }
14240
14241
14242 /* A player has just moved, so stop the previously running
14243    clock and (if in clock mode) start the other one.
14244    We redisplay both clocks in case we're in ICS mode, because
14245    ICS gives us an update to both clocks after every move.
14246    Note that this routine is called *after* forwardMostMove
14247    is updated, so the last fractional tick must be subtracted
14248    from the color that is *not* on move now.
14249 */
14250 void
14251 SwitchClocks()
14252 {
14253     long lastTickLength;
14254     TimeMark now;
14255     int flagged = FALSE;
14256
14257     GetTimeMark(&now);
14258
14259     if (StopClockTimer() && appData.clockMode) {
14260         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14261         if (WhiteOnMove(forwardMostMove)) {
14262             if(blackNPS >= 0) lastTickLength = 0;
14263             blackTimeRemaining -= lastTickLength;
14264            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14265 //         if(pvInfoList[forwardMostMove-1].time == -1)
14266                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14267                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14268         } else {
14269            if(whiteNPS >= 0) lastTickLength = 0;
14270            whiteTimeRemaining -= lastTickLength;
14271            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14272 //         if(pvInfoList[forwardMostMove-1].time == -1)
14273                  pvInfoList[forwardMostMove-1].time =
14274                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14275         }
14276         flagged = CheckFlags();
14277     }
14278     CheckTimeControl();
14279
14280     if (flagged || !appData.clockMode) return;
14281
14282     switch (gameMode) {
14283       case MachinePlaysBlack:
14284       case MachinePlaysWhite:
14285       case BeginningOfGame:
14286         if (pausing) return;
14287         break;
14288
14289       case EditGame:
14290       case PlayFromGameFile:
14291       case IcsExamining:
14292         return;
14293
14294       default:
14295         break;
14296     }
14297
14298     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14299         if(WhiteOnMove(forwardMostMove))
14300              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14301         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14302     }
14303
14304     tickStartTM = now;
14305     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14306       whiteTimeRemaining : blackTimeRemaining);
14307     StartClockTimer(intendedTickLength);
14308 }
14309
14310
14311 /* Stop both clocks */
14312 void
14313 StopClocks()
14314 {
14315     long lastTickLength;
14316     TimeMark now;
14317
14318     if (!StopClockTimer()) return;
14319     if (!appData.clockMode) return;
14320
14321     GetTimeMark(&now);
14322
14323     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14324     if (WhiteOnMove(forwardMostMove)) {
14325         if(whiteNPS >= 0) lastTickLength = 0;
14326         whiteTimeRemaining -= lastTickLength;
14327         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14328     } else {
14329         if(blackNPS >= 0) lastTickLength = 0;
14330         blackTimeRemaining -= lastTickLength;
14331         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14332     }
14333     CheckFlags();
14334 }
14335
14336 /* Start clock of player on move.  Time may have been reset, so
14337    if clock is already running, stop and restart it. */
14338 void
14339 StartClocks()
14340 {
14341     (void) StopClockTimer(); /* in case it was running already */
14342     DisplayBothClocks();
14343     if (CheckFlags()) return;
14344
14345     if (!appData.clockMode) return;
14346     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14347
14348     GetTimeMark(&tickStartTM);
14349     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14350       whiteTimeRemaining : blackTimeRemaining);
14351
14352    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14353     whiteNPS = blackNPS = -1;
14354     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14355        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14356         whiteNPS = first.nps;
14357     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14358        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14359         blackNPS = first.nps;
14360     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14361         whiteNPS = second.nps;
14362     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14363         blackNPS = second.nps;
14364     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14365
14366     StartClockTimer(intendedTickLength);
14367 }
14368
14369 char *
14370 TimeString(ms)
14371      long ms;
14372 {
14373     long second, minute, hour, day;
14374     char *sign = "";
14375     static char buf[32];
14376
14377     if (ms > 0 && ms <= 9900) {
14378       /* convert milliseconds to tenths, rounding up */
14379       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14380
14381       sprintf(buf, " %03.1f ", tenths/10.0);
14382       return buf;
14383     }
14384
14385     /* convert milliseconds to seconds, rounding up */
14386     /* use floating point to avoid strangeness of integer division
14387        with negative dividends on many machines */
14388     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14389
14390     if (second < 0) {
14391         sign = "-";
14392         second = -second;
14393     }
14394
14395     day = second / (60 * 60 * 24);
14396     second = second % (60 * 60 * 24);
14397     hour = second / (60 * 60);
14398     second = second % (60 * 60);
14399     minute = second / 60;
14400     second = second % 60;
14401
14402     if (day > 0)
14403       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14404               sign, day, hour, minute, second);
14405     else if (hour > 0)
14406       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14407     else
14408       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14409
14410     return buf;
14411 }
14412
14413
14414 /*
14415  * This is necessary because some C libraries aren't ANSI C compliant yet.
14416  */
14417 char *
14418 StrStr(string, match)
14419      char *string, *match;
14420 {
14421     int i, length;
14422
14423     length = strlen(match);
14424
14425     for (i = strlen(string) - length; i >= 0; i--, string++)
14426       if (!strncmp(match, string, length))
14427         return string;
14428
14429     return NULL;
14430 }
14431
14432 char *
14433 StrCaseStr(string, match)
14434      char *string, *match;
14435 {
14436     int i, j, length;
14437
14438     length = strlen(match);
14439
14440     for (i = strlen(string) - length; i >= 0; i--, string++) {
14441         for (j = 0; j < length; j++) {
14442             if (ToLower(match[j]) != ToLower(string[j]))
14443               break;
14444         }
14445         if (j == length) return string;
14446     }
14447
14448     return NULL;
14449 }
14450
14451 #ifndef _amigados
14452 int
14453 StrCaseCmp(s1, s2)
14454      char *s1, *s2;
14455 {
14456     char c1, c2;
14457
14458     for (;;) {
14459         c1 = ToLower(*s1++);
14460         c2 = ToLower(*s2++);
14461         if (c1 > c2) return 1;
14462         if (c1 < c2) return -1;
14463         if (c1 == NULLCHAR) return 0;
14464     }
14465 }
14466
14467
14468 int
14469 ToLower(c)
14470      int c;
14471 {
14472     return isupper(c) ? tolower(c) : c;
14473 }
14474
14475
14476 int
14477 ToUpper(c)
14478      int c;
14479 {
14480     return islower(c) ? toupper(c) : c;
14481 }
14482 #endif /* !_amigados    */
14483
14484 char *
14485 StrSave(s)
14486      char *s;
14487 {
14488     char *ret;
14489
14490     if ((ret = (char *) malloc(strlen(s) + 1))) {
14491         strcpy(ret, s);
14492     }
14493     return ret;
14494 }
14495
14496 char *
14497 StrSavePtr(s, savePtr)
14498      char *s, **savePtr;
14499 {
14500     if (*savePtr) {
14501         free(*savePtr);
14502     }
14503     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14504         strcpy(*savePtr, s);
14505     }
14506     return(*savePtr);
14507 }
14508
14509 char *
14510 PGNDate()
14511 {
14512     time_t clock;
14513     struct tm *tm;
14514     char buf[MSG_SIZ];
14515
14516     clock = time((time_t *)NULL);
14517     tm = localtime(&clock);
14518     sprintf(buf, "%04d.%02d.%02d",
14519             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14520     return StrSave(buf);
14521 }
14522
14523
14524 char *
14525 PositionToFEN(move, overrideCastling)
14526      int move;
14527      char *overrideCastling;
14528 {
14529     int i, j, fromX, fromY, toX, toY;
14530     int whiteToPlay;
14531     char buf[128];
14532     char *p, *q;
14533     int emptycount;
14534     ChessSquare piece;
14535
14536     whiteToPlay = (gameMode == EditPosition) ?
14537       !blackPlaysFirst : (move % 2 == 0);
14538     p = buf;
14539
14540     /* Piece placement data */
14541     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14542         emptycount = 0;
14543         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14544             if (boards[move][i][j] == EmptySquare) {
14545                 emptycount++;
14546             } else { ChessSquare piece = boards[move][i][j];
14547                 if (emptycount > 0) {
14548                     if(emptycount<10) /* [HGM] can be >= 10 */
14549                         *p++ = '0' + emptycount;
14550                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14551                     emptycount = 0;
14552                 }
14553                 if(PieceToChar(piece) == '+') {
14554                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14555                     *p++ = '+';
14556                     piece = (ChessSquare)(DEMOTED piece);
14557                 }
14558                 *p++ = PieceToChar(piece);
14559                 if(p[-1] == '~') {
14560                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14561                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14562                     *p++ = '~';
14563                 }
14564             }
14565         }
14566         if (emptycount > 0) {
14567             if(emptycount<10) /* [HGM] can be >= 10 */
14568                 *p++ = '0' + emptycount;
14569             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14570             emptycount = 0;
14571         }
14572         *p++ = '/';
14573     }
14574     *(p - 1) = ' ';
14575
14576     /* [HGM] print Crazyhouse or Shogi holdings */
14577     if( gameInfo.holdingsWidth ) {
14578         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14579         q = p;
14580         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14581             piece = boards[move][i][BOARD_WIDTH-1];
14582             if( piece != EmptySquare )
14583               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14584                   *p++ = PieceToChar(piece);
14585         }
14586         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14587             piece = boards[move][BOARD_HEIGHT-i-1][0];
14588             if( piece != EmptySquare )
14589               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14590                   *p++ = PieceToChar(piece);
14591         }
14592
14593         if( q == p ) *p++ = '-';
14594         *p++ = ']';
14595         *p++ = ' ';
14596     }
14597
14598     /* Active color */
14599     *p++ = whiteToPlay ? 'w' : 'b';
14600     *p++ = ' ';
14601
14602   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14603     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14604   } else {
14605   if(nrCastlingRights) {
14606      q = p;
14607      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14608        /* [HGM] write directly from rights */
14609            if(boards[move][CASTLING][2] != NoRights &&
14610               boards[move][CASTLING][0] != NoRights   )
14611                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14612            if(boards[move][CASTLING][2] != NoRights &&
14613               boards[move][CASTLING][1] != NoRights   )
14614                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14615            if(boards[move][CASTLING][5] != NoRights &&
14616               boards[move][CASTLING][3] != NoRights   )
14617                 *p++ = boards[move][CASTLING][3] + AAA;
14618            if(boards[move][CASTLING][5] != NoRights &&
14619               boards[move][CASTLING][4] != NoRights   )
14620                 *p++ = boards[move][CASTLING][4] + AAA;
14621      } else {
14622
14623         /* [HGM] write true castling rights */
14624         if( nrCastlingRights == 6 ) {
14625             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14626                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14627             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14628                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14629             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14630                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14631             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14632                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14633         }
14634      }
14635      if (q == p) *p++ = '-'; /* No castling rights */
14636      *p++ = ' ';
14637   }
14638
14639   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14640      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14641     /* En passant target square */
14642     if (move > backwardMostMove) {
14643         fromX = moveList[move - 1][0] - AAA;
14644         fromY = moveList[move - 1][1] - ONE;
14645         toX = moveList[move - 1][2] - AAA;
14646         toY = moveList[move - 1][3] - ONE;
14647         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14648             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14649             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14650             fromX == toX) {
14651             /* 2-square pawn move just happened */
14652             *p++ = toX + AAA;
14653             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14654         } else {
14655             *p++ = '-';
14656         }
14657     } else if(move == backwardMostMove) {
14658         // [HGM] perhaps we should always do it like this, and forget the above?
14659         if((signed char)boards[move][EP_STATUS] >= 0) {
14660             *p++ = boards[move][EP_STATUS] + AAA;
14661             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14662         } else {
14663             *p++ = '-';
14664         }
14665     } else {
14666         *p++ = '-';
14667     }
14668     *p++ = ' ';
14669   }
14670   }
14671
14672     /* [HGM] find reversible plies */
14673     {   int i = 0, j=move;
14674
14675         if (appData.debugMode) { int k;
14676             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14677             for(k=backwardMostMove; k<=forwardMostMove; k++)
14678                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14679
14680         }
14681
14682         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14683         if( j == backwardMostMove ) i += initialRulePlies;
14684         sprintf(p, "%d ", i);
14685         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14686     }
14687     /* Fullmove number */
14688     sprintf(p, "%d", (move / 2) + 1);
14689
14690     return StrSave(buf);
14691 }
14692
14693 Boolean
14694 ParseFEN(board, blackPlaysFirst, fen)
14695     Board board;
14696      int *blackPlaysFirst;
14697      char *fen;
14698 {
14699     int i, j;
14700     char *p;
14701     int emptycount;
14702     ChessSquare piece;
14703
14704     p = fen;
14705
14706     /* [HGM] by default clear Crazyhouse holdings, if present */
14707     if(gameInfo.holdingsWidth) {
14708        for(i=0; i<BOARD_HEIGHT; i++) {
14709            board[i][0]             = EmptySquare; /* black holdings */
14710            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14711            board[i][1]             = (ChessSquare) 0; /* black counts */
14712            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14713        }
14714     }
14715
14716     /* Piece placement data */
14717     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14718         j = 0;
14719         for (;;) {
14720             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14721                 if (*p == '/') p++;
14722                 emptycount = gameInfo.boardWidth - j;
14723                 while (emptycount--)
14724                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14725                 break;
14726 #if(BOARD_FILES >= 10)
14727             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14728                 p++; emptycount=10;
14729                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14730                 while (emptycount--)
14731                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14732 #endif
14733             } else if (isdigit(*p)) {
14734                 emptycount = *p++ - '0';
14735                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14736                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14737                 while (emptycount--)
14738                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14739             } else if (*p == '+' || isalpha(*p)) {
14740                 if (j >= gameInfo.boardWidth) return FALSE;
14741                 if(*p=='+') {
14742                     piece = CharToPiece(*++p);
14743                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14744                     piece = (ChessSquare) (PROMOTED piece ); p++;
14745                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14746                 } else piece = CharToPiece(*p++);
14747
14748                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14749                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14750                     piece = (ChessSquare) (PROMOTED piece);
14751                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14752                     p++;
14753                 }
14754                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14755             } else {
14756                 return FALSE;
14757             }
14758         }
14759     }
14760     while (*p == '/' || *p == ' ') p++;
14761
14762     /* [HGM] look for Crazyhouse holdings here */
14763     while(*p==' ') p++;
14764     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14765         if(*p == '[') p++;
14766         if(*p == '-' ) *p++; /* empty holdings */ else {
14767             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14768             /* if we would allow FEN reading to set board size, we would   */
14769             /* have to add holdings and shift the board read so far here   */
14770             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14771                 *p++;
14772                 if((int) piece >= (int) BlackPawn ) {
14773                     i = (int)piece - (int)BlackPawn;
14774                     i = PieceToNumber((ChessSquare)i);
14775                     if( i >= gameInfo.holdingsSize ) return FALSE;
14776                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14777                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14778                 } else {
14779                     i = (int)piece - (int)WhitePawn;
14780                     i = PieceToNumber((ChessSquare)i);
14781                     if( i >= gameInfo.holdingsSize ) return FALSE;
14782                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14783                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14784                 }
14785             }
14786         }
14787         if(*p == ']') *p++;
14788     }
14789
14790     while(*p == ' ') p++;
14791
14792     /* Active color */
14793     switch (*p++) {
14794       case 'w':
14795         *blackPlaysFirst = FALSE;
14796         break;
14797       case 'b':
14798         *blackPlaysFirst = TRUE;
14799         break;
14800       default:
14801         return FALSE;
14802     }
14803
14804     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14805     /* return the extra info in global variiables             */
14806
14807     /* set defaults in case FEN is incomplete */
14808     board[EP_STATUS] = EP_UNKNOWN;
14809     for(i=0; i<nrCastlingRights; i++ ) {
14810         board[CASTLING][i] =
14811             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14812     }   /* assume possible unless obviously impossible */
14813     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14814     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14815     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14816                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14817     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14818     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14819     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14820                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14821     FENrulePlies = 0;
14822
14823     while(*p==' ') p++;
14824     if(nrCastlingRights) {
14825       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14826           /* castling indicator present, so default becomes no castlings */
14827           for(i=0; i<nrCastlingRights; i++ ) {
14828                  board[CASTLING][i] = NoRights;
14829           }
14830       }
14831       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14832              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14833              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14834              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14835         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14836
14837         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14838             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14839             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14840         }
14841         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14842             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14843         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14844                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14845         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14846                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14847         switch(c) {
14848           case'K':
14849               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14850               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14851               board[CASTLING][2] = whiteKingFile;
14852               break;
14853           case'Q':
14854               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14855               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14856               board[CASTLING][2] = whiteKingFile;
14857               break;
14858           case'k':
14859               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14860               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14861               board[CASTLING][5] = blackKingFile;
14862               break;
14863           case'q':
14864               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14865               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14866               board[CASTLING][5] = blackKingFile;
14867           case '-':
14868               break;
14869           default: /* FRC castlings */
14870               if(c >= 'a') { /* black rights */
14871                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14872                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14873                   if(i == BOARD_RGHT) break;
14874                   board[CASTLING][5] = i;
14875                   c -= AAA;
14876                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14877                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14878                   if(c > i)
14879                       board[CASTLING][3] = c;
14880                   else
14881                       board[CASTLING][4] = c;
14882               } else { /* white rights */
14883                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14884                     if(board[0][i] == WhiteKing) break;
14885                   if(i == BOARD_RGHT) break;
14886                   board[CASTLING][2] = i;
14887                   c -= AAA - 'a' + 'A';
14888                   if(board[0][c] >= WhiteKing) break;
14889                   if(c > i)
14890                       board[CASTLING][0] = c;
14891                   else
14892                       board[CASTLING][1] = c;
14893               }
14894         }
14895       }
14896       for(i=0; i<nrCastlingRights; i++)
14897         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14898     if (appData.debugMode) {
14899         fprintf(debugFP, "FEN castling rights:");
14900         for(i=0; i<nrCastlingRights; i++)
14901         fprintf(debugFP, " %d", board[CASTLING][i]);
14902         fprintf(debugFP, "\n");
14903     }
14904
14905       while(*p==' ') p++;
14906     }
14907
14908     /* read e.p. field in games that know e.p. capture */
14909     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14910        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14911       if(*p=='-') {
14912         p++; board[EP_STATUS] = EP_NONE;
14913       } else {
14914          char c = *p++ - AAA;
14915
14916          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14917          if(*p >= '0' && *p <='9') *p++;
14918          board[EP_STATUS] = c;
14919       }
14920     }
14921
14922
14923     if(sscanf(p, "%d", &i) == 1) {
14924         FENrulePlies = i; /* 50-move ply counter */
14925         /* (The move number is still ignored)    */
14926     }
14927
14928     return TRUE;
14929 }
14930
14931 void
14932 EditPositionPasteFEN(char *fen)
14933 {
14934   if (fen != NULL) {
14935     Board initial_position;
14936
14937     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14938       DisplayError(_("Bad FEN position in clipboard"), 0);
14939       return ;
14940     } else {
14941       int savedBlackPlaysFirst = blackPlaysFirst;
14942       EditPositionEvent();
14943       blackPlaysFirst = savedBlackPlaysFirst;
14944       CopyBoard(boards[0], initial_position);
14945       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14946       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14947       DisplayBothClocks();
14948       DrawPosition(FALSE, boards[currentMove]);
14949     }
14950   }
14951 }
14952
14953 static char cseq[12] = "\\   ";
14954
14955 Boolean set_cont_sequence(char *new_seq)
14956 {
14957     int len;
14958     Boolean ret;
14959
14960     // handle bad attempts to set the sequence
14961         if (!new_seq)
14962                 return 0; // acceptable error - no debug
14963
14964     len = strlen(new_seq);
14965     ret = (len > 0) && (len < sizeof(cseq));
14966     if (ret)
14967         strcpy(cseq, new_seq);
14968     else if (appData.debugMode)
14969         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14970     return ret;
14971 }
14972
14973 /*
14974     reformat a source message so words don't cross the width boundary.  internal
14975     newlines are not removed.  returns the wrapped size (no null character unless
14976     included in source message).  If dest is NULL, only calculate the size required
14977     for the dest buffer.  lp argument indicats line position upon entry, and it's
14978     passed back upon exit.
14979 */
14980 int wrap(char *dest, char *src, int count, int width, int *lp)
14981 {
14982     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14983
14984     cseq_len = strlen(cseq);
14985     old_line = line = *lp;
14986     ansi = len = clen = 0;
14987
14988     for (i=0; i < count; i++)
14989     {
14990         if (src[i] == '\033')
14991             ansi = 1;
14992
14993         // if we hit the width, back up
14994         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14995         {
14996             // store i & len in case the word is too long
14997             old_i = i, old_len = len;
14998
14999             // find the end of the last word
15000             while (i && src[i] != ' ' && src[i] != '\n')
15001             {
15002                 i--;
15003                 len--;
15004             }
15005
15006             // word too long?  restore i & len before splitting it
15007             if ((old_i-i+clen) >= width)
15008             {
15009                 i = old_i;
15010                 len = old_len;
15011             }
15012
15013             // extra space?
15014             if (i && src[i-1] == ' ')
15015                 len--;
15016
15017             if (src[i] != ' ' && src[i] != '\n')
15018             {
15019                 i--;
15020                 if (len)
15021                     len--;
15022             }
15023
15024             // now append the newline and continuation sequence
15025             if (dest)
15026                 dest[len] = '\n';
15027             len++;
15028             if (dest)
15029                 strncpy(dest+len, cseq, cseq_len);
15030             len += cseq_len;
15031             line = cseq_len;
15032             clen = cseq_len;
15033             continue;
15034         }
15035
15036         if (dest)
15037             dest[len] = src[i];
15038         len++;
15039         if (!ansi)
15040             line++;
15041         if (src[i] == '\n')
15042             line = 0;
15043         if (src[i] == 'm')
15044             ansi = 0;
15045     }
15046     if (dest && appData.debugMode)
15047     {
15048         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15049             count, width, line, len, *lp);
15050         show_bytes(debugFP, src, count);
15051         fprintf(debugFP, "\ndest: ");
15052         show_bytes(debugFP, dest, len);
15053         fprintf(debugFP, "\n");
15054     }
15055     *lp = dest ? line : old_line;
15056
15057     return len;
15058 }
15059
15060 // [HGM] vari: routines for shelving variations
15061
15062 void 
15063 PushTail(int firstMove, int lastMove)
15064 {
15065         int i, j, nrMoves = lastMove - firstMove;
15066
15067         if(appData.icsActive) { // only in local mode
15068                 forwardMostMove = currentMove; // mimic old ICS behavior
15069                 return;
15070         }
15071         if(storedGames >= MAX_VARIATIONS-1) return;
15072
15073         // push current tail of game on stack
15074         savedResult[storedGames] = gameInfo.result;
15075         savedDetails[storedGames] = gameInfo.resultDetails;
15076         gameInfo.resultDetails = NULL;
15077         savedFirst[storedGames] = firstMove;
15078         savedLast [storedGames] = lastMove;
15079         savedFramePtr[storedGames] = framePtr;
15080         framePtr -= nrMoves; // reserve space for the boards
15081         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15082             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15083             for(j=0; j<MOVE_LEN; j++)
15084                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15085             for(j=0; j<2*MOVE_LEN; j++)
15086                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15087             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15088             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15089             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15090             pvInfoList[firstMove+i-1].depth = 0;
15091             commentList[framePtr+i] = commentList[firstMove+i];
15092             commentList[firstMove+i] = NULL;
15093         }
15094
15095         storedGames++;
15096         forwardMostMove = currentMove; // truncte game so we can start variation
15097         if(storedGames == 1) GreyRevert(FALSE);
15098 }
15099
15100 Boolean
15101 PopTail(Boolean annotate)
15102 {
15103         int i, j, nrMoves;
15104         char buf[8000], moveBuf[20];
15105
15106         if(appData.icsActive) return FALSE; // only in local mode
15107         if(!storedGames) return FALSE; // sanity
15108
15109         storedGames--;
15110         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15111         nrMoves = savedLast[storedGames] - currentMove;
15112         if(annotate) {
15113                 int cnt = 10;
15114                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15115                 else strcpy(buf, "(");
15116                 for(i=currentMove; i<forwardMostMove; i++) {
15117                         if(WhiteOnMove(i))
15118                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15119                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15120                         strcat(buf, moveBuf);
15121                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15122                 }
15123                 strcat(buf, ")");
15124         }
15125         for(i=1; i<nrMoves; i++) { // copy last variation back
15126             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15127             for(j=0; j<MOVE_LEN; j++)
15128                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15129             for(j=0; j<2*MOVE_LEN; j++)
15130                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15131             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15132             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15133             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15134             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15135             commentList[currentMove+i] = commentList[framePtr+i];
15136             commentList[framePtr+i] = NULL;
15137         }
15138         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15139         framePtr = savedFramePtr[storedGames];
15140         gameInfo.result = savedResult[storedGames];
15141         if(gameInfo.resultDetails != NULL) {
15142             free(gameInfo.resultDetails);
15143       }
15144         gameInfo.resultDetails = savedDetails[storedGames];
15145         forwardMostMove = currentMove + nrMoves;
15146         if(storedGames == 0) GreyRevert(TRUE);
15147         return TRUE;
15148 }
15149
15150 void 
15151 CleanupTail()
15152 {       // remove all shelved variations
15153         int i;
15154         for(i=0; i<storedGames; i++) {
15155             if(savedDetails[i])
15156                 free(savedDetails[i]);
15157             savedDetails[i] = NULL;
15158         }
15159         for(i=framePtr; i<MAX_MOVES; i++) {
15160                 if(commentList[i]) free(commentList[i]);
15161                 commentList[i] = NULL;
15162         }
15163         framePtr = MAX_MOVES-1;
15164         storedGames = 0;
15165 }