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