Also allow user to claim by offering draw before his move
[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 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 void
2068 read_from_ics(isr, closure, data, count, error)
2069      InputSourceRef isr;
2070      VOIDSTAR closure;
2071      char *data;
2072      int count;
2073      int error;
2074 {
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2084     
2085     static int started = STARTED_NONE;
2086     static char parse[20000];
2087     static int parse_pos = 0;
2088     static char buf[BUF_SIZE + 1];
2089     static int firstTime = TRUE, intfSet = FALSE;
2090     static ColorClass prevColor = ColorNormal;
2091     static int savingComment = FALSE;
2092     static int cmatch = 0; // continuation sequence match
2093     char *bp;
2094     char str[500];
2095     int i, oldi;
2096     int buf_len;
2097     int next_out;
2098     int tkind;
2099     int backup;    /* [DM] For zippy color lines */
2100     char *p;
2101     char talker[MSG_SIZ]; // [HGM] chat
2102     int channel;
2103
2104     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105
2106     if (appData.debugMode) {
2107       if (!error) {
2108         fprintf(debugFP, "<ICS: ");
2109         show_bytes(debugFP, data, count);
2110         fprintf(debugFP, "\n");
2111       }
2112     }
2113
2114     if (appData.debugMode) { int f = forwardMostMove;
2115         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2118     }
2119     if (count > 0) {
2120         /* If last read ended with a partial line that we couldn't parse,
2121            prepend it to the new read and try again. */
2122         if (leftover_len > 0) {
2123             for (i=0; i<leftover_len; i++)
2124               buf[i] = buf[leftover_start + i];
2125         }
2126
2127     /* copy new characters into the buffer */
2128     bp = buf + leftover_len;
2129     buf_len=leftover_len;
2130     for (i=0; i<count; i++)
2131     {
2132         // ignore these
2133         if (data[i] == '\r')
2134             continue;
2135
2136         // join lines split by ICS?
2137         if (!appData.noJoin)
2138         {
2139             /*
2140                 Joining just consists of finding matches against the
2141                 continuation sequence, and discarding that sequence
2142                 if found instead of copying it.  So, until a match
2143                 fails, there's nothing to do since it might be the
2144                 complete sequence, and thus, something we don't want
2145                 copied.
2146             */
2147             if (data[i] == cont_seq[cmatch])
2148             {
2149                 cmatch++;
2150                 if (cmatch == strlen(cont_seq))
2151                 {
2152                     cmatch = 0; // complete match.  just reset the counter
2153
2154                     /*
2155                         it's possible for the ICS to not include the space
2156                         at the end of the last word, making our [correct]
2157                         join operation fuse two separate words.  the server
2158                         does this when the space occurs at the width setting.
2159                     */
2160                     if (!buf_len || buf[buf_len-1] != ' ')
2161                     {
2162                         *bp++ = ' ';
2163                         buf_len++;
2164                     }
2165                 }
2166                 continue;
2167             }
2168             else if (cmatch)
2169             {
2170                 /*
2171                     match failed, so we have to copy what matched before
2172                     falling through and copying this character.  In reality,
2173                     this will only ever be just the newline character, but
2174                     it doesn't hurt to be precise.
2175                 */
2176                 strncpy(bp, cont_seq, cmatch);
2177                 bp += cmatch;
2178                 buf_len += cmatch;
2179                 cmatch = 0;
2180             }
2181         }
2182
2183         // copy this char
2184         *bp++ = data[i];
2185         buf_len++;
2186     }
2187
2188         buf[buf_len] = NULLCHAR;
2189 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2190         next_out = 0;
2191         leftover_start = 0;
2192         
2193         i = 0;
2194         while (i < buf_len) {
2195             /* Deal with part of the TELNET option negotiation
2196                protocol.  We refuse to do anything beyond the
2197                defaults, except that we allow the WILL ECHO option,
2198                which ICS uses to turn off password echoing when we are
2199                directly connected to it.  We reject this option
2200                if localLineEditing mode is on (always on in xboard)
2201                and we are talking to port 23, which might be a real
2202                telnet server that will try to keep WILL ECHO on permanently.
2203              */
2204             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206                 unsigned char option;
2207                 oldi = i;
2208                 switch ((unsigned char) buf[++i]) {
2209                   case TN_WILL:
2210                     if (appData.debugMode)
2211                       fprintf(debugFP, "\n<WILL ");
2212                     switch (option = (unsigned char) buf[++i]) {
2213                       case TN_ECHO:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "ECHO ");
2216                         /* Reply only if this is a change, according
2217                            to the protocol rules. */
2218                         if (remoteEchoOption) break;
2219                         if (appData.localLineEditing &&
2220                             atoi(appData.icsPort) == TN_PORT) {
2221                             TelnetRequest(TN_DONT, TN_ECHO);
2222                         } else {
2223                             EchoOff();
2224                             TelnetRequest(TN_DO, TN_ECHO);
2225                             remoteEchoOption = TRUE;
2226                         }
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         /* Whatever this is, we don't want it. */
2232                         TelnetRequest(TN_DONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_WONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<WONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       case TN_ECHO:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "ECHO ");
2243                         /* Reply only if this is a change, according
2244                            to the protocol rules. */
2245                         if (!remoteEchoOption) break;
2246                         EchoOn();
2247                         TelnetRequest(TN_DONT, TN_ECHO);
2248                         remoteEchoOption = FALSE;
2249                         break;
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", (unsigned char) option);
2253                         /* Whatever this is, it must already be turned
2254                            off, because we never agree to turn on
2255                            anything non-default, so according to the
2256                            protocol rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_DO:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<DO ");
2263                     switch (option = (unsigned char) buf[++i]) {
2264                       default:
2265                         /* Whatever this is, we refuse to do it. */
2266                         if (appData.debugMode)
2267                           fprintf(debugFP, "%d ", option);
2268                         TelnetRequest(TN_WONT, option);
2269                         break;
2270                     }
2271                     break;
2272                   case TN_DONT:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<DONT ");
2275                     switch (option = (unsigned char) buf[++i]) {
2276                       default:
2277                         if (appData.debugMode)
2278                           fprintf(debugFP, "%d ", option);
2279                         /* Whatever this is, we are already not doing
2280                            it, because we never agree to do anything
2281                            non-default, so according to the protocol
2282                            rules, we don't reply. */
2283                         break;
2284                     }
2285                     break;
2286                   case TN_IAC:
2287                     if (appData.debugMode)
2288                       fprintf(debugFP, "\n<IAC ");
2289                     /* Doubled IAC; pass it through */
2290                     i--;
2291                     break;
2292                   default:
2293                     if (appData.debugMode)
2294                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295                     /* Drop all other telnet commands on the floor */
2296                     break;
2297                 }
2298                 if (oldi > next_out)
2299                   SendToPlayer(&buf[next_out], oldi - next_out);
2300                 if (++i > next_out)
2301                   next_out = i;
2302                 continue;
2303             }
2304                 
2305             /* OK, this at least will *usually* work */
2306             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2307                 loggedOn = TRUE;
2308             }
2309             
2310             if (loggedOn && !intfSet) {
2311                 if (ics_type == ICS_ICC) {
2312                   sprintf(str,
2313                           "/set-quietly interface %s\n/set-quietly style 12\n",
2314                           programVersion);
2315                 } else if (ics_type == ICS_CHESSNET) {
2316                   sprintf(str, "/style 12\n");
2317                 } else {
2318                   strcpy(str, "alias $ @\n$set interface ");
2319                   strcat(str, programVersion);
2320                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 #ifdef WIN32
2322                   strcat(str, "$iset nohighlight 1\n");
2323 #endif
2324                   strcat(str, "$iset lock 1\n$style 12\n");
2325                 }
2326                 SendToICS(str);
2327                 NotifyFrontendLogin();
2328                 intfSet = TRUE;
2329             }
2330
2331             if (started == STARTED_COMMENT) {
2332                 /* Accumulate characters in comment */
2333                 parse[parse_pos++] = buf[i];
2334                 if (buf[i] == '\n') {
2335                     parse[parse_pos] = NULLCHAR;
2336                     if(chattingPartner>=0) {
2337                         char mess[MSG_SIZ];
2338                         sprintf(mess, "%s%s", talker, parse);
2339                         OutputChatMessage(chattingPartner, mess);
2340                         chattingPartner = -1;
2341                     } else
2342                     if(!suppressKibitz) // [HGM] kibitz
2343                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345                         int nrDigit = 0, nrAlph = 0, j;
2346                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348                         parse[parse_pos] = NULLCHAR;
2349                         // try to be smart: if it does not look like search info, it should go to
2350                         // ICS interaction window after all, not to engine-output window.
2351                         for(j=0; j<parse_pos; j++) { // count letters and digits
2352                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2354                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2355                         }
2356                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357                             int depth=0; float score;
2358                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360                                 pvInfoList[forwardMostMove-1].depth = depth;
2361                                 pvInfoList[forwardMostMove-1].score = 100*score;
2362                             }
2363                             OutputKibitz(suppressKibitz, parse);
2364                             next_out = i+1; // [HGM] suppress printing in ICS window
2365                         } else {
2366                             char tmp[MSG_SIZ];
2367                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368                             SendToPlayer(tmp, strlen(tmp));
2369                         }
2370                     }
2371                     started = STARTED_NONE;
2372                 } else {
2373                     /* Don't match patterns against characters in comment */
2374                     i++;
2375                     continue;
2376                 }
2377             }
2378             if (started == STARTED_CHATTER) {
2379                 if (buf[i] != '\n') {
2380                     /* Don't match patterns against characters in chatter */
2381                     i++;
2382                     continue;
2383                 }
2384                 started = STARTED_NONE;
2385             }
2386
2387             /* Kludge to deal with rcmd protocol */
2388             if (firstTime && looking_at(buf, &i, "\001*")) {
2389                 DisplayFatalError(&buf[1], 0, 1);
2390                 continue;
2391             } else {
2392                 firstTime = FALSE;
2393             }
2394
2395             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2396                 ics_type = ICS_ICC;
2397                 ics_prefix = "/";
2398                 if (appData.debugMode)
2399                   fprintf(debugFP, "ics_type %d\n", ics_type);
2400                 continue;
2401             }
2402             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403                 ics_type = ICS_FICS;
2404                 ics_prefix = "$";
2405                 if (appData.debugMode)
2406                   fprintf(debugFP, "ics_type %d\n", ics_type);
2407                 continue;
2408             }
2409             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410                 ics_type = ICS_CHESSNET;
2411                 ics_prefix = "/";
2412                 if (appData.debugMode)
2413                   fprintf(debugFP, "ics_type %d\n", ics_type);
2414                 continue;
2415             }
2416
2417             if (!loggedOn &&
2418                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2420                  looking_at(buf, &i, "will be \"*\""))) {
2421               strcpy(ics_handle, star_match[0]);
2422               continue;
2423             }
2424
2425             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426               char buf[MSG_SIZ];
2427               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428               DisplayIcsInteractionTitle(buf);
2429               have_set_title = TRUE;
2430             }
2431
2432             /* skip finger notes */
2433             if (started == STARTED_NONE &&
2434                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435                  (buf[i] == '1' && buf[i+1] == '0')) &&
2436                 buf[i+2] == ':' && buf[i+3] == ' ') {
2437               started = STARTED_CHATTER;
2438               i += 3;
2439               continue;
2440             }
2441
2442             /* skip formula vars */
2443             if (started == STARTED_NONE &&
2444                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445               started = STARTED_CHATTER;
2446               i += 3;
2447               continue;
2448             }
2449
2450             oldi = i;
2451             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452             if (appData.autoKibitz && started == STARTED_NONE && 
2453                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2454                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455                 if(looking_at(buf, &i, "* kibitzes: ") &&
2456                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2457                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2458                         suppressKibitz = TRUE;
2459                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460                                 && (gameMode == IcsPlayingWhite)) ||
2461                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2463                             started = STARTED_CHATTER; // own kibitz we simply discard
2464                         else {
2465                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466                             parse_pos = 0; parse[0] = NULLCHAR;
2467                             savingComment = TRUE;
2468                             suppressKibitz = gameMode != IcsObserving ? 2 :
2469                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2470                         } 
2471                         continue;
2472                 } else
2473                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474                     // suppress the acknowledgements of our own autoKibitz
2475                     SendToPlayer(star_match[0], strlen(star_match[0]));
2476                     looking_at(buf, &i, "*% "); // eat prompt
2477                     next_out = i;
2478                 }
2479             } // [HGM] kibitz: end of patch
2480
2481 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482
2483             // [HGM] chat: intercept tells by users for which we have an open chat window
2484             channel = -1;
2485             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2486                                            looking_at(buf, &i, "* whispers:") ||
2487                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2488                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489                 int p;
2490                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2491                 chattingPartner = -1;
2492
2493                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2494                 for(p=0; p<MAX_CHAT; p++) {
2495                     if(channel == atoi(chatPartner[p])) {
2496                     talker[0] = '['; strcat(talker, "]");
2497                     chattingPartner = p; break;
2498                     }
2499                 } else
2500                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2501                 for(p=0; p<MAX_CHAT; p++) {
2502                     if(!strcmp("WHISPER", chatPartner[p])) {
2503                         talker[0] = '['; strcat(talker, "]");
2504                         chattingPartner = p; break;
2505                     }
2506                 }
2507                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2508                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509                     talker[0] = 0;
2510                     chattingPartner = p; break;
2511                 }
2512                 if(chattingPartner<0) i = oldi; else {
2513                     started = STARTED_COMMENT;
2514                     parse_pos = 0; parse[0] = NULLCHAR;
2515                     savingComment = TRUE;
2516                     suppressKibitz = TRUE;
2517                 }
2518             } // [HGM] chat: end of patch
2519
2520             if (appData.zippyTalk || appData.zippyPlay) {
2521                 /* [DM] Backup address for color zippy lines */
2522                 backup = i;
2523 #if ZIPPY
2524        #ifdef WIN32
2525                if (loggedOn == TRUE)
2526                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528        #else
2529                 if (ZippyControl(buf, &i) ||
2530                     ZippyConverse(buf, &i) ||
2531                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532                       loggedOn = TRUE;
2533                       if (!appData.colorize) continue;
2534                 }
2535        #endif
2536 #endif
2537             } // [DM] 'else { ' deleted
2538                 if (
2539                     /* Regular tells and says */
2540                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2542                     looking_at(buf, &i, "* says: ") ||
2543                     /* Don't color "message" or "messages" output */
2544                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545                     looking_at(buf, &i, "*. * at *:*: ") ||
2546                     looking_at(buf, &i, "--* (*:*): ") ||
2547                     /* Message notifications (same color as tells) */
2548                     looking_at(buf, &i, "* has left a message ") ||
2549                     looking_at(buf, &i, "* just sent you a message:\n") ||
2550                     /* Whispers and kibitzes */
2551                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552                     looking_at(buf, &i, "* kibitzes: ") ||
2553                     /* Channel tells */
2554                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555
2556                   if (tkind == 1 && strchr(star_match[0], ':')) {
2557                       /* Avoid "tells you:" spoofs in channels */
2558                      tkind = 3;
2559                   }
2560                   if (star_match[0][0] == NULLCHAR ||
2561                       strchr(star_match[0], ' ') ||
2562                       (tkind == 3 && strchr(star_match[1], ' '))) {
2563                     /* Reject bogus matches */
2564                     i = oldi;
2565                   } else {
2566                     if (appData.colorize) {
2567                       if (oldi > next_out) {
2568                         SendToPlayer(&buf[next_out], oldi - next_out);
2569                         next_out = oldi;
2570                       }
2571                       switch (tkind) {
2572                       case 1:
2573                         Colorize(ColorTell, FALSE);
2574                         curColor = ColorTell;
2575                         break;
2576                       case 2:
2577                         Colorize(ColorKibitz, FALSE);
2578                         curColor = ColorKibitz;
2579                         break;
2580                       case 3:
2581                         p = strrchr(star_match[1], '(');
2582                         if (p == NULL) {
2583                           p = star_match[1];
2584                         } else {
2585                           p++;
2586                         }
2587                         if (atoi(p) == 1) {
2588                           Colorize(ColorChannel1, FALSE);
2589                           curColor = ColorChannel1;
2590                         } else {
2591                           Colorize(ColorChannel, FALSE);
2592                           curColor = ColorChannel;
2593                         }
2594                         break;
2595                       case 5:
2596                         curColor = ColorNormal;
2597                         break;
2598                       }
2599                     }
2600                     if (started == STARTED_NONE && appData.autoComment &&
2601                         (gameMode == IcsObserving ||
2602                          gameMode == IcsPlayingWhite ||
2603                          gameMode == IcsPlayingBlack)) {
2604                       parse_pos = i - oldi;
2605                       memcpy(parse, &buf[oldi], parse_pos);
2606                       parse[parse_pos] = NULLCHAR;
2607                       started = STARTED_COMMENT;
2608                       savingComment = TRUE;
2609                     } else {
2610                       started = STARTED_CHATTER;
2611                       savingComment = FALSE;
2612                     }
2613                     loggedOn = TRUE;
2614                     continue;
2615                   }
2616                 }
2617
2618                 if (looking_at(buf, &i, "* s-shouts: ") ||
2619                     looking_at(buf, &i, "* c-shouts: ")) {
2620                     if (appData.colorize) {
2621                         if (oldi > next_out) {
2622                             SendToPlayer(&buf[next_out], oldi - next_out);
2623                             next_out = oldi;
2624                         }
2625                         Colorize(ColorSShout, FALSE);
2626                         curColor = ColorSShout;
2627                     }
2628                     loggedOn = TRUE;
2629                     started = STARTED_CHATTER;
2630                     continue;
2631                 }
2632
2633                 if (looking_at(buf, &i, "--->")) {
2634                     loggedOn = TRUE;
2635                     continue;
2636                 }
2637
2638                 if (looking_at(buf, &i, "* shouts: ") ||
2639                     looking_at(buf, &i, "--> ")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorShout, FALSE);
2646                         curColor = ColorShout;
2647                     }
2648                     loggedOn = TRUE;
2649                     started = STARTED_CHATTER;
2650                     continue;
2651                 }
2652
2653                 if (looking_at( buf, &i, "Challenge:")) {
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorChallenge, FALSE);
2660                         curColor = ColorChallenge;
2661                     }
2662                     loggedOn = TRUE;
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* offers you") ||
2667                     looking_at(buf, &i, "* offers to be") ||
2668                     looking_at(buf, &i, "* would like to") ||
2669                     looking_at(buf, &i, "* requests to") ||
2670                     looking_at(buf, &i, "Your opponent offers") ||
2671                     looking_at(buf, &i, "Your opponent requests")) {
2672
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorRequest, FALSE);
2679                         curColor = ColorRequest;
2680                     }
2681                     continue;
2682                 }
2683
2684                 if (looking_at(buf, &i, "* (*) seeking")) {
2685                     if (appData.colorize) {
2686                         if (oldi > next_out) {
2687                             SendToPlayer(&buf[next_out], oldi - next_out);
2688                             next_out = oldi;
2689                         }
2690                         Colorize(ColorSeek, FALSE);
2691                         curColor = ColorSeek;
2692                     }
2693                     continue;
2694             }
2695
2696             if (looking_at(buf, &i, "\\   ")) {
2697                 if (prevColor != ColorNormal) {
2698                     if (oldi > next_out) {
2699                         SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = oldi;
2701                     }
2702                     Colorize(prevColor, TRUE);
2703                     curColor = prevColor;
2704                 }
2705                 if (savingComment) {
2706                     parse_pos = i - oldi;
2707                     memcpy(parse, &buf[oldi], parse_pos);
2708                     parse[parse_pos] = NULLCHAR;
2709                     started = STARTED_COMMENT;
2710                 } else {
2711                     started = STARTED_CHATTER;
2712                 }
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Black Strength :") ||
2717                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2718                 looking_at(buf, &i, "<10>") ||
2719                 looking_at(buf, &i, "#@#")) {
2720                 /* Wrong board style */
2721                 loggedOn = TRUE;
2722                 SendToICS(ics_prefix);
2723                 SendToICS("set style 12\n");
2724                 SendToICS(ics_prefix);
2725                 SendToICS("refresh\n");
2726                 continue;
2727             }
2728             
2729             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730                 ICSInitScript();
2731                 have_sent_ICS_logon = 1;
2732                 continue;
2733             }
2734               
2735             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2736                 (looking_at(buf, &i, "\n<12> ") ||
2737                  looking_at(buf, &i, "<12> "))) {
2738                 loggedOn = TRUE;
2739                 if (oldi > next_out) {
2740                     SendToPlayer(&buf[next_out], oldi - next_out);
2741                 }
2742                 next_out = i;
2743                 started = STARTED_BOARD;
2744                 parse_pos = 0;
2745                 continue;
2746             }
2747
2748             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2749                 looking_at(buf, &i, "<b1> ")) {
2750                 if (oldi > next_out) {
2751                     SendToPlayer(&buf[next_out], oldi - next_out);
2752                 }
2753                 next_out = i;
2754                 started = STARTED_HOLDINGS;
2755                 parse_pos = 0;
2756                 continue;
2757             }
2758
2759             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760                 loggedOn = TRUE;
2761                 /* Header for a move list -- first line */
2762
2763                 switch (ics_getting_history) {
2764                   case H_FALSE:
2765                     switch (gameMode) {
2766                       case IcsIdle:
2767                       case BeginningOfGame:
2768                         /* User typed "moves" or "oldmoves" while we
2769                            were idle.  Pretend we asked for these
2770                            moves and soak them up so user can step
2771                            through them and/or save them.
2772                            */
2773                         Reset(FALSE, TRUE);
2774                         gameMode = IcsObserving;
2775                         ModeHighlight();
2776                         ics_gamenum = -1;
2777                         ics_getting_history = H_GOT_UNREQ_HEADER;
2778                         break;
2779                       case EditGame: /*?*/
2780                       case EditPosition: /*?*/
2781                         /* Should above feature work in these modes too? */
2782                         /* For now it doesn't */
2783                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2784                         break;
2785                       default:
2786                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2787                         break;
2788                     }
2789                     break;
2790                   case H_REQUESTED:
2791                     /* Is this the right one? */
2792                     if (gameInfo.white && gameInfo.black &&
2793                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2794                         strcmp(gameInfo.black, star_match[2]) == 0) {
2795                         /* All is well */
2796                         ics_getting_history = H_GOT_REQ_HEADER;
2797                     }
2798                     break;
2799                   case H_GOT_REQ_HEADER:
2800                   case H_GOT_UNREQ_HEADER:
2801                   case H_GOT_UNWANTED_HEADER:
2802                   case H_GETTING_MOVES:
2803                     /* Should not happen */
2804                     DisplayError(_("Error gathering move list: two headers"), 0);
2805                     ics_getting_history = H_FALSE;
2806                     break;
2807                 }
2808
2809                 /* Save player ratings into gameInfo if needed */
2810                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2811                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2812                     (gameInfo.whiteRating == -1 ||
2813                      gameInfo.blackRating == -1)) {
2814
2815                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2816                     gameInfo.blackRating = string_to_rating(star_match[3]);
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2819                               gameInfo.whiteRating, gameInfo.blackRating);
2820                 }
2821                 continue;
2822             }
2823
2824             if (looking_at(buf, &i,
2825               "* * match, initial time: * minute*, increment: * second")) {
2826                 /* Header for a move list -- second line */
2827                 /* Initial board will follow if this is a wild game */
2828                 if (gameInfo.event != NULL) free(gameInfo.event);
2829                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2830                 gameInfo.event = StrSave(str);
2831                 /* [HGM] we switched variant. Translate boards if needed. */
2832                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2833                 continue;
2834             }
2835
2836             if (looking_at(buf, &i, "Move  ")) {
2837                 /* Beginning of a move list */
2838                 switch (ics_getting_history) {
2839                   case H_FALSE:
2840                     /* Normally should not happen */
2841                     /* Maybe user hit reset while we were parsing */
2842                     break;
2843                   case H_REQUESTED:
2844                     /* Happens if we are ignoring a move list that is not
2845                      * the one we just requested.  Common if the user
2846                      * tries to observe two games without turning off
2847                      * getMoveList */
2848                     break;
2849                   case H_GETTING_MOVES:
2850                     /* Should not happen */
2851                     DisplayError(_("Error gathering move list: nested"), 0);
2852                     ics_getting_history = H_FALSE;
2853                     break;
2854                   case H_GOT_REQ_HEADER:
2855                     ics_getting_history = H_GETTING_MOVES;
2856                     started = STARTED_MOVES;
2857                     parse_pos = 0;
2858                     if (oldi > next_out) {
2859                         SendToPlayer(&buf[next_out], oldi - next_out);
2860                     }
2861                     break;
2862                   case H_GOT_UNREQ_HEADER:
2863                     ics_getting_history = H_GETTING_MOVES;
2864                     started = STARTED_MOVES_NOHIDE;
2865                     parse_pos = 0;
2866                     break;
2867                   case H_GOT_UNWANTED_HEADER:
2868                     ics_getting_history = H_FALSE;
2869                     break;
2870                 }
2871                 continue;
2872             }                           
2873             
2874             if (looking_at(buf, &i, "% ") ||
2875                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2876                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2877                 if(suppressKibitz) next_out = i;
2878                 savingComment = FALSE;
2879                 suppressKibitz = 0;
2880                 switch (started) {
2881                   case STARTED_MOVES:
2882                   case STARTED_MOVES_NOHIDE:
2883                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2884                     parse[parse_pos + i - oldi] = NULLCHAR;
2885                     ParseGameHistory(parse);
2886 #if ZIPPY
2887                     if (appData.zippyPlay && first.initDone) {
2888                         FeedMovesToProgram(&first, forwardMostMove);
2889                         if (gameMode == IcsPlayingWhite) {
2890                             if (WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("black\n", &first); 
2894                                   }
2895                                   SendTimeRemaining(&first, TRUE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, TRUE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         } else if (gameMode == IcsPlayingBlack) {
2914                             if (!WhiteOnMove(forwardMostMove)) {
2915                                 if (first.sendTime) {
2916                                   if (first.useColors) {
2917                                     SendToProgram("white\n", &first);
2918                                   }
2919                                   SendTimeRemaining(&first, FALSE);
2920                                 }
2921                                 if (first.useColors) {
2922                                   SendToProgram("black\n", &first);
2923                                 }
2924                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2925                                 first.maybeThinking = TRUE;
2926                             } else {
2927                                 if (first.usePlayother) {
2928                                   if (first.sendTime) {
2929                                     SendTimeRemaining(&first, FALSE);
2930                                   }
2931                                   SendToProgram("playother\n", &first);
2932                                   firstMove = FALSE;
2933                                 } else {
2934                                   firstMove = TRUE;
2935                                 }
2936                             }
2937                         }                       
2938                     }
2939 #endif
2940                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2941                         /* Moves came from oldmoves or moves command
2942                            while we weren't doing anything else.
2943                            */
2944                         currentMove = forwardMostMove;
2945                         ClearHighlights();/*!!could figure this out*/
2946                         flipView = appData.flipView;
2947                         DrawPosition(TRUE, boards[currentMove]);
2948                         DisplayBothClocks();
2949                         sprintf(str, "%s vs. %s",
2950                                 gameInfo.white, gameInfo.black);
2951                         DisplayTitle(str);
2952                         gameMode = IcsIdle;
2953                     } else {
2954                         /* Moves were history of an active game */
2955                         if (gameInfo.resultDetails != NULL) {
2956                             free(gameInfo.resultDetails);
2957                             gameInfo.resultDetails = NULL;
2958                         }
2959                     }
2960                     HistorySet(parseList, backwardMostMove,
2961                                forwardMostMove, currentMove-1);
2962                     DisplayMove(currentMove - 1);
2963                     if (started == STARTED_MOVES) next_out = i;
2964                     started = STARTED_NONE;
2965                     ics_getting_history = H_FALSE;
2966                     break;
2967
2968                   case STARTED_OBSERVE:
2969                     started = STARTED_NONE;
2970                     SendToICS(ics_prefix);
2971                     SendToICS("refresh\n");
2972                     break;
2973
2974                   default:
2975                     break;
2976                 }
2977                 if(bookHit) { // [HGM] book: simulate book reply
2978                     static char bookMove[MSG_SIZ]; // a bit generous?
2979
2980                     programStats.nodes = programStats.depth = programStats.time = 
2981                     programStats.score = programStats.got_only_move = 0;
2982                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983
2984                     strcpy(bookMove, "move ");
2985                     strcat(bookMove, bookHit);
2986                     HandleMachineMove(bookMove, &first);
2987                 }
2988                 continue;
2989             }
2990             
2991             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2992                  started == STARTED_HOLDINGS ||
2993                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2994                 /* Accumulate characters in move list or board */
2995                 parse[parse_pos++] = buf[i];
2996             }
2997             
2998             /* Start of game messages.  Mostly we detect start of game
2999                when the first board image arrives.  On some versions
3000                of the ICS, though, we need to do a "refresh" after starting
3001                to observe in order to get the current board right away. */
3002             if (looking_at(buf, &i, "Adding game * to observation list")) {
3003                 started = STARTED_OBSERVE;
3004                 continue;
3005             }
3006
3007             /* Handle auto-observe */
3008             if (appData.autoObserve &&
3009                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3010                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011                 char *player;
3012                 /* Choose the player that was highlighted, if any. */
3013                 if (star_match[0][0] == '\033' ||
3014                     star_match[1][0] != '\033') {
3015                     player = star_match[0];
3016                 } else {
3017                     player = star_match[2];
3018                 }
3019                 sprintf(str, "%sobserve %s\n",
3020                         ics_prefix, StripHighlightAndTitle(player));
3021                 SendToICS(str);
3022
3023                 /* Save ratings from notify string */
3024                 strcpy(player1Name, star_match[0]);
3025                 player1Rating = string_to_rating(star_match[1]);
3026                 strcpy(player2Name, star_match[2]);
3027                 player2Rating = string_to_rating(star_match[3]);
3028
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, 
3031                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3032                           player1Name, player1Rating,
3033                           player2Name, player2Rating);
3034
3035                 continue;
3036             }
3037
3038             /* Deal with automatic examine mode after a game,
3039                and with IcsObserving -> IcsExamining transition */
3040             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3041                 looking_at(buf, &i, "has made you an examiner of game *")) {
3042
3043                 int gamenum = atoi(star_match[0]);
3044                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3045                     gamenum == ics_gamenum) {
3046                     /* We were already playing or observing this game;
3047                        no need to refetch history */
3048                     gameMode = IcsExamining;
3049                     if (pausing) {
3050                         pauseExamForwardMostMove = forwardMostMove;
3051                     } else if (currentMove < forwardMostMove) {
3052                         ForwardInner(forwardMostMove);
3053                     }
3054                 } else {
3055                     /* I don't think this case really can happen */
3056                     SendToICS(ics_prefix);
3057                     SendToICS("refresh\n");
3058                 }
3059                 continue;
3060             }    
3061             
3062             /* Error messages */
3063 //          if (ics_user_moved) {
3064             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3065                 if (looking_at(buf, &i, "Illegal move") ||
3066                     looking_at(buf, &i, "Not a legal move") ||
3067                     looking_at(buf, &i, "Your king is in check") ||
3068                     looking_at(buf, &i, "It isn't your turn") ||
3069                     looking_at(buf, &i, "It is not your move")) {
3070                     /* Illegal move */
3071                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3072                         currentMove = --forwardMostMove;
3073                         DisplayMove(currentMove - 1); /* before DMError */
3074                         DrawPosition(FALSE, boards[currentMove]);
3075                         SwitchClocks();
3076                         DisplayBothClocks();
3077                     }
3078                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3079                     ics_user_moved = 0;
3080                     continue;
3081                 }
3082             }
3083
3084             if (looking_at(buf, &i, "still have time") ||
3085                 looking_at(buf, &i, "not out of time") ||
3086                 looking_at(buf, &i, "either player is out of time") ||
3087                 looking_at(buf, &i, "has timeseal; checking")) {
3088                 /* We must have called his flag a little too soon */
3089                 whiteFlag = blackFlag = FALSE;
3090                 continue;
3091             }
3092
3093             if (looking_at(buf, &i, "added * seconds to") ||
3094                 looking_at(buf, &i, "seconds were added to")) {
3095                 /* Update the clocks */
3096                 SendToICS(ics_prefix);
3097                 SendToICS("refresh\n");
3098                 continue;
3099             }
3100
3101             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3102                 ics_clock_paused = TRUE;
3103                 StopClocks();
3104                 continue;
3105             }
3106
3107             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3108                 ics_clock_paused = FALSE;
3109                 StartClocks();
3110                 continue;
3111             }
3112
3113             /* Grab player ratings from the Creating: message.
3114                Note we have to check for the special case when
3115                the ICS inserts things like [white] or [black]. */
3116             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3117                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118                 /* star_matches:
3119                    0    player 1 name (not necessarily white)
3120                    1    player 1 rating
3121                    2    empty, white, or black (IGNORED)
3122                    3    player 2 name (not necessarily black)
3123                    4    player 2 rating
3124                    
3125                    The names/ratings are sorted out when the game
3126                    actually starts (below).
3127                 */
3128                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3129                 player1Rating = string_to_rating(star_match[1]);
3130                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3131                 player2Rating = string_to_rating(star_match[4]);
3132
3133                 if (appData.debugMode)
3134                   fprintf(debugFP, 
3135                           "Ratings from 'Creating:' %s %d, %s %d\n",
3136                           player1Name, player1Rating,
3137                           player2Name, player2Rating);
3138
3139                 continue;
3140             }
3141             
3142             /* Improved generic start/end-of-game messages */
3143             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3144                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3145                 /* If tkind == 0: */
3146                 /* star_match[0] is the game number */
3147                 /*           [1] is the white player's name */
3148                 /*           [2] is the black player's name */
3149                 /* For end-of-game: */
3150                 /*           [3] is the reason for the game end */
3151                 /*           [4] is a PGN end game-token, preceded by " " */
3152                 /* For start-of-game: */
3153                 /*           [3] begins with "Creating" or "Continuing" */
3154                 /*           [4] is " *" or empty (don't care). */
3155                 int gamenum = atoi(star_match[0]);
3156                 char *whitename, *blackname, *why, *endtoken;
3157                 ChessMove endtype = (ChessMove) 0;
3158
3159                 if (tkind == 0) {
3160                   whitename = star_match[1];
3161                   blackname = star_match[2];
3162                   why = star_match[3];
3163                   endtoken = star_match[4];
3164                 } else {
3165                   whitename = star_match[1];
3166                   blackname = star_match[3];
3167                   why = star_match[5];
3168                   endtoken = star_match[6];
3169                 }
3170
3171                 /* Game start messages */
3172                 if (strncmp(why, "Creating ", 9) == 0 ||
3173                     strncmp(why, "Continuing ", 11) == 0) {
3174                     gs_gamenum = gamenum;
3175                     strcpy(gs_kind, strchr(why, ' ') + 1);
3176                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 #if ZIPPY
3178                     if (appData.zippyPlay) {
3179                         ZippyGameStart(whitename, blackname);
3180                     }
3181 #endif /*ZIPPY*/
3182                     continue;
3183                 }
3184
3185                 /* Game end messages */
3186                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3187                     ics_gamenum != gamenum) {
3188                     continue;
3189                 }
3190                 while (endtoken[0] == ' ') endtoken++;
3191                 switch (endtoken[0]) {
3192                   case '*':
3193                   default:
3194                     endtype = GameUnfinished;
3195                     break;
3196                   case '0':
3197                     endtype = BlackWins;
3198                     break;
3199                   case '1':
3200                     if (endtoken[1] == '/')
3201                       endtype = GameIsDrawn;
3202                     else
3203                       endtype = WhiteWins;
3204                     break;
3205                 }
3206                 GameEnds(endtype, why, GE_ICS);
3207 #if ZIPPY
3208                 if (appData.zippyPlay && first.initDone) {
3209                     ZippyGameEnd(endtype, why);
3210                     if (first.pr == NULL) {
3211                       /* Start the next process early so that we'll
3212                          be ready for the next challenge */
3213                       StartChessProgram(&first);
3214                     }
3215                     /* Send "new" early, in case this command takes
3216                        a long time to finish, so that we'll be ready
3217                        for the next challenge. */
3218                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3219                     Reset(TRUE, TRUE);
3220                 }
3221 #endif /*ZIPPY*/
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "Removing game * from observation") ||
3226                 looking_at(buf, &i, "no longer observing game *") ||
3227                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3228                 if (gameMode == IcsObserving &&
3229                     atoi(star_match[0]) == ics_gamenum)
3230                   {
3231                       /* icsEngineAnalyze */
3232                       if (appData.icsEngineAnalyze) {
3233                             ExitAnalyzeMode();
3234                             ModeHighlight();
3235                       }
3236                       StopClocks();
3237                       gameMode = IcsIdle;
3238                       ics_gamenum = -1;
3239                       ics_user_moved = FALSE;
3240                   }
3241                 continue;
3242             }
3243
3244             if (looking_at(buf, &i, "no longer examining game *")) {
3245                 if (gameMode == IcsExamining &&
3246                     atoi(star_match[0]) == ics_gamenum)
3247                   {
3248                       gameMode = IcsIdle;
3249                       ics_gamenum = -1;
3250                       ics_user_moved = FALSE;
3251                   }
3252                 continue;
3253             }
3254
3255             /* Advance leftover_start past any newlines we find,
3256                so only partial lines can get reparsed */
3257             if (looking_at(buf, &i, "\n")) {
3258                 prevColor = curColor;
3259                 if (curColor != ColorNormal) {
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                     }
3264                     Colorize(ColorNormal, FALSE);
3265                     curColor = ColorNormal;
3266                 }
3267                 if (started == STARTED_BOARD) {
3268                     started = STARTED_NONE;
3269                     parse[parse_pos] = NULLCHAR;
3270                     ParseBoard12(parse);
3271                     ics_user_moved = 0;
3272
3273                     /* Send premove here */
3274                     if (appData.premove) {
3275                       char str[MSG_SIZ];
3276                       if (currentMove == 0 &&
3277                           gameMode == IcsPlayingWhite &&
3278                           appData.premoveWhite) {
3279                         sprintf(str, "%s\n", appData.premoveWhiteText);
3280                         if (appData.debugMode)
3281                           fprintf(debugFP, "Sending premove:\n");
3282                         SendToICS(str);
3283                       } else if (currentMove == 1 &&
3284                                  gameMode == IcsPlayingBlack &&
3285                                  appData.premoveBlack) {
3286                         sprintf(str, "%s\n", appData.premoveBlackText);
3287                         if (appData.debugMode)
3288                           fprintf(debugFP, "Sending premove:\n");
3289                         SendToICS(str);
3290                       } else if (gotPremove) {
3291                         gotPremove = 0;
3292                         ClearPremoveHighlights();
3293                         if (appData.debugMode)
3294                           fprintf(debugFP, "Sending premove:\n");
3295                           UserMoveEvent(premoveFromX, premoveFromY, 
3296                                         premoveToX, premoveToY, 
3297                                         premovePromoChar);
3298                       }
3299                     }
3300
3301                     /* Usually suppress following prompt */
3302                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3303                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3304                         if (looking_at(buf, &i, "*% ")) {
3305                             savingComment = FALSE;
3306                             suppressKibitz = 0;
3307                         }
3308                     }
3309                     next_out = i;
3310                 } else if (started == STARTED_HOLDINGS) {
3311                     int gamenum;
3312                     char new_piece[MSG_SIZ];
3313                     started = STARTED_NONE;
3314                     parse[parse_pos] = NULLCHAR;
3315                     if (appData.debugMode)
3316                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3317                                                         parse, currentMove);
3318                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3319                         gamenum == ics_gamenum) {
3320                         if (gameInfo.variant == VariantNormal) {
3321                           /* [HGM] We seem to switch variant during a game!
3322                            * Presumably no holdings were displayed, so we have
3323                            * to move the position two files to the right to
3324                            * create room for them!
3325                            */
3326                           VariantClass newVariant;
3327                           switch(gameInfo.boardWidth) { // base guess on board width
3328                                 case 9:  newVariant = VariantShogi; break;
3329                                 case 10: newVariant = VariantGreat; break;
3330                                 default: newVariant = VariantCrazyhouse; break;
3331                           }
3332                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3333                           /* Get a move list just to see the header, which
3334                              will tell us whether this is really bug or zh */
3335                           if (ics_getting_history == H_FALSE) {
3336                             ics_getting_history = H_REQUESTED;
3337                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3338                             SendToICS(str);
3339                           }
3340                         }
3341                         new_piece[0] = NULLCHAR;
3342                         sscanf(parse, "game %d white [%s black [%s <- %s",
3343                                &gamenum, white_holding, black_holding,
3344                                new_piece);
3345                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3346                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3347                         /* [HGM] copy holdings to board holdings area */
3348                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3349                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3350                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 #if ZIPPY
3352                         if (appData.zippyPlay && first.initDone) {
3353                             ZippyHoldings(white_holding, black_holding,
3354                                           new_piece);
3355                         }
3356 #endif /*ZIPPY*/
3357                         if (tinyLayout || smallLayout) {
3358                             char wh[16], bh[16];
3359                             PackHolding(wh, white_holding);
3360                             PackHolding(bh, black_holding);
3361                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3362                                     gameInfo.white, gameInfo.black);
3363                         } else {
3364                             sprintf(str, "%s [%s] vs. %s [%s]",
3365                                     gameInfo.white, white_holding,
3366                                     gameInfo.black, black_holding);
3367                         }
3368
3369                         DrawPosition(FALSE, boards[currentMove]);
3370                         DisplayTitle(str);
3371                     }
3372                     /* Suppress following prompt */
3373                     if (looking_at(buf, &i, "*% ")) {
3374                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3375                         savingComment = FALSE;
3376                         suppressKibitz = 0;
3377                     }
3378                     next_out = i;
3379                 }
3380                 continue;
3381             }
3382
3383             i++;                /* skip unparsed character and loop back */
3384         }
3385         
3386         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3387 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3388 //          SendToPlayer(&buf[next_out], i - next_out);
3389             started != STARTED_HOLDINGS && leftover_start > next_out) {
3390             SendToPlayer(&buf[next_out], leftover_start - next_out);
3391             next_out = i;
3392         }
3393         
3394         leftover_len = buf_len - leftover_start;
3395         /* if buffer ends with something we couldn't parse,
3396            reparse it after appending the next read */
3397         
3398     } else if (count == 0) {
3399         RemoveInputSource(isr);
3400         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401     } else {
3402         DisplayFatalError(_("Error reading from ICS"), error, 1);
3403     }
3404 }
3405
3406
3407 /* Board style 12 looks like this:
3408    
3409    <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
3410    
3411  * The "<12> " is stripped before it gets to this routine.  The two
3412  * trailing 0's (flip state and clock ticking) are later addition, and
3413  * some chess servers may not have them, or may have only the first.
3414  * Additional trailing fields may be added in the future.  
3415  */
3416
3417 #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"
3418
3419 #define RELATION_OBSERVING_PLAYED    0
3420 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3421 #define RELATION_PLAYING_MYMOVE      1
3422 #define RELATION_PLAYING_NOTMYMOVE  -1
3423 #define RELATION_EXAMINING           2
3424 #define RELATION_ISOLATED_BOARD     -3
3425 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3426
3427 void
3428 ParseBoard12(string)
3429      char *string;
3430
3431     GameMode newGameMode;
3432     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3433     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3434     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3435     char to_play, board_chars[200];
3436     char move_str[500], str[500], elapsed_time[500];
3437     char black[32], white[32];
3438     Board board;
3439     int prevMove = currentMove;
3440     int ticking = 2;
3441     ChessMove moveType;
3442     int fromX, fromY, toX, toY;
3443     char promoChar;
3444     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3445     char *bookHit = NULL; // [HGM] book
3446     Boolean weird = FALSE, reqFlag = FALSE;
3447
3448     fromX = fromY = toX = toY = -1;
3449     
3450     newGame = FALSE;
3451
3452     if (appData.debugMode)
3453       fprintf(debugFP, _("Parsing board: %s\n"), string);
3454
3455     move_str[0] = NULLCHAR;
3456     elapsed_time[0] = NULLCHAR;
3457     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458         int  i = 0, j;
3459         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3460             if(string[i] == ' ') { ranks++; files = 0; }
3461             else files++;
3462             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3463             i++;
3464         }
3465         for(j = 0; j <i; j++) board_chars[j] = string[j];
3466         board_chars[i] = '\0';
3467         string += i + 1;
3468     }
3469     n = sscanf(string, PATTERN, &to_play, &double_push,
3470                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3471                &gamenum, white, black, &relation, &basetime, &increment,
3472                &white_stren, &black_stren, &white_time, &black_time,
3473                &moveNum, str, elapsed_time, move_str, &ics_flip,
3474                &ticking);
3475
3476     if (n < 21) {
3477         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3478         DisplayError(str, 0);
3479         return;
3480     }
3481
3482     /* Convert the move number to internal form */
3483     moveNum = (moveNum - 1) * 2;
3484     if (to_play == 'B') moveNum++;
3485     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3486       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3487                         0, 1);
3488       return;
3489     }
3490     
3491     switch (relation) {
3492       case RELATION_OBSERVING_PLAYED:
3493       case RELATION_OBSERVING_STATIC:
3494         if (gamenum == -1) {
3495             /* Old ICC buglet */
3496             relation = RELATION_OBSERVING_STATIC;
3497         }
3498         newGameMode = IcsObserving;
3499         break;
3500       case RELATION_PLAYING_MYMOVE:
3501       case RELATION_PLAYING_NOTMYMOVE:
3502         newGameMode =
3503           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3504             IcsPlayingWhite : IcsPlayingBlack;
3505         break;
3506       case RELATION_EXAMINING:
3507         newGameMode = IcsExamining;
3508         break;
3509       case RELATION_ISOLATED_BOARD:
3510       default:
3511         /* Just display this board.  If user was doing something else,
3512            we will forget about it until the next board comes. */ 
3513         newGameMode = IcsIdle;
3514         break;
3515       case RELATION_STARTING_POSITION:
3516         newGameMode = gameMode;
3517         break;
3518     }
3519     
3520     /* Modify behavior for initial board display on move listing
3521        of wild games.
3522        */
3523     switch (ics_getting_history) {
3524       case H_FALSE:
3525       case H_REQUESTED:
3526         break;
3527       case H_GOT_REQ_HEADER:
3528       case H_GOT_UNREQ_HEADER:
3529         /* This is the initial position of the current game */
3530         gamenum = ics_gamenum;
3531         moveNum = 0;            /* old ICS bug workaround */
3532         if (to_play == 'B') {
3533           startedFromSetupPosition = TRUE;
3534           blackPlaysFirst = TRUE;
3535           moveNum = 1;
3536           if (forwardMostMove == 0) forwardMostMove = 1;
3537           if (backwardMostMove == 0) backwardMostMove = 1;
3538           if (currentMove == 0) currentMove = 1;
3539         }
3540         newGameMode = gameMode;
3541         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542         break;
3543       case H_GOT_UNWANTED_HEADER:
3544         /* This is an initial board that we don't want */
3545         return;
3546       case H_GETTING_MOVES:
3547         /* Should not happen */
3548         DisplayError(_("Error gathering move list: extra board"), 0);
3549         ics_getting_history = H_FALSE;
3550         return;
3551     }
3552
3553    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3554                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3555      /* [HGM] We seem to have switched variant unexpectedly
3556       * Try to guess new variant from board size
3557       */
3558           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3559           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3560           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3561           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3562           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3563           if(!weird) newVariant = VariantNormal;
3564           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3565           /* Get a move list just to see the header, which
3566              will tell us whether this is really bug or zh */
3567           if (ics_getting_history == H_FALSE) {
3568             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3569             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3570             SendToICS(str);
3571           }
3572     }
3573     
3574     /* Take action if this is the first board of a new game, or of a
3575        different game than is currently being displayed.  */
3576     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3577         relation == RELATION_ISOLATED_BOARD) {
3578         
3579         /* Forget the old game and get the history (if any) of the new one */
3580         if (gameMode != BeginningOfGame) {
3581           Reset(TRUE, TRUE);
3582         }
3583         newGame = TRUE;
3584         if (appData.autoRaiseBoard) BoardToTop();
3585         prevMove = -3;
3586         if (gamenum == -1) {
3587             newGameMode = IcsIdle;
3588         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3589                    appData.getMoveList && !reqFlag) {
3590             /* Need to get game history */
3591             ics_getting_history = H_REQUESTED;
3592             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3593             SendToICS(str);
3594         }
3595         
3596         /* Initially flip the board to have black on the bottom if playing
3597            black or if the ICS flip flag is set, but let the user change
3598            it with the Flip View button. */
3599         flipView = appData.autoFlipView ? 
3600           (newGameMode == IcsPlayingBlack) || ics_flip :
3601           appData.flipView;
3602         
3603         /* Done with values from previous mode; copy in new ones */
3604         gameMode = newGameMode;
3605         ModeHighlight();
3606         ics_gamenum = gamenum;
3607         if (gamenum == gs_gamenum) {
3608             int klen = strlen(gs_kind);
3609             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3610             sprintf(str, "ICS %s", gs_kind);
3611             gameInfo.event = StrSave(str);
3612         } else {
3613             gameInfo.event = StrSave("ICS game");
3614         }
3615         gameInfo.site = StrSave(appData.icsHost);
3616         gameInfo.date = PGNDate();
3617         gameInfo.round = StrSave("-");
3618         gameInfo.white = StrSave(white);
3619         gameInfo.black = StrSave(black);
3620         timeControl = basetime * 60 * 1000;
3621         timeControl_2 = 0;
3622         timeIncrement = increment * 1000;
3623         movesPerSession = 0;
3624         gameInfo.timeControl = TimeControlTagValue();
3625         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3626   if (appData.debugMode) {
3627     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3628     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3629     setbuf(debugFP, NULL);
3630   }
3631
3632         gameInfo.outOfBook = NULL;
3633         
3634         /* Do we have the ratings? */
3635         if (strcmp(player1Name, white) == 0 &&
3636             strcmp(player2Name, black) == 0) {
3637             if (appData.debugMode)
3638               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3639                       player1Rating, player2Rating);
3640             gameInfo.whiteRating = player1Rating;
3641             gameInfo.blackRating = player2Rating;
3642         } else if (strcmp(player2Name, white) == 0 &&
3643                    strcmp(player1Name, black) == 0) {
3644             if (appData.debugMode)
3645               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3646                       player2Rating, player1Rating);
3647             gameInfo.whiteRating = player2Rating;
3648             gameInfo.blackRating = player1Rating;
3649         }
3650         player1Name[0] = player2Name[0] = NULLCHAR;
3651
3652         /* Silence shouts if requested */
3653         if (appData.quietPlay &&
3654             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3655             SendToICS(ics_prefix);
3656             SendToICS("set shout 0\n");
3657         }
3658     }
3659     
3660     /* Deal with midgame name changes */
3661     if (!newGame) {
3662         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3663             if (gameInfo.white) free(gameInfo.white);
3664             gameInfo.white = StrSave(white);
3665         }
3666         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3667             if (gameInfo.black) free(gameInfo.black);
3668             gameInfo.black = StrSave(black);
3669         }
3670     }
3671     
3672     /* Throw away game result if anything actually changes in examine mode */
3673     if (gameMode == IcsExamining && !newGame) {
3674         gameInfo.result = GameUnfinished;
3675         if (gameInfo.resultDetails != NULL) {
3676             free(gameInfo.resultDetails);
3677             gameInfo.resultDetails = NULL;
3678         }
3679     }
3680     
3681     /* In pausing && IcsExamining mode, we ignore boards coming
3682        in if they are in a different variation than we are. */
3683     if (pauseExamInvalid) return;
3684     if (pausing && gameMode == IcsExamining) {
3685         if (moveNum <= pauseExamForwardMostMove) {
3686             pauseExamInvalid = TRUE;
3687             forwardMostMove = pauseExamForwardMostMove;
3688             return;
3689         }
3690     }
3691     
3692   if (appData.debugMode) {
3693     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694   }
3695     /* Parse the board */
3696     for (k = 0; k < ranks; k++) {
3697       for (j = 0; j < files; j++)
3698         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3699       if(gameInfo.holdingsWidth > 1) {
3700            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3701            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3702       }
3703     }
3704     CopyBoard(boards[moveNum], board);
3705     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706     if (moveNum == 0) {
3707         startedFromSetupPosition =
3708           !CompareBoards(board, initialPosition);
3709         if(startedFromSetupPosition)
3710             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3711     }
3712
3713     /* [HGM] Set castling rights. Take the outermost Rooks,
3714        to make it also work for FRC opening positions. Note that board12
3715        is really defective for later FRC positions, as it has no way to
3716        indicate which Rook can castle if they are on the same side of King.
3717        For the initial position we grant rights to the outermost Rooks,
3718        and remember thos rights, and we then copy them on positions
3719        later in an FRC game. This means WB might not recognize castlings with
3720        Rooks that have moved back to their original position as illegal,
3721        but in ICS mode that is not its job anyway.
3722     */
3723     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3724     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725
3726         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3727             if(board[0][i] == WhiteRook) j = i;
3728         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3730             if(board[0][i] == WhiteRook) j = i;
3731         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3732         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3733             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3734         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3735         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3736             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3737         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738
3739         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3740         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3741             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[BOARD_HEIGHT-1][k] == bKing)
3744                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3745         if(gameInfo.variant == VariantTwoKings) {
3746             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3747             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3748             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3749         }
3750     } else { int r;
3751         r = boards[moveNum][CASTLING][0] = initialRights[0];
3752         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3753         r = boards[moveNum][CASTLING][1] = initialRights[1];
3754         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3755         r = boards[moveNum][CASTLING][3] = initialRights[3];
3756         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3757         r = boards[moveNum][CASTLING][4] = initialRights[4];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3759         /* wildcastle kludge: always assume King has rights */
3760         r = boards[moveNum][CASTLING][2] = initialRights[2];
3761         r = boards[moveNum][CASTLING][5] = initialRights[5];
3762     }
3763     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3764     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3765
3766     
3767     if (ics_getting_history == H_GOT_REQ_HEADER ||
3768         ics_getting_history == H_GOT_UNREQ_HEADER) {
3769         /* This was an initial position from a move list, not
3770            the current position */
3771         return;
3772     }
3773     
3774     /* Update currentMove and known move number limits */
3775     newMove = newGame || moveNum > forwardMostMove;
3776
3777     if (newGame) {
3778         forwardMostMove = backwardMostMove = currentMove = moveNum;
3779         if (gameMode == IcsExamining && moveNum == 0) {
3780           /* Workaround for ICS limitation: we are not told the wild
3781              type when starting to examine a game.  But if we ask for
3782              the move list, the move list header will tell us */
3783             ics_getting_history = H_REQUESTED;
3784             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3785             SendToICS(str);
3786         }
3787     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3788                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 #if ZIPPY
3790         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3791         /* [HGM] applied this also to an engine that is silently watching        */
3792         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3793             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3794             gameInfo.variant == currentlyInitializedVariant) {
3795           takeback = forwardMostMove - moveNum;
3796           for (i = 0; i < takeback; i++) {
3797             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3798             SendToProgram("undo\n", &first);
3799           }
3800         }
3801 #endif
3802
3803         forwardMostMove = moveNum;
3804         if (!pausing || currentMove > forwardMostMove)
3805           currentMove = forwardMostMove;
3806     } else {
3807         /* New part of history that is not contiguous with old part */ 
3808         if (pausing && gameMode == IcsExamining) {
3809             pauseExamInvalid = TRUE;
3810             forwardMostMove = pauseExamForwardMostMove;
3811             return;
3812         }
3813         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 #if ZIPPY
3815             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3816                 // [HGM] when we will receive the move list we now request, it will be
3817                 // fed to the engine from the first move on. So if the engine is not
3818                 // in the initial position now, bring it there.
3819                 InitChessProgram(&first, 0);
3820             }
3821 #endif
3822             ics_getting_history = H_REQUESTED;
3823             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3824             SendToICS(str);
3825         }
3826         forwardMostMove = backwardMostMove = currentMove = moveNum;
3827     }
3828     
3829     /* Update the clocks */
3830     if (strchr(elapsed_time, '.')) {
3831       /* Time is in ms */
3832       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3833       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834     } else {
3835       /* Time is in seconds */
3836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3838     }
3839       
3840
3841 #if ZIPPY
3842     if (appData.zippyPlay && newGame &&
3843         gameMode != IcsObserving && gameMode != IcsIdle &&
3844         gameMode != IcsExamining)
3845       ZippyFirstBoard(moveNum, basetime, increment);
3846 #endif
3847     
3848     /* Put the move on the move list, first converting
3849        to canonical algebraic form. */
3850     if (moveNum > 0) {
3851   if (appData.debugMode) {
3852     if (appData.debugMode) { int f = forwardMostMove;
3853         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856     }
3857     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858     fprintf(debugFP, "moveNum = %d\n", moveNum);
3859     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860     setbuf(debugFP, NULL);
3861   }
3862         if (moveNum <= backwardMostMove) {
3863             /* We don't know what the board looked like before
3864                this move.  Punt. */
3865             strcpy(parseList[moveNum - 1], move_str);
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             moveList[moveNum - 1][0] = NULLCHAR;
3869         } else if (strcmp(move_str, "none") == 0) {
3870             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871             /* Again, we don't know what the board looked like;
3872                this is really the start of the game. */
3873             parseList[moveNum - 1][0] = NULLCHAR;
3874             moveList[moveNum - 1][0] = NULLCHAR;
3875             backwardMostMove = moveNum;
3876             startedFromSetupPosition = TRUE;
3877             fromX = fromY = toX = toY = -1;
3878         } else {
3879           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3880           //                 So we parse the long-algebraic move string in stead of the SAN move
3881           int valid; char buf[MSG_SIZ], *prom;
3882
3883           // str looks something like "Q/a1-a2"; kill the slash
3884           if(str[1] == '/') 
3885                 sprintf(buf, "%c%s", str[0], str+2);
3886           else  strcpy(buf, str); // might be castling
3887           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3888                 strcat(buf, prom); // long move lacks promo specification!
3889           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890                 if(appData.debugMode) 
3891                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892                 strcpy(move_str, buf);
3893           }
3894           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895                                 &fromX, &fromY, &toX, &toY, &promoChar)
3896                || ParseOneMove(buf, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar);
3898           // end of long SAN patch
3899           if (valid) {
3900             (void) CoordsToAlgebraic(boards[moveNum - 1],
3901                                      PosFlags(moveNum - 1),
3902                                      fromY, fromX, toY, toX, promoChar,
3903                                      parseList[moveNum-1]);
3904             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3905               case MT_NONE:
3906               case MT_STALEMATE:
3907               default:
3908                 break;
3909               case MT_CHECK:
3910                 if(gameInfo.variant != VariantShogi)
3911                     strcat(parseList[moveNum - 1], "+");
3912                 break;
3913               case MT_CHECKMATE:
3914               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3915                 strcat(parseList[moveNum - 1], "#");
3916                 break;
3917             }
3918             strcat(parseList[moveNum - 1], " ");
3919             strcat(parseList[moveNum - 1], elapsed_time);
3920             /* currentMoveString is set as a side-effect of ParseOneMove */
3921             strcpy(moveList[moveNum - 1], currentMoveString);
3922             strcat(moveList[moveNum - 1], "\n");
3923           } else {
3924             /* Move from ICS was illegal!?  Punt. */
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928   }
3929             strcpy(parseList[moveNum - 1], move_str);
3930             strcat(parseList[moveNum - 1], " ");
3931             strcat(parseList[moveNum - 1], elapsed_time);
3932             moveList[moveNum - 1][0] = NULLCHAR;
3933             fromX = fromY = toX = toY = -1;
3934           }
3935         }
3936   if (appData.debugMode) {
3937     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3938     setbuf(debugFP, NULL);
3939   }
3940
3941 #if ZIPPY
3942         /* Send move to chess program (BEFORE animating it). */
3943         if (appData.zippyPlay && !newGame && newMove && 
3944            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945
3946             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3947                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3948                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3949                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950                             move_str);
3951                     DisplayError(str, 0);
3952                 } else {
3953                     if (first.sendTime) {
3954                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955                     }
3956                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3957                     if (firstMove && !bookHit) {
3958                         firstMove = FALSE;
3959                         if (first.useColors) {
3960                           SendToProgram(gameMode == IcsPlayingWhite ?
3961                                         "white\ngo\n" :
3962                                         "black\ngo\n", &first);
3963                         } else {
3964                           SendToProgram("go\n", &first);
3965                         }
3966                         first.maybeThinking = TRUE;
3967                     }
3968                 }
3969             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3970               if (moveList[moveNum - 1][0] == NULLCHAR) {
3971                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3972                 DisplayError(str, 0);
3973               } else {
3974                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3975                 SendMoveToProgram(moveNum - 1, &first);
3976               }
3977             }
3978         }
3979 #endif
3980     }
3981
3982     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3983         /* If move comes from a remote source, animate it.  If it
3984            isn't remote, it will have already been animated. */
3985         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3986             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987         }
3988         if (!pausing && appData.highlightLastMove) {
3989             SetHighlights(fromX, fromY, toX, toY);
3990         }
3991     }
3992     
3993     /* Start the clocks */
3994     whiteFlag = blackFlag = FALSE;
3995     appData.clockMode = !(basetime == 0 && increment == 0);
3996     if (ticking == 0) {
3997       ics_clock_paused = TRUE;
3998       StopClocks();
3999     } else if (ticking == 1) {
4000       ics_clock_paused = FALSE;
4001     }
4002     if (gameMode == IcsIdle ||
4003         relation == RELATION_OBSERVING_STATIC ||
4004         relation == RELATION_EXAMINING ||
4005         ics_clock_paused)
4006       DisplayBothClocks();
4007     else
4008       StartClocks();
4009     
4010     /* Display opponents and material strengths */
4011     if (gameInfo.variant != VariantBughouse &&
4012         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4013         if (tinyLayout || smallLayout) {
4014             if(gameInfo.variant == VariantNormal)
4015                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4016                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4017                     basetime, increment);
4018             else
4019                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment, (int) gameInfo.variant);
4022         } else {
4023             if(gameInfo.variant == VariantNormal)
4024                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4025                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4026                     basetime, increment);
4027             else
4028                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4029                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4030                     basetime, increment, VariantName(gameInfo.variant));
4031         }
4032         DisplayTitle(str);
4033   if (appData.debugMode) {
4034     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4035   }
4036     }
4037
4038    
4039     /* Display the board */
4040     if (!pausing && !appData.noGUI) {
4041       
4042       if (appData.premove)
4043           if (!gotPremove || 
4044              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4045              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4046               ClearPremoveHighlights();
4047
4048       DrawPosition(FALSE, boards[currentMove]);
4049       DisplayMove(moveNum - 1);
4050       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4051             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4052               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4053         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4054       }
4055     }
4056
4057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 #if ZIPPY
4059     if(bookHit) { // [HGM] book: simulate book reply
4060         static char bookMove[MSG_SIZ]; // a bit generous?
4061
4062         programStats.nodes = programStats.depth = programStats.time = 
4063         programStats.score = programStats.got_only_move = 0;
4064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065
4066         strcpy(bookMove, "move ");
4067         strcat(bookMove, bookHit);
4068         HandleMachineMove(bookMove, &first);
4069     }
4070 #endif
4071 }
4072
4073 void
4074 GetMoveListEvent()
4075 {
4076     char buf[MSG_SIZ];
4077     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4078         ics_getting_history = H_REQUESTED;
4079         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4080         SendToICS(buf);
4081     }
4082 }
4083
4084 void
4085 AnalysisPeriodicEvent(force)
4086      int force;
4087 {
4088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4089          && !force) || !appData.periodicUpdates)
4090       return;
4091
4092     /* Send . command to Crafty to collect stats */
4093     SendToProgram(".\n", &first);
4094
4095     /* Don't send another until we get a response (this makes
4096        us stop sending to old Crafty's which don't understand
4097        the "." command (sending illegal cmds resets node count & time,
4098        which looks bad)) */
4099     programStats.ok_to_send = 0;
4100 }
4101
4102 void ics_update_width(new_width)
4103         int new_width;
4104 {
4105         ics_printf("set width %d\n", new_width);
4106 }
4107
4108 void
4109 SendMoveToProgram(moveNum, cps)
4110      int moveNum;
4111      ChessProgramState *cps;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     if (cps->useUsermove) {
4116       SendToProgram("usermove ", cps);
4117     }
4118     if (cps->useSAN) {
4119       char *space;
4120       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4121         int len = space - parseList[moveNum];
4122         memcpy(buf, parseList[moveNum], len);
4123         buf[len++] = '\n';
4124         buf[len] = NULLCHAR;
4125       } else {
4126         sprintf(buf, "%s\n", parseList[moveNum]);
4127       }
4128       SendToProgram(buf, cps);
4129     } else {
4130       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4131         AlphaRank(moveList[moveNum], 4);
4132         SendToProgram(moveList[moveNum], cps);
4133         AlphaRank(moveList[moveNum], 4); // and back
4134       } else
4135       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4136        * the engine. It would be nice to have a better way to identify castle 
4137        * moves here. */
4138       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4139                                                                          && cps->useOOCastle) {
4140         int fromX = moveList[moveNum][0] - AAA; 
4141         int fromY = moveList[moveNum][1] - ONE;
4142         int toX = moveList[moveNum][2] - AAA; 
4143         int toY = moveList[moveNum][3] - ONE;
4144         if((boards[moveNum][fromY][fromX] == WhiteKing 
4145             && boards[moveNum][toY][toX] == WhiteRook)
4146            || (boards[moveNum][fromY][fromX] == BlackKing 
4147                && boards[moveNum][toY][toX] == BlackRook)) {
4148           if(toX > fromX) SendToProgram("O-O\n", cps);
4149           else SendToProgram("O-O-O\n", cps);
4150         }
4151         else SendToProgram(moveList[moveNum], cps);
4152       }
4153       else SendToProgram(moveList[moveNum], cps);
4154       /* End of additions by Tord */
4155     }
4156
4157     /* [HGM] setting up the opening has brought engine in force mode! */
4158     /*       Send 'go' if we are in a mode where machine should play. */
4159     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4160         (gameMode == TwoMachinesPlay   ||
4161 #ifdef ZIPPY
4162          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4163 #endif
4164          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4165         SendToProgram("go\n", cps);
4166   if (appData.debugMode) {
4167     fprintf(debugFP, "(extra)\n");
4168   }
4169     }
4170     setboardSpoiledMachineBlack = 0;
4171 }
4172
4173 void
4174 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175      ChessMove moveType;
4176      int fromX, fromY, toX, toY;
4177 {
4178     char user_move[MSG_SIZ];
4179
4180     switch (moveType) {
4181       default:
4182         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4183                 (int)moveType, fromX, fromY, toX, toY);
4184         DisplayError(user_move + strlen("say "), 0);
4185         break;
4186       case WhiteKingSideCastle:
4187       case BlackKingSideCastle:
4188       case WhiteQueenSideCastleWild:
4189       case BlackQueenSideCastleWild:
4190       /* PUSH Fabien */
4191       case WhiteHSideCastleFR:
4192       case BlackHSideCastleFR:
4193       /* POP Fabien */
4194         sprintf(user_move, "o-o\n");
4195         break;
4196       case WhiteQueenSideCastle:
4197       case BlackQueenSideCastle:
4198       case WhiteKingSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       /* PUSH Fabien */
4201       case WhiteASideCastleFR:
4202       case BlackASideCastleFR:
4203       /* POP Fabien */
4204         sprintf(user_move, "o-o-o\n");
4205         break;
4206       case WhitePromotionQueen:
4207       case BlackPromotionQueen:
4208       case WhitePromotionRook:
4209       case BlackPromotionRook:
4210       case WhitePromotionBishop:
4211       case BlackPromotionBishop:
4212       case WhitePromotionKnight:
4213       case BlackPromotionKnight:
4214       case WhitePromotionKing:
4215       case BlackPromotionKing:
4216       case WhitePromotionChancellor:
4217       case BlackPromotionChancellor:
4218       case WhitePromotionArchbishop:
4219       case BlackPromotionArchbishop:
4220         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4221             sprintf(user_move, "%c%c%c%c=%c\n",
4222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4223                 PieceToChar(WhiteFerz));
4224         else if(gameInfo.variant == VariantGreat)
4225             sprintf(user_move, "%c%c%c%c=%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227                 PieceToChar(WhiteMan));
4228         else
4229             sprintf(user_move, "%c%c%c%c=%c\n",
4230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231                 PieceToChar(PromoPiece(moveType)));
4232         break;
4233       case WhiteDrop:
4234       case BlackDrop:
4235         sprintf(user_move, "%c@%c%c\n",
4236                 ToUpper(PieceToChar((ChessSquare) fromX)),
4237                 AAA + toX, ONE + toY);
4238         break;
4239       case NormalMove:
4240       case WhiteCapturesEnPassant:
4241       case BlackCapturesEnPassant:
4242       case IllegalMove:  /* could be a variant we don't quite understand */
4243         sprintf(user_move, "%c%c%c%c\n",
4244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4245         break;
4246     }
4247     SendToICS(user_move);
4248     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4249         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4250 }
4251
4252 void
4253 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4254      int rf, ff, rt, ft;
4255      char promoChar;
4256      char move[7];
4257 {
4258     if (rf == DROP_RANK) {
4259         sprintf(move, "%c@%c%c\n",
4260                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261     } else {
4262         if (promoChar == 'x' || promoChar == NULLCHAR) {
4263             sprintf(move, "%c%c%c%c\n",
4264                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265         } else {
4266             sprintf(move, "%c%c%c%c%c\n",
4267                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4268         }
4269     }
4270 }
4271
4272 void
4273 ProcessICSInitScript(f)
4274      FILE *f;
4275 {
4276     char buf[MSG_SIZ];
4277
4278     while (fgets(buf, MSG_SIZ, f)) {
4279         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4280     }
4281
4282     fclose(f);
4283 }
4284
4285
4286 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 void
4288 AlphaRank(char *move, int n)
4289 {
4290 //    char *p = move, c; int x, y;
4291
4292     if (appData.debugMode) {
4293         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4294     }
4295
4296     if(move[1]=='*' && 
4297        move[2]>='0' && move[2]<='9' &&
4298        move[3]>='a' && move[3]<='x'    ) {
4299         move[1] = '@';
4300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302     } else
4303     if(move[0]>='0' && move[0]<='9' &&
4304        move[1]>='a' && move[1]<='x' &&
4305        move[2]>='0' && move[2]<='9' &&
4306        move[3]>='a' && move[3]<='x'    ) {
4307         /* input move, Shogi -> normal */
4308         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4309         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4310         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4311         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4312     } else
4313     if(move[1]=='@' &&
4314        move[3]>='0' && move[3]<='9' &&
4315        move[2]>='a' && move[2]<='x'    ) {
4316         move[1] = '*';
4317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319     } else
4320     if(
4321        move[0]>='a' && move[0]<='x' &&
4322        move[3]>='0' && move[3]<='9' &&
4323        move[2]>='a' && move[2]<='x'    ) {
4324          /* output move, normal -> Shogi */
4325         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4326         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4327         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4328         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4329         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330     }
4331     if (appData.debugMode) {
4332         fprintf(debugFP, "   out = '%s'\n", move);
4333     }
4334 }
4335
4336 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 Boolean
4338 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4339      char *move;
4340      int moveNum;
4341      ChessMove *moveType;
4342      int *fromX, *fromY, *toX, *toY;
4343      char *promoChar;
4344 {       
4345     if (appData.debugMode) {
4346         fprintf(debugFP, "move to parse: %s\n", move);
4347     }
4348     *moveType = yylexstr(moveNum, move);
4349
4350     switch (*moveType) {
4351       case WhitePromotionChancellor:
4352       case BlackPromotionChancellor:
4353       case WhitePromotionArchbishop:
4354       case BlackPromotionArchbishop:
4355       case WhitePromotionQueen:
4356       case BlackPromotionQueen:
4357       case WhitePromotionRook:
4358       case BlackPromotionRook:
4359       case WhitePromotionBishop:
4360       case BlackPromotionBishop:
4361       case WhitePromotionKnight:
4362       case BlackPromotionKnight:
4363       case WhitePromotionKing:
4364       case BlackPromotionKing:
4365       case NormalMove:
4366       case WhiteCapturesEnPassant:
4367       case BlackCapturesEnPassant:
4368       case WhiteKingSideCastle:
4369       case WhiteQueenSideCastle:
4370       case BlackKingSideCastle:
4371       case BlackQueenSideCastle:
4372       case WhiteKingSideCastleWild:
4373       case WhiteQueenSideCastleWild:
4374       case BlackKingSideCastleWild:
4375       case BlackQueenSideCastleWild:
4376       /* Code added by Tord: */
4377       case WhiteHSideCastleFR:
4378       case WhiteASideCastleFR:
4379       case BlackHSideCastleFR:
4380       case BlackASideCastleFR:
4381       /* End of code added by Tord */
4382       case IllegalMove:         /* bug or odd chess variant */
4383         *fromX = currentMoveString[0] - AAA;
4384         *fromY = currentMoveString[1] - ONE;
4385         *toX = currentMoveString[2] - AAA;
4386         *toY = currentMoveString[3] - ONE;
4387         *promoChar = currentMoveString[4];
4388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4390     if (appData.debugMode) {
4391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392     }
4393             *fromX = *fromY = *toX = *toY = 0;
4394             return FALSE;
4395         }
4396         if (appData.testLegality) {
4397           return (*moveType != IllegalMove);
4398         } else {
4399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4401         }
4402
4403       case WhiteDrop:
4404       case BlackDrop:
4405         *fromX = *moveType == WhiteDrop ?
4406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407           (int) CharToPiece(ToLower(currentMoveString[0]));
4408         *fromY = DROP_RANK;
4409         *toX = currentMoveString[2] - AAA;
4410         *toY = currentMoveString[3] - ONE;
4411         *promoChar = NULLCHAR;
4412         return TRUE;
4413
4414       case AmbiguousMove:
4415       case ImpossibleMove:
4416       case (ChessMove) 0:       /* end of file */
4417       case ElapsedTime:
4418       case Comment:
4419       case PGNTag:
4420       case NAG:
4421       case WhiteWins:
4422       case BlackWins:
4423       case GameIsDrawn:
4424       default:
4425     if (appData.debugMode) {
4426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4427     }
4428         /* bug? */
4429         *fromX = *fromY = *toX = *toY = 0;
4430         *promoChar = NULLCHAR;
4431         return FALSE;
4432     }
4433 }
4434
4435
4436 void
4437 ParsePV(char *pv)
4438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4439   int fromX, fromY, toX, toY; char promoChar;
4440   ChessMove moveType;
4441   Boolean valid;
4442   int nr = 0;
4443
4444   endPV = forwardMostMove;
4445   do {
4446     while(*pv == ' ') pv++;
4447     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4448     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4449 if(appData.debugMode){
4450 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 }
4452     if(!valid && nr == 0 &&
4453        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4454         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455     }
4456     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4457     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458     nr++;
4459     if(endPV+1 > framePtr) break; // no space, truncate
4460     if(!valid) break;
4461     endPV++;
4462     CopyBoard(boards[endPV], boards[endPV-1]);
4463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4464     moveList[endPV-1][0] = fromX + AAA;
4465     moveList[endPV-1][1] = fromY + ONE;
4466     moveList[endPV-1][2] = toX + AAA;
4467     moveList[endPV-1][3] = toY + ONE;
4468     parseList[endPV-1][0] = NULLCHAR;
4469   } while(valid);
4470   currentMove = endPV;
4471   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4472   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4473                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4474   DrawPosition(TRUE, boards[currentMove]);
4475 }
4476
4477 static int lastX, lastY;
4478
4479 Boolean
4480 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4481 {
4482         int startPV;
4483
4484         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4485         lastX = x; lastY = y;
4486         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487         startPV = index;
4488       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489       index = startPV;
4490         while(buf[index] && buf[index] != '\n') index++;
4491         buf[index] = 0;
4492         ParsePV(buf+startPV);
4493         *start = startPV; *end = index-1;
4494         return TRUE;
4495 }
4496
4497 Boolean
4498 LoadPV(int x, int y)
4499 { // called on right mouse click to load PV
4500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4501   lastX = x; lastY = y;
4502   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4503   return TRUE;
4504 }
4505
4506 void
4507 UnLoadPV()
4508 {
4509   if(endPV < 0) return;
4510   endPV = -1;
4511   currentMove = forwardMostMove;
4512   ClearPremoveHighlights();
4513   DrawPosition(TRUE, boards[currentMove]);
4514 }
4515
4516 void
4517 MovePV(int x, int y, int h)
4518 { // step through PV based on mouse coordinates (called on mouse move)
4519   int margin = h>>3, step = 0;
4520
4521   if(endPV < 0) return;
4522   // we must somehow check if right button is still down (might be released off board!)
4523   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4524   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4525   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526   if(!step) return;
4527   lastX = x; lastY = y;
4528   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4529   currentMove += step;
4530   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4531   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4532                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4533   DrawPosition(FALSE, boards[currentMove]);
4534 }
4535
4536
4537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4538 // All positions will have equal probability, but the current method will not provide a unique
4539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4540 #define DARK 1
4541 #define LITE 2
4542 #define ANY 3
4543
4544 int squaresLeft[4];
4545 int piecesLeft[(int)BlackPawn];
4546 int seed, nrOfShuffles;
4547
4548 void GetPositionNumber()
4549 {       // sets global variable seed
4550         int i;
4551
4552         seed = appData.defaultFrcPosition;
4553         if(seed < 0) { // randomize based on time for negative FRC position numbers
4554                 for(i=0; i<50; i++) seed += random();
4555                 seed = random() ^ random() >> 8 ^ random() << 8;
4556                 if(seed<0) seed = -seed;
4557         }
4558 }
4559
4560 int put(Board board, int pieceType, int rank, int n, int shade)
4561 // put the piece on the (n-1)-th empty squares of the given shade
4562 {
4563         int i;
4564
4565         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4566                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4567                         board[rank][i] = (ChessSquare) pieceType;
4568                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569                         squaresLeft[ANY]--;
4570                         piecesLeft[pieceType]--; 
4571                         return i;
4572                 }
4573         }
4574         return -1;
4575 }
4576
4577
4578 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4579 // calculate where the next piece goes, (any empty square), and put it there
4580 {
4581         int i;
4582
4583         i = seed % squaresLeft[shade];
4584         nrOfShuffles *= squaresLeft[shade];
4585         seed /= squaresLeft[shade];
4586         put(board, pieceType, rank, i, shade);
4587 }
4588
4589 void AddTwoPieces(Board board, int pieceType, int rank)
4590 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 {
4592         int i, n=squaresLeft[ANY], j=n-1, k;
4593
4594         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4595         i = seed % k;  // pick one
4596         nrOfShuffles *= k;
4597         seed /= k;
4598         while(i >= j) i -= j--;
4599         j = n - 1 - j; i += j;
4600         put(board, pieceType, rank, j, ANY);
4601         put(board, pieceType, rank, i, ANY);
4602 }
4603
4604 void SetUpShuffle(Board board, int number)
4605 {
4606         int i, p, first=1;
4607
4608         GetPositionNumber(); nrOfShuffles = 1;
4609
4610         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4611         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4612         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613
4614         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615
4616         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4617             p = (int) board[0][i];
4618             if(p < (int) BlackPawn) piecesLeft[p] ++;
4619             board[0][i] = EmptySquare;
4620         }
4621
4622         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4623             // shuffles restricted to allow normal castling put KRR first
4624             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4625                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4626             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4627                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4628             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4629                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4630             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4631                 put(board, WhiteRook, 0, 0, ANY);
4632             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4633         }
4634
4635         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4636             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4637             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4638                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4639                 while(piecesLeft[p] >= 2) {
4640                     AddOnePiece(board, p, 0, LITE);
4641                     AddOnePiece(board, p, 0, DARK);
4642                 }
4643                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4644             }
4645
4646         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4647             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4648             // but we leave King and Rooks for last, to possibly obey FRC restriction
4649             if(p == (int)WhiteRook) continue;
4650             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4651             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4652         }
4653
4654         // now everything is placed, except perhaps King (Unicorn) and Rooks
4655
4656         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4657             // Last King gets castling rights
4658             while(piecesLeft[(int)WhiteUnicorn]) {
4659                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4660                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4661             }
4662
4663             while(piecesLeft[(int)WhiteKing]) {
4664                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4665                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4666             }
4667
4668
4669         } else {
4670             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4671             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4672         }
4673
4674         // Only Rooks can be left; simply place them all
4675         while(piecesLeft[(int)WhiteRook]) {
4676                 i = put(board, WhiteRook, 0, 0, ANY);
4677                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4678                         if(first) {
4679                                 first=0;
4680                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4681                         }
4682                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4683                 }
4684         }
4685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4686             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4687         }
4688
4689         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4690 }
4691
4692 int SetCharTable( char *table, const char * map )
4693 /* [HGM] moved here from winboard.c because of its general usefulness */
4694 /*       Basically a safe strcpy that uses the last character as King */
4695 {
4696     int result = FALSE; int NrPieces;
4697
4698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4699                     && NrPieces >= 12 && !(NrPieces&1)) {
4700         int i; /* [HGM] Accept even length from 12 to 34 */
4701
4702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4703         for( i=0; i<NrPieces/2-1; i++ ) {
4704             table[i] = map[i];
4705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706         }
4707         table[(int) WhiteKing]  = map[NrPieces/2-1];
4708         table[(int) BlackKing]  = map[NrPieces-1];
4709
4710         result = TRUE;
4711     }
4712
4713     return result;
4714 }
4715
4716 void Prelude(Board board)
4717 {       // [HGM] superchess: random selection of exo-pieces
4718         int i, j, k; ChessSquare p; 
4719         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720
4721         GetPositionNumber(); // use FRC position number
4722
4723         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4724             SetCharTable(pieceToChar, appData.pieceToCharTable);
4725             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4726                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4727         }
4728
4729         j = seed%4;                 seed /= 4; 
4730         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4731         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4732         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4733         j = seed%3 + (seed%3 >= j); seed /= 3; 
4734         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4736         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4737         j = seed%3;                 seed /= 3; 
4738         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4739         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4740         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4741         j = seed%2 + (seed%2 >= j); seed /= 2; 
4742         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4744         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4745         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4746         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4747         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4748         put(board, exoPieces[0],    0, 0, ANY);
4749         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4750 }
4751
4752 void
4753 InitPosition(redraw)
4754      int redraw;
4755 {
4756     ChessSquare (* pieces)[BOARD_FILES];
4757     int i, j, pawnRow, overrule,
4758     oldx = gameInfo.boardWidth,
4759     oldy = gameInfo.boardHeight,
4760     oldh = gameInfo.holdingsWidth,
4761     oldv = gameInfo.variant;
4762
4763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764
4765     /* [AS] Initialize pv info list [HGM] and game status */
4766     {
4767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4768             pvInfoList[i].depth = 0;
4769             boards[i][EP_STATUS] = EP_NONE;
4770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4771         }
4772
4773         initialRulePlies = 0; /* 50-move counter start */
4774
4775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4777     }
4778
4779     
4780     /* [HGM] logic here is completely changed. In stead of full positions */
4781     /* the initialized data only consist of the two backranks. The switch */
4782     /* selects which one we will use, which is than copied to the Board   */
4783     /* initialPosition, which for the rest is initialized by Pawns and    */
4784     /* empty squares. This initial position is then copied to boards[0],  */
4785     /* possibly after shuffling, so that it remains available.            */
4786
4787     gameInfo.holdingsWidth = 0; /* default board sizes */
4788     gameInfo.boardWidth    = 8;
4789     gameInfo.boardHeight   = 8;
4790     gameInfo.holdingsSize  = 0;
4791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4792     for(i=0; i<BOARD_FILES-2; i++)
4793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4794     initialPosition[EP_STATUS] = EP_NONE;
4795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4796
4797     switch (gameInfo.variant) {
4798     case VariantFischeRandom:
4799       shuffleOpenings = TRUE;
4800     default:
4801       pieces = FIDEArray;
4802       break;
4803     case VariantShatranj:
4804       pieces = ShatranjArray;
4805       nrCastlingRights = 0;
4806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4807       break;
4808     case VariantMakruk:
4809       pieces = makrukArray;
4810       nrCastlingRights = 0;
4811       startedFromSetupPosition = TRUE;
4812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4813       break;
4814     case VariantTwoKings:
4815       pieces = twoKingsArray;
4816       break;
4817     case VariantCapaRandom:
4818       shuffleOpenings = TRUE;
4819     case VariantCapablanca:
4820       pieces = CapablancaArray;
4821       gameInfo.boardWidth = 10;
4822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4823       break;
4824     case VariantGothic:
4825       pieces = GothicArray;
4826       gameInfo.boardWidth = 10;
4827       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4828       break;
4829     case VariantJanus:
4830       pieces = JanusArray;
4831       gameInfo.boardWidth = 10;
4832       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4833       nrCastlingRights = 6;
4834         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4835         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4836         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4837         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4838         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4839         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4840       break;
4841     case VariantFalcon:
4842       pieces = FalconArray;
4843       gameInfo.boardWidth = 10;
4844       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4845       break;
4846     case VariantXiangqi:
4847       pieces = XiangqiArray;
4848       gameInfo.boardWidth  = 9;
4849       gameInfo.boardHeight = 10;
4850       nrCastlingRights = 0;
4851       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4852       break;
4853     case VariantShogi:
4854       pieces = ShogiArray;
4855       gameInfo.boardWidth  = 9;
4856       gameInfo.boardHeight = 9;
4857       gameInfo.holdingsSize = 7;
4858       nrCastlingRights = 0;
4859       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4860       break;
4861     case VariantCourier:
4862       pieces = CourierArray;
4863       gameInfo.boardWidth  = 12;
4864       nrCastlingRights = 0;
4865       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4866       break;
4867     case VariantKnightmate:
4868       pieces = KnightmateArray;
4869       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4870       break;
4871     case VariantFairy:
4872       pieces = fairyArray;
4873       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4874       break;
4875     case VariantGreat:
4876       pieces = GreatArray;
4877       gameInfo.boardWidth = 10;
4878       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4879       gameInfo.holdingsSize = 8;
4880       break;
4881     case VariantSuper:
4882       pieces = FIDEArray;
4883       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4884       gameInfo.holdingsSize = 8;
4885       startedFromSetupPosition = TRUE;
4886       break;
4887     case VariantCrazyhouse:
4888     case VariantBughouse:
4889       pieces = FIDEArray;
4890       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4891       gameInfo.holdingsSize = 5;
4892       break;
4893     case VariantWildCastle:
4894       pieces = FIDEArray;
4895       /* !!?shuffle with kings guaranteed to be on d or e file */
4896       shuffleOpenings = 1;
4897       break;
4898     case VariantNoCastle:
4899       pieces = FIDEArray;
4900       nrCastlingRights = 0;
4901       /* !!?unconstrained back-rank shuffle */
4902       shuffleOpenings = 1;
4903       break;
4904     }
4905
4906     overrule = 0;
4907     if(appData.NrFiles >= 0) {
4908         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4909         gameInfo.boardWidth = appData.NrFiles;
4910     }
4911     if(appData.NrRanks >= 0) {
4912         gameInfo.boardHeight = appData.NrRanks;
4913     }
4914     if(appData.holdingsSize >= 0) {
4915         i = appData.holdingsSize;
4916         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4917         gameInfo.holdingsSize = i;
4918     }
4919     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4920     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4921         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922
4923     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4924     if(pawnRow < 1) pawnRow = 1;
4925     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926
4927     /* User pieceToChar list overrules defaults */
4928     if(appData.pieceToCharTable != NULL)
4929         SetCharTable(pieceToChar, appData.pieceToCharTable);
4930
4931     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932
4933         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4934             s = (ChessSquare) 0; /* account holding counts in guard band */
4935         for( i=0; i<BOARD_HEIGHT; i++ )
4936             initialPosition[i][j] = s;
4937
4938         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4939         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4940         initialPosition[pawnRow][j] = WhitePawn;
4941         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4942         if(gameInfo.variant == VariantXiangqi) {
4943             if(j&1) {
4944                 initialPosition[pawnRow][j] = 
4945                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4946                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4947                    initialPosition[2][j] = WhiteCannon;
4948                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4949                 }
4950             }
4951         }
4952         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4953     }
4954     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4955
4956             j=BOARD_LEFT+1;
4957             initialPosition[1][j] = WhiteBishop;
4958             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959             j=BOARD_RGHT-2;
4960             initialPosition[1][j] = WhiteRook;
4961             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4962     }
4963
4964     if( nrCastlingRights == -1) {
4965         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4966         /*       This sets default castling rights from none to normal corners   */
4967         /* Variants with other castling rights must set them themselves above    */
4968         nrCastlingRights = 6;
4969        
4970         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4971         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4972         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4973         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4974         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4975         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4976      }
4977
4978      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4979      if(gameInfo.variant == VariantGreat) { // promotion commoners
4980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4981         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4983         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984      }
4985   if (appData.debugMode) {
4986     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987   }
4988     if(shuffleOpenings) {
4989         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4990         startedFromSetupPosition = TRUE;
4991     }
4992     if(startedFromPositionFile) {
4993       /* [HGM] loadPos: use PositionFile for every new game */
4994       CopyBoard(initialPosition, filePosition);
4995       for(i=0; i<nrCastlingRights; i++)
4996           initialRights[i] = filePosition[CASTLING][i];
4997       startedFromSetupPosition = TRUE;
4998     }
4999
5000     CopyBoard(boards[0], initialPosition);
5001
5002     if(oldx != gameInfo.boardWidth ||
5003        oldy != gameInfo.boardHeight ||
5004        oldh != gameInfo.holdingsWidth
5005 #ifdef GOTHIC
5006        || oldv == VariantGothic ||        // For licensing popups
5007        gameInfo.variant == VariantGothic
5008 #endif
5009 #ifdef FALCON
5010        || oldv == VariantFalcon ||
5011        gameInfo.variant == VariantFalcon
5012 #endif
5013                                          )
5014             InitDrawingSizes(-2 ,0);
5015
5016     if (redraw)
5017       DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 void
5021 SendBoard(cps, moveNum)
5022      ChessProgramState *cps;
5023      int moveNum;
5024 {
5025     char message[MSG_SIZ];
5026     
5027     if (cps->useSetboard) {
5028       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5029       sprintf(message, "setboard %s\n", fen);
5030       SendToProgram(message, cps);
5031       free(fen);
5032
5033     } else {
5034       ChessSquare *bp;
5035       int i, j;
5036       /* Kludge to set black to move, avoiding the troublesome and now
5037        * deprecated "black" command.
5038        */
5039       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040
5041       SendToProgram("edit\n", cps);
5042       SendToProgram("#\n", cps);
5043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5044         bp = &boards[moveNum][i][BOARD_LEFT];
5045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5046           if ((int) *bp < (int) BlackPawn) {
5047             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5048                     AAA + j, ONE + i);
5049             if(message[0] == '+' || message[0] == '~') {
5050                 sprintf(message, "%c%c%c+\n",
5051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5052                         AAA + j, ONE + i);
5053             }
5054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5055                 message[1] = BOARD_RGHT   - 1 - j + '1';
5056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057             }
5058             SendToProgram(message, cps);
5059           }
5060         }
5061       }
5062     
5063       SendToProgram("c\n", cps);
5064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5065         bp = &boards[moveNum][i][BOARD_LEFT];
5066         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5067           if (((int) *bp != (int) EmptySquare)
5068               && ((int) *bp >= (int) BlackPawn)) {
5069             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070                     AAA + j, ONE + i);
5071             if(message[0] == '+' || message[0] == '~') {
5072                 sprintf(message, "%c%c%c+\n",
5073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5074                         AAA + j, ONE + i);
5075             }
5076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5077                 message[1] = BOARD_RGHT   - 1 - j + '1';
5078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079             }
5080             SendToProgram(message, cps);
5081           }
5082         }
5083       }
5084     
5085       SendToProgram(".\n", cps);
5086     }
5087     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5088 }
5089
5090 int
5091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 {
5093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5094     /* [HGM] add Shogi promotions */
5095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5096     ChessSquare piece;
5097     ChessMove moveType;
5098     Boolean premove;
5099
5100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5102
5103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5105         return FALSE;
5106
5107     piece = boards[currentMove][fromY][fromX];
5108     if(gameInfo.variant == VariantShogi) {
5109         promotionZoneSize = 3;
5110         highestPromotingPiece = (int)WhiteFerz;
5111     } else if(gameInfo.variant == VariantMakruk) {
5112         promotionZoneSize = 3;
5113     }
5114
5115     // next weed out all moves that do not touch the promotion zone at all
5116     if((int)piece >= BlackPawn) {
5117         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118              return FALSE;
5119         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120     } else {
5121         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5122            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5123     }
5124
5125     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126
5127     // weed out mandatory Shogi promotions
5128     if(gameInfo.variant == VariantShogi) {
5129         if(piece >= BlackPawn) {
5130             if(toY == 0 && piece == BlackPawn ||
5131                toY == 0 && piece == BlackQueen ||
5132                toY <= 1 && piece == BlackKnight) {
5133                 *promoChoice = '+';
5134                 return FALSE;
5135             }
5136         } else {
5137             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5138                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5139                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5140                 *promoChoice = '+';
5141                 return FALSE;
5142             }
5143         }
5144     }
5145
5146     // weed out obviously illegal Pawn moves
5147     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5148         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5149         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5150         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5151         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5152         // note we are not allowed to test for valid (non-)capture, due to premove
5153     }
5154
5155     // we either have a choice what to promote to, or (in Shogi) whether to promote
5156     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5157         *promoChoice = PieceToChar(BlackFerz);  // no choice
5158         return FALSE;
5159     }
5160     if(appData.alwaysPromoteToQueen) { // predetermined
5161         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5162              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5163         else *promoChoice = PieceToChar(BlackQueen);
5164         return FALSE;
5165     }
5166
5167     // suppress promotion popup on illegal moves that are not premoves
5168     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5169               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5170     if(appData.testLegality && !premove) {
5171         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5172                         fromY, fromX, toY, toX, NULLCHAR);
5173         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5174            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5175             return FALSE;
5176     }
5177
5178     return TRUE;
5179 }
5180
5181 int
5182 InPalace(row, column)
5183      int row, column;
5184 {   /* [HGM] for Xiangqi */
5185     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5186          column < (BOARD_WIDTH + 4)/2 &&
5187          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5188     return FALSE;
5189 }
5190
5191 int
5192 PieceForSquare (x, y)
5193      int x;
5194      int y;
5195 {
5196   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5197      return -1;
5198   else
5199      return boards[currentMove][y][x];
5200 }
5201
5202 int
5203 OKToStartUserMove(x, y)
5204      int x, y;
5205 {
5206     ChessSquare from_piece;
5207     int white_piece;
5208
5209     if (matchMode) return FALSE;
5210     if (gameMode == EditPosition) return TRUE;
5211
5212     if (x >= 0 && y >= 0)
5213       from_piece = boards[currentMove][y][x];
5214     else
5215       from_piece = EmptySquare;
5216
5217     if (from_piece == EmptySquare) return FALSE;
5218
5219     white_piece = (int)from_piece >= (int)WhitePawn &&
5220       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5221
5222     switch (gameMode) {
5223       case PlayFromGameFile:
5224       case AnalyzeFile:
5225       case TwoMachinesPlay:
5226       case EndOfGame:
5227         return FALSE;
5228
5229       case IcsObserving:
5230       case IcsIdle:
5231         return FALSE;
5232
5233       case MachinePlaysWhite:
5234       case IcsPlayingBlack:
5235         if (appData.zippyPlay) return FALSE;
5236         if (white_piece) {
5237             DisplayMoveError(_("You are playing Black"));
5238             return FALSE;
5239         }
5240         break;
5241
5242       case MachinePlaysBlack:
5243       case IcsPlayingWhite:
5244         if (appData.zippyPlay) return FALSE;
5245         if (!white_piece) {
5246             DisplayMoveError(_("You are playing White"));
5247             return FALSE;
5248         }
5249         break;
5250
5251       case EditGame:
5252         if (!white_piece && WhiteOnMove(currentMove)) {
5253             DisplayMoveError(_("It is White's turn"));
5254             return FALSE;
5255         }           
5256         if (white_piece && !WhiteOnMove(currentMove)) {
5257             DisplayMoveError(_("It is Black's turn"));
5258             return FALSE;
5259         }           
5260         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5261             /* Editing correspondence game history */
5262             /* Could disallow this or prompt for confirmation */
5263             cmailOldMove = -1;
5264         }
5265         break;
5266
5267       case BeginningOfGame:
5268         if (appData.icsActive) return FALSE;
5269         if (!appData.noChessProgram) {
5270             if (!white_piece) {
5271                 DisplayMoveError(_("You are playing White"));
5272                 return FALSE;
5273             }
5274         }
5275         break;
5276         
5277       case Training:
5278         if (!white_piece && WhiteOnMove(currentMove)) {
5279             DisplayMoveError(_("It is White's turn"));
5280             return FALSE;
5281         }           
5282         if (white_piece && !WhiteOnMove(currentMove)) {
5283             DisplayMoveError(_("It is Black's turn"));
5284             return FALSE;
5285         }           
5286         break;
5287
5288       default:
5289       case IcsExamining:
5290         break;
5291     }
5292     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5293         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5294         && gameMode != AnalyzeFile && gameMode != Training) {
5295         DisplayMoveError(_("Displayed position is not current"));
5296         return FALSE;
5297     }
5298     return TRUE;
5299 }
5300
5301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5303 int lastLoadGameUseList = FALSE;
5304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5305 ChessMove lastLoadGameStart = (ChessMove) 0;
5306
5307 ChessMove
5308 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5309      int fromX, fromY, toX, toY;
5310      int promoChar;
5311      Boolean captureOwn;
5312 {
5313     ChessMove moveType;
5314     ChessSquare pdown, pup;
5315
5316     /* Check if the user is playing in turn.  This is complicated because we
5317        let the user "pick up" a piece before it is his turn.  So the piece he
5318        tried to pick up may have been captured by the time he puts it down!
5319        Therefore we use the color the user is supposed to be playing in this
5320        test, not the color of the piece that is currently on the starting
5321        square---except in EditGame mode, where the user is playing both
5322        sides; fortunately there the capture race can't happen.  (It can
5323        now happen in IcsExamining mode, but that's just too bad.  The user
5324        will get a somewhat confusing message in that case.)
5325        */
5326
5327     switch (gameMode) {
5328       case PlayFromGameFile:
5329       case AnalyzeFile:
5330       case TwoMachinesPlay:
5331       case EndOfGame:
5332       case IcsObserving:
5333       case IcsIdle:
5334         /* We switched into a game mode where moves are not accepted,
5335            perhaps while the mouse button was down. */
5336         return ImpossibleMove;
5337
5338       case MachinePlaysWhite:
5339         /* User is moving for Black */
5340         if (WhiteOnMove(currentMove)) {
5341             DisplayMoveError(_("It is White's turn"));
5342             return ImpossibleMove;
5343         }
5344         break;
5345
5346       case MachinePlaysBlack:
5347         /* User is moving for White */
5348         if (!WhiteOnMove(currentMove)) {
5349             DisplayMoveError(_("It is Black's turn"));
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case EditGame:
5355       case IcsExamining:
5356       case BeginningOfGame:
5357       case AnalyzeMode:
5358       case Training:
5359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5361             /* User is moving for Black */
5362             if (WhiteOnMove(currentMove)) {
5363                 DisplayMoveError(_("It is White's turn"));
5364                 return ImpossibleMove;
5365             }
5366         } else {
5367             /* User is moving for White */
5368             if (!WhiteOnMove(currentMove)) {
5369                 DisplayMoveError(_("It is Black's turn"));
5370                 return ImpossibleMove;
5371             }
5372         }
5373         break;
5374
5375       case IcsPlayingBlack:
5376         /* User is moving for Black */
5377         if (WhiteOnMove(currentMove)) {
5378             if (!appData.premove) {
5379                 DisplayMoveError(_("It is White's turn"));
5380             } else if (toX >= 0 && toY >= 0) {
5381                 premoveToX = toX;
5382                 premoveToY = toY;
5383                 premoveFromX = fromX;
5384                 premoveFromY = fromY;
5385                 premovePromoChar = promoChar;
5386                 gotPremove = 1;
5387                 if (appData.debugMode) 
5388                     fprintf(debugFP, "Got premove: fromX %d,"
5389                             "fromY %d, toX %d, toY %d\n",
5390                             fromX, fromY, toX, toY);
5391             }
5392             return ImpossibleMove;
5393         }
5394         break;
5395
5396       case IcsPlayingWhite:
5397         /* User is moving for White */
5398         if (!WhiteOnMove(currentMove)) {
5399             if (!appData.premove) {
5400                 DisplayMoveError(_("It is Black's turn"));
5401             } else if (toX >= 0 && toY >= 0) {
5402                 premoveToX = toX;
5403                 premoveToY = toY;
5404                 premoveFromX = fromX;
5405                 premoveFromY = fromY;
5406                 premovePromoChar = promoChar;
5407                 gotPremove = 1;
5408                 if (appData.debugMode) 
5409                     fprintf(debugFP, "Got premove: fromX %d,"
5410                             "fromY %d, toX %d, toY %d\n",
5411                             fromX, fromY, toX, toY);
5412             }
5413             return ImpossibleMove;
5414         }
5415         break;
5416
5417       default:
5418         break;
5419
5420       case EditPosition:
5421         /* EditPosition, empty square, or different color piece;
5422            click-click move is possible */
5423         if (toX == -2 || toY == -2) {
5424             boards[0][fromY][fromX] = EmptySquare;
5425             return AmbiguousMove;
5426         } else if (toX >= 0 && toY >= 0) {
5427             boards[0][toY][toX] = boards[0][fromY][fromX];
5428             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5429                 if(boards[0][fromY][0] != EmptySquare) {
5430                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5431                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5432                 }
5433             } else
5434             if(fromX == BOARD_RGHT+1) {
5435                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5436                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5437                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5438                 }
5439             } else
5440             boards[0][fromY][fromX] = EmptySquare;
5441             return AmbiguousMove;
5442         }
5443         return ImpossibleMove;
5444     }
5445
5446     if(toX < 0 || toY < 0) return ImpossibleMove;
5447     pdown = boards[currentMove][fromY][fromX];
5448     pup = boards[currentMove][toY][toX];
5449
5450     /* [HGM] If move started in holdings, it means a drop */
5451     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5452          if( pup != EmptySquare ) return ImpossibleMove;
5453          if(appData.testLegality) {
5454              /* it would be more logical if LegalityTest() also figured out
5455               * which drops are legal. For now we forbid pawns on back rank.
5456               * Shogi is on its own here...
5457               */
5458              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5459                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5460                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461          }
5462          return WhiteDrop; /* Not needed to specify white or black yet */
5463     }
5464
5465     /* [HGM] always test for legality, to get promotion info */
5466     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5467                                          fromY, fromX, toY, toX, promoChar);
5468     /* [HGM] but possibly ignore an IllegalMove result */
5469     if (appData.testLegality) {
5470         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5471             DisplayMoveError(_("Illegal move"));
5472             return ImpossibleMove;
5473         }
5474     }
5475
5476     userOfferedDraw = FALSE; // [HGM] drawclaim: only if we do a legal move!
5477         
5478     return moveType;
5479     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5480        function is made into one that returns an OK move type if FinishMove
5481        should be called. This to give the calling driver routine the
5482        opportunity to finish the userMove input with a promotion popup,
5483        without bothering the user with this for invalid or illegal moves */
5484
5485 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5486 }
5487
5488 /* Common tail of UserMoveEvent and DropMenuEvent */
5489 int
5490 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5491      ChessMove moveType;
5492      int fromX, fromY, toX, toY;
5493      /*char*/int promoChar;
5494 {
5495     char *bookHit = 0;
5496
5497     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5498         // [HGM] superchess: suppress promotions to non-available piece
5499         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5500         if(WhiteOnMove(currentMove)) {
5501             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5502         } else {
5503             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5504         }
5505     }
5506
5507     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5508        move type in caller when we know the move is a legal promotion */
5509     if(moveType == NormalMove && promoChar)
5510         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5511
5512     /* [HGM] convert drag-and-drop piece drops to standard form */
5513     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5514          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5515            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5516                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5517            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5518            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5519            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5520            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5521          fromY = DROP_RANK;
5522     }
5523
5524     /* [HGM] <popupFix> The following if has been moved here from
5525        UserMoveEvent(). Because it seemed to belong here (why not allow
5526        piece drops in training games?), and because it can only be
5527        performed after it is known to what we promote. */
5528     if (gameMode == Training) {
5529       /* compare the move played on the board to the next move in the
5530        * game. If they match, display the move and the opponent's response. 
5531        * If they don't match, display an error message.
5532        */
5533       int saveAnimate;
5534       Board testBoard;
5535       CopyBoard(testBoard, boards[currentMove]);
5536       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5537
5538       if (CompareBoards(testBoard, boards[currentMove+1])) {
5539         ForwardInner(currentMove+1);
5540
5541         /* Autoplay the opponent's response.
5542          * if appData.animate was TRUE when Training mode was entered,
5543          * the response will be animated.
5544          */
5545         saveAnimate = appData.animate;
5546         appData.animate = animateTraining;
5547         ForwardInner(currentMove+1);
5548         appData.animate = saveAnimate;
5549
5550         /* check for the end of the game */
5551         if (currentMove >= forwardMostMove) {
5552           gameMode = PlayFromGameFile;
5553           ModeHighlight();
5554           SetTrainingModeOff();
5555           DisplayInformation(_("End of game"));
5556         }
5557       } else {
5558         DisplayError(_("Incorrect move"), 0);
5559       }
5560       return 1;
5561     }
5562
5563   /* Ok, now we know that the move is good, so we can kill
5564      the previous line in Analysis Mode */
5565   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5566                                 && currentMove < forwardMostMove) {
5567     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5568   }
5569
5570   /* If we need the chess program but it's dead, restart it */
5571   ResurrectChessProgram();
5572
5573   /* A user move restarts a paused game*/
5574   if (pausing)
5575     PauseEvent();
5576
5577   thinkOutput[0] = NULLCHAR;
5578
5579   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5580
5581   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5582
5583   if (gameMode == BeginningOfGame) {
5584     if (appData.noChessProgram) {
5585       gameMode = EditGame;
5586       SetGameInfo();
5587     } else {
5588       char buf[MSG_SIZ];
5589       gameMode = MachinePlaysBlack;
5590       StartClocks();
5591       SetGameInfo();
5592       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5593       DisplayTitle(buf);
5594       if (first.sendName) {
5595         sprintf(buf, "name %s\n", gameInfo.white);
5596         SendToProgram(buf, &first);
5597       }
5598       StartClocks();
5599     }
5600     ModeHighlight();
5601   }
5602
5603   /* Relay move to ICS or chess engine */
5604   if (appData.icsActive) {
5605     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5606         gameMode == IcsExamining) {
5607       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5608       ics_user_moved = 1;
5609     }
5610   } else {
5611     if (first.sendTime && (gameMode == BeginningOfGame ||
5612                            gameMode == MachinePlaysWhite ||
5613                            gameMode == MachinePlaysBlack)) {
5614       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5615     }
5616     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5617          // [HGM] book: if program might be playing, let it use book
5618         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5619         first.maybeThinking = TRUE;
5620     } else SendMoveToProgram(forwardMostMove-1, &first);
5621     if (currentMove == cmailOldMove + 1) {
5622       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5623     }
5624   }
5625
5626   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5627
5628   switch (gameMode) {
5629   case EditGame:
5630     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5631     case MT_NONE:
5632     case MT_CHECK:
5633       break;
5634     case MT_CHECKMATE:
5635     case MT_STAINMATE:
5636       if (WhiteOnMove(currentMove)) {
5637         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5638       } else {
5639         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5640       }
5641       break;
5642     case MT_STALEMATE:
5643       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5644       break;
5645     }
5646     break;
5647     
5648   case MachinePlaysBlack:
5649   case MachinePlaysWhite:
5650     /* disable certain menu options while machine is thinking */
5651     SetMachineThinkingEnables();
5652     break;
5653
5654   default:
5655     break;
5656   }
5657
5658   if(bookHit) { // [HGM] book: simulate book reply
5659         static char bookMove[MSG_SIZ]; // a bit generous?
5660
5661         programStats.nodes = programStats.depth = programStats.time = 
5662         programStats.score = programStats.got_only_move = 0;
5663         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5664
5665         strcpy(bookMove, "move ");
5666         strcat(bookMove, bookHit);
5667         HandleMachineMove(bookMove, &first);
5668   }
5669   return 1;
5670 }
5671
5672 void
5673 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5674      int fromX, fromY, toX, toY;
5675      int promoChar;
5676 {
5677     /* [HGM] This routine was added to allow calling of its two logical
5678        parts from other modules in the old way. Before, UserMoveEvent()
5679        automatically called FinishMove() if the move was OK, and returned
5680        otherwise. I separated the two, in order to make it possible to
5681        slip a promotion popup in between. But that it always needs two
5682        calls, to the first part, (now called UserMoveTest() ), and to
5683        FinishMove if the first part succeeded. Calls that do not need
5684        to do anything in between, can call this routine the old way. 
5685     */
5686     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5687 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5688     if(moveType == AmbiguousMove)
5689         DrawPosition(FALSE, boards[currentMove]);
5690     else if(moveType != ImpossibleMove && moveType != Comment)
5691         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5692 }
5693
5694 void
5695 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5696      Board board;
5697      int flags;
5698      ChessMove kind;
5699      int rf, ff, rt, ft;
5700      VOIDSTAR closure;
5701 {
5702     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5703     Markers *m = (Markers *) closure;
5704     if(rf == fromY && ff == fromX)
5705         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5706                          || kind == WhiteCapturesEnPassant
5707                          || kind == BlackCapturesEnPassant);
5708     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5709 }
5710
5711 void
5712 MarkTargetSquares(int clear)
5713 {
5714   int x, y;
5715   if(!appData.markers || !appData.highlightDragging || 
5716      !appData.testLegality || gameMode == EditPosition) return;
5717   if(clear) {
5718     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5719   } else {
5720     int capt = 0;
5721     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5722     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5723       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5724       if(capt)
5725       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5726     }
5727   }
5728   DrawPosition(TRUE, NULL);
5729 }
5730
5731 void LeftClick(ClickType clickType, int xPix, int yPix)
5732 {
5733     int x, y;
5734     Boolean saveAnimate;
5735     static int second = 0, promotionChoice = 0;
5736     char promoChoice = NULLCHAR;
5737
5738     if (clickType == Press) ErrorPopDown();
5739     MarkTargetSquares(1);
5740
5741     x = EventToSquare(xPix, BOARD_WIDTH);
5742     y = EventToSquare(yPix, BOARD_HEIGHT);
5743     if (!flipView && y >= 0) {
5744         y = BOARD_HEIGHT - 1 - y;
5745     }
5746     if (flipView && x >= 0) {
5747         x = BOARD_WIDTH - 1 - x;
5748     }
5749
5750     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5751         if(clickType == Release) return; // ignore upclick of click-click destination
5752         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5753         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5754         if(gameInfo.holdingsWidth && 
5755                 (WhiteOnMove(currentMove) 
5756                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5757                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5758             // click in right holdings, for determining promotion piece
5759             ChessSquare p = boards[currentMove][y][x];
5760             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5761             if(p != EmptySquare) {
5762                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5763                 fromX = fromY = -1;
5764                 return;
5765             }
5766         }
5767         DrawPosition(FALSE, boards[currentMove]);
5768         return;
5769     }
5770
5771     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5772     if(clickType == Press
5773             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5774               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5775               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5776         return;
5777
5778     if (fromX == -1) {
5779         if (clickType == Press) {
5780             /* First square */
5781             if (OKToStartUserMove(x, y)) {
5782                 fromX = x;
5783                 fromY = y;
5784                 second = 0;
5785                 MarkTargetSquares(0);
5786                 DragPieceBegin(xPix, yPix);
5787                 if (appData.highlightDragging) {
5788                     SetHighlights(x, y, -1, -1);
5789                 }
5790             }
5791         }
5792         return;
5793     }
5794
5795     /* fromX != -1 */
5796     if (clickType == Press && gameMode != EditPosition) {
5797         ChessSquare fromP;
5798         ChessSquare toP;
5799         int frc;
5800
5801         // ignore off-board to clicks
5802         if(y < 0 || x < 0) return;
5803
5804         /* Check if clicking again on the same color piece */
5805         fromP = boards[currentMove][fromY][fromX];
5806         toP = boards[currentMove][y][x];
5807         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5808         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5809              WhitePawn <= toP && toP <= WhiteKing &&
5810              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5811              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5812             (BlackPawn <= fromP && fromP <= BlackKing && 
5813              BlackPawn <= toP && toP <= BlackKing &&
5814              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5815              !(fromP == BlackKing && toP == BlackRook && frc))) {
5816             /* Clicked again on same color piece -- changed his mind */
5817             second = (x == fromX && y == fromY);
5818             if (appData.highlightDragging) {
5819                 SetHighlights(x, y, -1, -1);
5820             } else {
5821                 ClearHighlights();
5822             }
5823             if (OKToStartUserMove(x, y)) {
5824                 fromX = x;
5825                 fromY = y;
5826                 MarkTargetSquares(0);
5827                 DragPieceBegin(xPix, yPix);
5828             }
5829             return;
5830         }
5831         // ignore clicks on holdings
5832         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5833     }
5834
5835     if (clickType == Release && x == fromX && y == fromY) {
5836         DragPieceEnd(xPix, yPix);
5837         if (appData.animateDragging) {
5838             /* Undo animation damage if any */
5839             DrawPosition(FALSE, NULL);
5840         }
5841         if (second) {
5842             /* Second up/down in same square; just abort move */
5843             second = 0;
5844             fromX = fromY = -1;
5845             ClearHighlights();
5846             gotPremove = 0;
5847             ClearPremoveHighlights();
5848         } else {
5849             /* First upclick in same square; start click-click mode */
5850             SetHighlights(x, y, -1, -1);
5851         }
5852         return;
5853     }
5854
5855     /* we now have a different from- and (possibly off-board) to-square */
5856     /* Completed move */
5857     toX = x;
5858     toY = y;
5859     saveAnimate = appData.animate;
5860     if (clickType == Press) {
5861         /* Finish clickclick move */
5862         if (appData.animate || appData.highlightLastMove) {
5863             SetHighlights(fromX, fromY, toX, toY);
5864         } else {
5865             ClearHighlights();
5866         }
5867     } else {
5868         /* Finish drag move */
5869         if (appData.highlightLastMove) {
5870             SetHighlights(fromX, fromY, toX, toY);
5871         } else {
5872             ClearHighlights();
5873         }
5874         DragPieceEnd(xPix, yPix);
5875         /* Don't animate move and drag both */
5876         appData.animate = FALSE;
5877     }
5878
5879     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5880     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5881         ChessSquare piece = boards[currentMove][fromY][fromX];
5882         if(gameMode == EditPosition && piece != EmptySquare &&
5883            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5884             int n;
5885              
5886             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5887                 n = PieceToNumber(piece - (int)BlackPawn);
5888                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5889                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5890                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5891             } else
5892             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5893                 n = PieceToNumber(piece);
5894                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5895                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5896                 boards[currentMove][n][BOARD_WIDTH-2]++;
5897             }
5898             boards[currentMove][fromY][fromX] = EmptySquare;
5899         }
5900         ClearHighlights();
5901         fromX = fromY = -1;
5902         DrawPosition(TRUE, boards[currentMove]);
5903         return;
5904     }
5905
5906     // off-board moves should not be highlighted
5907     if(x < 0 || x < 0) ClearHighlights();
5908
5909     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5910         SetHighlights(fromX, fromY, toX, toY);
5911         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5912             // [HGM] super: promotion to captured piece selected from holdings
5913             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5914             promotionChoice = TRUE;
5915             // kludge follows to temporarily execute move on display, without promoting yet
5916             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5917             boards[currentMove][toY][toX] = p;
5918             DrawPosition(FALSE, boards[currentMove]);
5919             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5920             boards[currentMove][toY][toX] = q;
5921             DisplayMessage("Click in holdings to choose piece", "");
5922             return;
5923         }
5924         PromotionPopUp();
5925     } else {
5926         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5927         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5928         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5929         fromX = fromY = -1;
5930     }
5931     appData.animate = saveAnimate;
5932     if (appData.animate || appData.animateDragging) {
5933         /* Undo animation damage if needed */
5934         DrawPosition(FALSE, NULL);
5935     }
5936 }
5937
5938 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5939 {
5940 //    char * hint = lastHint;
5941     FrontEndProgramStats stats;
5942
5943     stats.which = cps == &first ? 0 : 1;
5944     stats.depth = cpstats->depth;
5945     stats.nodes = cpstats->nodes;
5946     stats.score = cpstats->score;
5947     stats.time = cpstats->time;
5948     stats.pv = cpstats->movelist;
5949     stats.hint = lastHint;
5950     stats.an_move_index = 0;
5951     stats.an_move_count = 0;
5952
5953     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5954         stats.hint = cpstats->move_name;
5955         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5956         stats.an_move_count = cpstats->nr_moves;
5957     }
5958
5959     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5960
5961     SetProgramStats( &stats );
5962 }
5963
5964 int
5965 Adjudicate(ChessProgramState *cps)
5966 {       // [HGM] some adjudications useful with buggy engines
5967         // [HGM] adjudicate: made into separate routine, which now can be called after every move
5968         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5969         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
5970         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5971         int k, count = 0; static int bare = 1;
5972         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5973         Boolean canAdjudicate = !appData.icsActive;
5974
5975         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5976         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5977             if( appData.testLegality )
5978             {   /* [HGM] Some more adjudications for obstinate engines */
5979                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5980                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5981                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5982                 static int moveCount = 6;
5983                 ChessMove result;
5984                 char *reason = NULL;
5985
5986                 /* Count what is on board. */
5987                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5988                 {   ChessSquare p = boards[forwardMostMove][i][j];
5989                     int m=i;
5990
5991                     switch((int) p)
5992                     {   /* count B,N,R and other of each side */
5993                         case WhiteKing:
5994                         case BlackKing:
5995                              NrK++; break; // [HGM] atomic: count Kings
5996                         case WhiteKnight:
5997                              NrWN++; break;
5998                         case WhiteBishop:
5999                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6000                              bishopsColor |= 1 << ((i^j)&1);
6001                              NrWB++; break;
6002                         case BlackKnight:
6003                              NrBN++; break;
6004                         case BlackBishop:
6005                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6006                              bishopsColor |= 1 << ((i^j)&1);
6007                              NrBB++; break;
6008                         case WhiteRook:
6009                              NrWR++; break;
6010                         case BlackRook:
6011                              NrBR++; break;
6012                         case WhiteQueen:
6013                              NrWQ++; break;
6014                         case BlackQueen:
6015                              NrBQ++; break;
6016                         case EmptySquare: 
6017                              break;
6018                         case BlackPawn:
6019                              m = 7-i;
6020                         case WhitePawn:
6021                              PawnAdvance += m; NrPawns++;
6022                     }
6023                     NrPieces += (p != EmptySquare);
6024                     NrW += ((int)p < (int)BlackPawn);
6025                     if(gameInfo.variant == VariantXiangqi && 
6026                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6027                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6028                         NrW -= ((int)p < (int)BlackPawn);
6029                     }
6030                 }
6031
6032                 /* Some material-based adjudications that have to be made before stalemate test */
6033                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6034                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6035                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6036                      if(canAdjudicate && appData.checkMates) {
6037                          if(engineOpponent)
6038                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6039                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6041                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6042                          return 1;
6043                      }
6044                 }
6045
6046                 /* Bare King in Shatranj (loses) or Losers (wins) */
6047                 if( NrW == 1 || NrPieces - NrW == 1) {
6048                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6049                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6050                      if(canAdjudicate && appData.checkMates) {
6051                          if(engineOpponent)
6052                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6053                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6055                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6056                          return 1;
6057                      }
6058                   } else
6059                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6060                   {    /* bare King */
6061                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6062                         if(canAdjudicate && appData.checkMates) {
6063                             /* but only adjudicate if adjudication enabled */
6064                             if(engineOpponent)
6065                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6066                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6069                             return 1;
6070                         }
6071                   }
6072                 } else bare = 1;
6073
6074
6075             // don't wait for engine to announce game end if we can judge ourselves
6076             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6077               case MT_CHECK:
6078                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6079                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6080                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6081                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6082                             checkCnt++;
6083                         if(checkCnt >= 2) {
6084                             reason = "Xboard adjudication: 3rd check";
6085                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6086                             break;
6087                         }
6088                     }
6089                 }
6090               case MT_NONE:
6091               default:
6092                 break;
6093               case MT_STALEMATE:
6094               case MT_STAINMATE:
6095                 reason = "Xboard adjudication: Stalemate";
6096                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6097                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6098                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6099                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6100                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6101                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6102                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6103                                                                         EP_CHECKMATE : EP_WINS);
6104                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6105                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6106                 }
6107                 break;
6108               case MT_CHECKMATE:
6109                 reason = "Xboard adjudication: Checkmate";
6110                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6111                 break;
6112             }
6113
6114                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6115                     case EP_STALEMATE:
6116                         result = GameIsDrawn; break;
6117                     case EP_CHECKMATE:
6118                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6119                     case EP_WINS:
6120                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6121                     default:
6122                         result = (ChessMove) 0;
6123                 }
6124                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6125                     if(engineOpponent)
6126                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6127                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128                     GameEnds( result, reason, GE_XBOARD );
6129                     return 1;
6130                 }
6131
6132                 /* Next absolutely insufficient mating material. */
6133                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6134                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6135                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6136                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6137                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6138
6139                      /* always flag draws, for judging claims */
6140                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6141
6142                      if(canAdjudicate && appData.materialDraws) {
6143                          /* but only adjudicate them if adjudication enabled */
6144                          if(engineOpponent) {
6145                            SendToProgram("force\n", engineOpponent); // suppress reply
6146                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6147                          }
6148                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6150                          return 1;
6151                      }
6152                 }
6153
6154                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6155                 if(NrPieces == 4 && 
6156                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6157                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6158                    || NrWN==2 || NrBN==2     /* KNNK */
6159                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6160                   ) ) {
6161                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6162                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6163                           if(engineOpponent) {
6164                             SendToProgram("force\n", engineOpponent); // suppress reply
6165                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6166                           }
6167                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6169                           return 1;
6170                      }
6171                 } else moveCount = 6;
6172             }
6173         }
6174           
6175         if (appData.debugMode) { int i;
6176             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6177                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6178                     appData.drawRepeats);
6179             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6180               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6181             
6182         }
6183
6184         // Repetition draws and 50-move rule can be applied independently of legality testing
6185
6186                 /* Check for rep-draws */
6187                 count = 0;
6188                 for(k = forwardMostMove-2;
6189                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6190                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6191                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6192                     k-=2)
6193                 {   int rights=0;
6194                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6195                         /* compare castling rights */
6196                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6197                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6198                                 rights++; /* King lost rights, while rook still had them */
6199                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6200                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6201                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6202                                    rights++; /* but at least one rook lost them */
6203                         }
6204                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6205                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6206                                 rights++; 
6207                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6208                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6209                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6210                                    rights++;
6211                         }
6212                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6213                             && appData.drawRepeats > 1) {
6214                              /* adjudicate after user-specified nr of repeats */
6215                              if(engineOpponent) {
6216                                SendToProgram("force\n", engineOpponent); // suppress reply
6217                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6218                              }
6219                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6220                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6221                                 // [HGM] xiangqi: check for forbidden perpetuals
6222                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6223                                 for(m=forwardMostMove; m>k; m-=2) {
6224                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6225                                         ourPerpetual = 0; // the current mover did not always check
6226                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6227                                         hisPerpetual = 0; // the opponent did not always check
6228                                 }
6229                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6230                                                                         ourPerpetual, hisPerpetual);
6231                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6232                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6233                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6234                                     return 1;
6235                                 }
6236                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6237                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6238                                 // Now check for perpetual chases
6239                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6240                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6241                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6242                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6243                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6244                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6245                                         return 1;
6246                                     }
6247                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6248                                         break; // Abort repetition-checking loop.
6249                                 }
6250                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6251                              }
6252                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6253                              return 1;
6254                         }
6255                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6256                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6257                     }
6258                 }
6259
6260                 /* Now we test for 50-move draws. Determine ply count */
6261                 count = forwardMostMove;
6262                 /* look for last irreversble move */
6263                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6264                     count--;
6265                 /* if we hit starting position, add initial plies */
6266                 if( count == backwardMostMove )
6267                     count -= initialRulePlies;
6268                 count = forwardMostMove - count; 
6269                 if( count >= 100)
6270                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6271                          /* this is used to judge if draw claims are legal */
6272                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6273                          if(engineOpponent) {
6274                            SendToProgram("force\n", engineOpponent); // suppress reply
6275                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6276                          }
6277                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6279                          return 1;
6280                 }
6281
6282                 /* if draw offer is pending, treat it as a draw claim
6283                  * when draw condition present, to allow engines a way to
6284                  * claim draws before making their move to avoid a race
6285                  * condition occurring after their move
6286                  */
6287                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6288                          char *p = NULL;
6289                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6290                              p = "Draw claim: 50-move rule";
6291                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6292                              p = "Draw claim: 3-fold repetition";
6293                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6294                              p = "Draw claim: insufficient mating material";
6295                          if( p != NULL && canAdjudicate) {
6296                              if(engineOpponent) {
6297                                SendToProgram("force\n", engineOpponent); // suppress reply
6298                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6299                              }
6300                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6301                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6302                              return 1;
6303                          }
6304                 }
6305
6306                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6307                     if(engineOpponent) {
6308                       SendToProgram("force\n", engineOpponent); // suppress reply
6309                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6310                     }
6311                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6312                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6313                     return 1;
6314                 }
6315         return 0;
6316 }
6317
6318 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6319 {   // [HGM] book: this routine intercepts moves to simulate book replies
6320     char *bookHit = NULL;
6321
6322     //first determine if the incoming move brings opponent into his book
6323     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6324         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6325     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6326     if(bookHit != NULL && !cps->bookSuspend) {
6327         // make sure opponent is not going to reply after receiving move to book position
6328         SendToProgram("force\n", cps);
6329         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6330     }
6331     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6332     // now arrange restart after book miss
6333     if(bookHit) {
6334         // after a book hit we never send 'go', and the code after the call to this routine
6335         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6336         char buf[MSG_SIZ];
6337         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6338         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6339         SendToProgram(buf, cps);
6340         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6341     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6342         SendToProgram("go\n", cps);
6343         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6344     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6345         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6346             SendToProgram("go\n", cps); 
6347         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6348     }
6349     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6350 }
6351
6352 char *savedMessage;
6353 ChessProgramState *savedState;
6354 void DeferredBookMove(void)
6355 {
6356         if(savedState->lastPing != savedState->lastPong)
6357                     ScheduleDelayedEvent(DeferredBookMove, 10);
6358         else
6359         HandleMachineMove(savedMessage, savedState);
6360 }
6361
6362 void
6363 HandleMachineMove(message, cps)
6364      char *message;
6365      ChessProgramState *cps;
6366 {
6367     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6368     char realname[MSG_SIZ];
6369     int fromX, fromY, toX, toY;
6370     ChessMove moveType;
6371     char promoChar;
6372     char *p;
6373     int machineWhite;
6374     char *bookHit;
6375
6376     cps->userError = 0;
6377
6378 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6379     /*
6380      * Kludge to ignore BEL characters
6381      */
6382     while (*message == '\007') message++;
6383
6384     /*
6385      * [HGM] engine debug message: ignore lines starting with '#' character
6386      */
6387     if(cps->debug && *message == '#') return;
6388
6389     /*
6390      * Look for book output
6391      */
6392     if (cps == &first && bookRequested) {
6393         if (message[0] == '\t' || message[0] == ' ') {
6394             /* Part of the book output is here; append it */
6395             strcat(bookOutput, message);
6396             strcat(bookOutput, "  \n");
6397             return;
6398         } else if (bookOutput[0] != NULLCHAR) {
6399             /* All of book output has arrived; display it */
6400             char *p = bookOutput;
6401             while (*p != NULLCHAR) {
6402                 if (*p == '\t') *p = ' ';
6403                 p++;
6404             }
6405             DisplayInformation(bookOutput);
6406             bookRequested = FALSE;
6407             /* Fall through to parse the current output */
6408         }
6409     }
6410
6411     /*
6412      * Look for machine move.
6413      */
6414     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6415         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6416     {
6417         /* This method is only useful on engines that support ping */
6418         if (cps->lastPing != cps->lastPong) {
6419           if (gameMode == BeginningOfGame) {
6420             /* Extra move from before last new; ignore */
6421             if (appData.debugMode) {
6422                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6423             }
6424           } else {
6425             if (appData.debugMode) {
6426                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6427                         cps->which, gameMode);
6428             }
6429
6430             SendToProgram("undo\n", cps);
6431           }
6432           return;
6433         }
6434
6435         switch (gameMode) {
6436           case BeginningOfGame:
6437             /* Extra move from before last reset; ignore */
6438             if (appData.debugMode) {
6439                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6440             }
6441             return;
6442
6443           case EndOfGame:
6444           case IcsIdle:
6445           default:
6446             /* Extra move after we tried to stop.  The mode test is
6447                not a reliable way of detecting this problem, but it's
6448                the best we can do on engines that don't support ping.
6449             */
6450             if (appData.debugMode) {
6451                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6452                         cps->which, gameMode);
6453             }
6454             SendToProgram("undo\n", cps);
6455             return;
6456
6457           case MachinePlaysWhite:
6458           case IcsPlayingWhite:
6459             machineWhite = TRUE;
6460             break;
6461
6462           case MachinePlaysBlack:
6463           case IcsPlayingBlack:
6464             machineWhite = FALSE;
6465             break;
6466
6467           case TwoMachinesPlay:
6468             machineWhite = (cps->twoMachinesColor[0] == 'w');
6469             break;
6470         }
6471         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6472             if (appData.debugMode) {
6473                 fprintf(debugFP,
6474                         "Ignoring move out of turn by %s, gameMode %d"
6475                         ", forwardMost %d\n",
6476                         cps->which, gameMode, forwardMostMove);
6477             }
6478             return;
6479         }
6480
6481     if (appData.debugMode) { int f = forwardMostMove;
6482         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6483                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6484                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6485     }
6486         if(cps->alphaRank) AlphaRank(machineMove, 4);
6487         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6488                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6489             /* Machine move could not be parsed; ignore it. */
6490             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6491                     machineMove, cps->which);
6492             DisplayError(buf1, 0);
6493             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6494                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6495             if (gameMode == TwoMachinesPlay) {
6496               GameEnds(machineWhite ? BlackWins : WhiteWins,
6497                        buf1, GE_XBOARD);
6498             }
6499             return;
6500         }
6501
6502         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6503         /* So we have to redo legality test with true e.p. status here,  */
6504         /* to make sure an illegal e.p. capture does not slip through,   */
6505         /* to cause a forfeit on a justified illegal-move complaint      */
6506         /* of the opponent.                                              */
6507         if( gameMode==TwoMachinesPlay && appData.testLegality
6508             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6509                                                               ) {
6510            ChessMove moveType;
6511            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6512                              fromY, fromX, toY, toX, promoChar);
6513             if (appData.debugMode) {
6514                 int i;
6515                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6516                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6517                 fprintf(debugFP, "castling rights\n");
6518             }
6519             if(moveType == IllegalMove) {
6520                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6521                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6522                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6523                            buf1, GE_XBOARD);
6524                 return;
6525            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6526            /* [HGM] Kludge to handle engines that send FRC-style castling
6527               when they shouldn't (like TSCP-Gothic) */
6528            switch(moveType) {
6529              case WhiteASideCastleFR:
6530              case BlackASideCastleFR:
6531                toX+=2;
6532                currentMoveString[2]++;
6533                break;
6534              case WhiteHSideCastleFR:
6535              case BlackHSideCastleFR:
6536                toX--;
6537                currentMoveString[2]--;
6538                break;
6539              default: ; // nothing to do, but suppresses warning of pedantic compilers
6540            }
6541         }
6542         hintRequested = FALSE;
6543         lastHint[0] = NULLCHAR;
6544         bookRequested = FALSE;
6545         /* Program may be pondering now */
6546         cps->maybeThinking = TRUE;
6547         if (cps->sendTime == 2) cps->sendTime = 1;
6548         if (cps->offeredDraw) cps->offeredDraw--;
6549
6550 #if ZIPPY
6551         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6552             first.initDone) {
6553           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6554           ics_user_moved = 1;
6555           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6556                 char buf[3*MSG_SIZ];
6557
6558                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6559                         programStats.score / 100.,
6560                         programStats.depth,
6561                         programStats.time / 100.,
6562                         (unsigned int)programStats.nodes,
6563                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6564                         programStats.movelist);
6565                 SendToICS(buf);
6566 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6567           }
6568         }
6569 #endif
6570         /* currentMoveString is set as a side-effect of ParseOneMove */
6571         strcpy(machineMove, currentMoveString);
6572         strcat(machineMove, "\n");
6573         strcpy(moveList[forwardMostMove], machineMove);
6574
6575         /* [AS] Save move info and clear stats for next move */
6576         pvInfoList[ forwardMostMove ].score = programStats.score;
6577         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6578         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6579         ClearProgramStats();
6580         thinkOutput[0] = NULLCHAR;
6581         hiddenThinkOutputState = 0;
6582
6583         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6584
6585         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6586         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6587             int count = 0;
6588
6589             while( count < adjudicateLossPlies ) {
6590                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6591
6592                 if( count & 1 ) {
6593                     score = -score; /* Flip score for winning side */
6594                 }
6595
6596                 if( score > adjudicateLossThreshold ) {
6597                     break;
6598                 }
6599
6600                 count++;
6601             }
6602
6603             if( count >= adjudicateLossPlies ) {
6604                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6605
6606                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6607                     "Xboard adjudication", 
6608                     GE_XBOARD );
6609
6610                 return;
6611             }
6612         }
6613
6614         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6615
6616         bookHit = NULL;
6617         if (gameMode == TwoMachinesPlay) {
6618             /* [HGM] relaying draw offers moved to after reception of move */
6619             /* and interpreting offer as claim if it brings draw condition */
6620             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6621                 SendToProgram("draw\n", cps->other);
6622             }
6623             if (cps->other->sendTime) {
6624                 SendTimeRemaining(cps->other,
6625                                   cps->other->twoMachinesColor[0] == 'w');
6626             }
6627             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6628             if (firstMove && !bookHit) {
6629                 firstMove = FALSE;
6630                 if (cps->other->useColors) {
6631                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6632                 }
6633                 SendToProgram("go\n", cps->other);
6634             }
6635             cps->other->maybeThinking = TRUE;
6636         }
6637
6638         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639         
6640         if (!pausing && appData.ringBellAfterMoves) {
6641             RingBell();
6642         }
6643
6644         /* 
6645          * Reenable menu items that were disabled while
6646          * machine was thinking
6647          */
6648         if (gameMode != TwoMachinesPlay)
6649             SetUserThinkingEnables();
6650
6651         // [HGM] book: after book hit opponent has received move and is now in force mode
6652         // force the book reply into it, and then fake that it outputted this move by jumping
6653         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6654         if(bookHit) {
6655                 static char bookMove[MSG_SIZ]; // a bit generous?
6656
6657                 strcpy(bookMove, "move ");
6658                 strcat(bookMove, bookHit);
6659                 message = bookMove;
6660                 cps = cps->other;
6661                 programStats.nodes = programStats.depth = programStats.time = 
6662                 programStats.score = programStats.got_only_move = 0;
6663                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6664
6665                 if(cps->lastPing != cps->lastPong) {
6666                     savedMessage = message; // args for deferred call
6667                     savedState = cps;
6668                     ScheduleDelayedEvent(DeferredBookMove, 10);
6669                     return;
6670                 }
6671                 goto FakeBookMove;
6672         }
6673
6674         return;
6675     }
6676
6677     /* Set special modes for chess engines.  Later something general
6678      *  could be added here; for now there is just one kludge feature,
6679      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6680      *  when "xboard" is given as an interactive command.
6681      */
6682     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6683         cps->useSigint = FALSE;
6684         cps->useSigterm = FALSE;
6685     }
6686     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6687       ParseFeatures(message+8, cps);
6688       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6689     }
6690
6691     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6692      * want this, I was asked to put it in, and obliged.
6693      */
6694     if (!strncmp(message, "setboard ", 9)) {
6695         Board initial_position;
6696
6697         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6698
6699         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6700             DisplayError(_("Bad FEN received from engine"), 0);
6701             return ;
6702         } else {
6703            Reset(TRUE, FALSE);
6704            CopyBoard(boards[0], initial_position);
6705            initialRulePlies = FENrulePlies;
6706            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6707            else gameMode = MachinePlaysBlack;                 
6708            DrawPosition(FALSE, boards[currentMove]);
6709         }
6710         return;
6711     }
6712
6713     /*
6714      * Look for communication commands
6715      */
6716     if (!strncmp(message, "telluser ", 9)) {
6717         DisplayNote(message + 9);
6718         return;
6719     }
6720     if (!strncmp(message, "tellusererror ", 14)) {
6721         cps->userError = 1;
6722         DisplayError(message + 14, 0);
6723         return;
6724     }
6725     if (!strncmp(message, "tellopponent ", 13)) {
6726       if (appData.icsActive) {
6727         if (loggedOn) {
6728           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6729           SendToICS(buf1);
6730         }
6731       } else {
6732         DisplayNote(message + 13);
6733       }
6734       return;
6735     }
6736     if (!strncmp(message, "tellothers ", 11)) {
6737       if (appData.icsActive) {
6738         if (loggedOn) {
6739           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6740           SendToICS(buf1);
6741         }
6742       }
6743       return;
6744     }
6745     if (!strncmp(message, "tellall ", 8)) {
6746       if (appData.icsActive) {
6747         if (loggedOn) {
6748           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6749           SendToICS(buf1);
6750         }
6751       } else {
6752         DisplayNote(message + 8);
6753       }
6754       return;
6755     }
6756     if (strncmp(message, "warning", 7) == 0) {
6757         /* Undocumented feature, use tellusererror in new code */
6758         DisplayError(message, 0);
6759         return;
6760     }
6761     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6762         strcpy(realname, cps->tidy);
6763         strcat(realname, " query");
6764         AskQuestion(realname, buf2, buf1, cps->pr);
6765         return;
6766     }
6767     /* Commands from the engine directly to ICS.  We don't allow these to be 
6768      *  sent until we are logged on. Crafty kibitzes have been known to 
6769      *  interfere with the login process.
6770      */
6771     if (loggedOn) {
6772         if (!strncmp(message, "tellics ", 8)) {
6773             SendToICS(message + 8);
6774             SendToICS("\n");
6775             return;
6776         }
6777         if (!strncmp(message, "tellicsnoalias ", 15)) {
6778             SendToICS(ics_prefix);
6779             SendToICS(message + 15);
6780             SendToICS("\n");
6781             return;
6782         }
6783         /* The following are for backward compatibility only */
6784         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6785             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6786             SendToICS(ics_prefix);
6787             SendToICS(message);
6788             SendToICS("\n");
6789             return;
6790         }
6791     }
6792     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6793         return;
6794     }
6795     /*
6796      * If the move is illegal, cancel it and redraw the board.
6797      * Also deal with other error cases.  Matching is rather loose
6798      * here to accommodate engines written before the spec.
6799      */
6800     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6801         strncmp(message, "Error", 5) == 0) {
6802         if (StrStr(message, "name") || 
6803             StrStr(message, "rating") || StrStr(message, "?") ||
6804             StrStr(message, "result") || StrStr(message, "board") ||
6805             StrStr(message, "bk") || StrStr(message, "computer") ||
6806             StrStr(message, "variant") || StrStr(message, "hint") ||
6807             StrStr(message, "random") || StrStr(message, "depth") ||
6808             StrStr(message, "accepted")) {
6809             return;
6810         }
6811         if (StrStr(message, "protover")) {
6812           /* Program is responding to input, so it's apparently done
6813              initializing, and this error message indicates it is
6814              protocol version 1.  So we don't need to wait any longer
6815              for it to initialize and send feature commands. */
6816           FeatureDone(cps, 1);
6817           cps->protocolVersion = 1;
6818           return;
6819         }
6820         cps->maybeThinking = FALSE;
6821
6822         if (StrStr(message, "draw")) {
6823             /* Program doesn't have "draw" command */
6824             cps->sendDrawOffers = 0;
6825             return;
6826         }
6827         if (cps->sendTime != 1 &&
6828             (StrStr(message, "time") || StrStr(message, "otim"))) {
6829           /* Program apparently doesn't have "time" or "otim" command */
6830           cps->sendTime = 0;
6831           return;
6832         }
6833         if (StrStr(message, "analyze")) {
6834             cps->analysisSupport = FALSE;
6835             cps->analyzing = FALSE;
6836             Reset(FALSE, TRUE);
6837             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6838             DisplayError(buf2, 0);
6839             return;
6840         }
6841         if (StrStr(message, "(no matching move)st")) {
6842           /* Special kludge for GNU Chess 4 only */
6843           cps->stKludge = TRUE;
6844           SendTimeControl(cps, movesPerSession, timeControl,
6845                           timeIncrement, appData.searchDepth,
6846                           searchTime);
6847           return;
6848         }
6849         if (StrStr(message, "(no matching move)sd")) {
6850           /* Special kludge for GNU Chess 4 only */
6851           cps->sdKludge = TRUE;
6852           SendTimeControl(cps, movesPerSession, timeControl,
6853                           timeIncrement, appData.searchDepth,
6854                           searchTime);
6855           return;
6856         }
6857         if (!StrStr(message, "llegal")) {
6858             return;
6859         }
6860         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6861             gameMode == IcsIdle) return;
6862         if (forwardMostMove <= backwardMostMove) return;
6863         if (pausing) PauseEvent();
6864       if(appData.forceIllegal) {
6865             // [HGM] illegal: machine refused move; force position after move into it
6866           SendToProgram("force\n", cps);
6867           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6868                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6869                 // when black is to move, while there might be nothing on a2 or black
6870                 // might already have the move. So send the board as if white has the move.
6871                 // But first we must change the stm of the engine, as it refused the last move
6872                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6873                 if(WhiteOnMove(forwardMostMove)) {
6874                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6875                     SendBoard(cps, forwardMostMove); // kludgeless board
6876                 } else {
6877                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6878                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6879                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6880                 }
6881           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6882             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6883                  gameMode == TwoMachinesPlay)
6884               SendToProgram("go\n", cps);
6885             return;
6886       } else
6887         if (gameMode == PlayFromGameFile) {
6888             /* Stop reading this game file */
6889             gameMode = EditGame;
6890             ModeHighlight();
6891         }
6892         currentMove = --forwardMostMove;
6893         DisplayMove(currentMove-1); /* before DisplayMoveError */
6894         SwitchClocks();
6895         DisplayBothClocks();
6896         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6897                 parseList[currentMove], cps->which);
6898         DisplayMoveError(buf1);
6899         DrawPosition(FALSE, boards[currentMove]);
6900
6901         /* [HGM] illegal-move claim should forfeit game when Xboard */
6902         /* only passes fully legal moves                            */
6903         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6904             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6905                                 "False illegal-move claim", GE_XBOARD );
6906         }
6907         return;
6908     }
6909     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6910         /* Program has a broken "time" command that
6911            outputs a string not ending in newline.
6912            Don't use it. */
6913         cps->sendTime = 0;
6914     }
6915     
6916     /*
6917      * If chess program startup fails, exit with an error message.
6918      * Attempts to recover here are futile.
6919      */
6920     if ((StrStr(message, "unknown host") != NULL)
6921         || (StrStr(message, "No remote directory") != NULL)
6922         || (StrStr(message, "not found") != NULL)
6923         || (StrStr(message, "No such file") != NULL)
6924         || (StrStr(message, "can't alloc") != NULL)
6925         || (StrStr(message, "Permission denied") != NULL)) {
6926
6927         cps->maybeThinking = FALSE;
6928         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6929                 cps->which, cps->program, cps->host, message);
6930         RemoveInputSource(cps->isr);
6931         DisplayFatalError(buf1, 0, 1);
6932         return;
6933     }
6934     
6935     /* 
6936      * Look for hint output
6937      */
6938     if (sscanf(message, "Hint: %s", buf1) == 1) {
6939         if (cps == &first && hintRequested) {
6940             hintRequested = FALSE;
6941             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6942                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6943                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6944                                     PosFlags(forwardMostMove),
6945                                     fromY, fromX, toY, toX, promoChar, buf1);
6946                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6947                 DisplayInformation(buf2);
6948             } else {
6949                 /* Hint move could not be parsed!? */
6950               snprintf(buf2, sizeof(buf2),
6951                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6952                         buf1, cps->which);
6953                 DisplayError(buf2, 0);
6954             }
6955         } else {
6956             strcpy(lastHint, buf1);
6957         }
6958         return;
6959     }
6960
6961     /*
6962      * Ignore other messages if game is not in progress
6963      */
6964     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6965         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6966
6967     /*
6968      * look for win, lose, draw, or draw offer
6969      */
6970     if (strncmp(message, "1-0", 3) == 0) {
6971         char *p, *q, *r = "";
6972         p = strchr(message, '{');
6973         if (p) {
6974             q = strchr(p, '}');
6975             if (q) {
6976                 *q = NULLCHAR;
6977                 r = p + 1;
6978             }
6979         }
6980         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6981         return;
6982     } else if (strncmp(message, "0-1", 3) == 0) {
6983         char *p, *q, *r = "";
6984         p = strchr(message, '{');
6985         if (p) {
6986             q = strchr(p, '}');
6987             if (q) {
6988                 *q = NULLCHAR;
6989                 r = p + 1;
6990             }
6991         }
6992         /* Kludge for Arasan 4.1 bug */
6993         if (strcmp(r, "Black resigns") == 0) {
6994             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6995             return;
6996         }
6997         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6998         return;
6999     } else if (strncmp(message, "1/2", 3) == 0) {
7000         char *p, *q, *r = "";
7001         p = strchr(message, '{');
7002         if (p) {
7003             q = strchr(p, '}');
7004             if (q) {
7005                 *q = NULLCHAR;
7006                 r = p + 1;
7007             }
7008         }
7009             
7010         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7011         return;
7012
7013     } else if (strncmp(message, "White resign", 12) == 0) {
7014         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7015         return;
7016     } else if (strncmp(message, "Black resign", 12) == 0) {
7017         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7018         return;
7019     } else if (strncmp(message, "White matches", 13) == 0 ||
7020                strncmp(message, "Black matches", 13) == 0   ) {
7021         /* [HGM] ignore GNUShogi noises */
7022         return;
7023     } else if (strncmp(message, "White", 5) == 0 &&
7024                message[5] != '(' &&
7025                StrStr(message, "Black") == NULL) {
7026         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7027         return;
7028     } else if (strncmp(message, "Black", 5) == 0 &&
7029                message[5] != '(') {
7030         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7031         return;
7032     } else if (strcmp(message, "resign") == 0 ||
7033                strcmp(message, "computer resigns") == 0) {
7034         switch (gameMode) {
7035           case MachinePlaysBlack:
7036           case IcsPlayingBlack:
7037             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7038             break;
7039           case MachinePlaysWhite:
7040           case IcsPlayingWhite:
7041             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7042             break;
7043           case TwoMachinesPlay:
7044             if (cps->twoMachinesColor[0] == 'w')
7045               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7046             else
7047               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7048             break;
7049           default:
7050             /* can't happen */
7051             break;
7052         }
7053         return;
7054     } else if (strncmp(message, "opponent mates", 14) == 0) {
7055         switch (gameMode) {
7056           case MachinePlaysBlack:
7057           case IcsPlayingBlack:
7058             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7059             break;
7060           case MachinePlaysWhite:
7061           case IcsPlayingWhite:
7062             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7063             break;
7064           case TwoMachinesPlay:
7065             if (cps->twoMachinesColor[0] == 'w')
7066               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7067             else
7068               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7069             break;
7070           default:
7071             /* can't happen */
7072             break;
7073         }
7074         return;
7075     } else if (strncmp(message, "computer mates", 14) == 0) {
7076         switch (gameMode) {
7077           case MachinePlaysBlack:
7078           case IcsPlayingBlack:
7079             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7080             break;
7081           case MachinePlaysWhite:
7082           case IcsPlayingWhite:
7083             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7084             break;
7085           case TwoMachinesPlay:
7086             if (cps->twoMachinesColor[0] == 'w')
7087               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7088             else
7089               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7090             break;
7091           default:
7092             /* can't happen */
7093             break;
7094         }
7095         return;
7096     } else if (strncmp(message, "checkmate", 9) == 0) {
7097         if (WhiteOnMove(forwardMostMove)) {
7098             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7099         } else {
7100             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7101         }
7102         return;
7103     } else if (strstr(message, "Draw") != NULL ||
7104                strstr(message, "game is a draw") != NULL) {
7105         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7106         return;
7107     } else if (strstr(message, "offer") != NULL &&
7108                strstr(message, "draw") != NULL) {
7109 #if ZIPPY
7110         if (appData.zippyPlay && first.initDone) {
7111             /* Relay offer to ICS */
7112             SendToICS(ics_prefix);
7113             SendToICS("draw\n");
7114         }
7115 #endif
7116         cps->offeredDraw = 2; /* valid until this engine moves twice */
7117         if (gameMode == TwoMachinesPlay) {
7118             if (cps->other->offeredDraw) {
7119                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7120             /* [HGM] in two-machine mode we delay relaying draw offer      */
7121             /* until after we also have move, to see if it is really claim */
7122             }
7123         } else if (gameMode == MachinePlaysWhite ||
7124                    gameMode == MachinePlaysBlack) {
7125           if (userOfferedDraw) {
7126             DisplayInformation(_("Machine accepts your draw offer"));
7127             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7128           } else {
7129             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7130           }
7131         }
7132     }
7133
7134     
7135     /*
7136      * Look for thinking output
7137      */
7138     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7139           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7140                                 ) {
7141         int plylev, mvleft, mvtot, curscore, time;
7142         char mvname[MOVE_LEN];
7143         u64 nodes; // [DM]
7144         char plyext;
7145         int ignore = FALSE;
7146         int prefixHint = FALSE;
7147         mvname[0] = NULLCHAR;
7148
7149         switch (gameMode) {
7150           case MachinePlaysBlack:
7151           case IcsPlayingBlack:
7152             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7153             break;
7154           case MachinePlaysWhite:
7155           case IcsPlayingWhite:
7156             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7157             break;
7158           case AnalyzeMode:
7159           case AnalyzeFile:
7160             break;
7161           case IcsObserving: /* [DM] icsEngineAnalyze */
7162             if (!appData.icsEngineAnalyze) ignore = TRUE;
7163             break;
7164           case TwoMachinesPlay:
7165             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7166                 ignore = TRUE;
7167             }
7168             break;
7169           default:
7170             ignore = TRUE;
7171             break;
7172         }
7173
7174         if (!ignore) {
7175             buf1[0] = NULLCHAR;
7176             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7177                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7178
7179                 if (plyext != ' ' && plyext != '\t') {
7180                     time *= 100;
7181                 }
7182
7183                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7184                 if( cps->scoreIsAbsolute && 
7185                     ( gameMode == MachinePlaysBlack ||
7186                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7187                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7188                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7189                      !WhiteOnMove(currentMove)
7190                     ) )
7191                 {
7192                     curscore = -curscore;
7193                 }
7194
7195
7196                 programStats.depth = plylev;
7197                 programStats.nodes = nodes;
7198                 programStats.time = time;
7199                 programStats.score = curscore;
7200                 programStats.got_only_move = 0;
7201
7202                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7203                         int ticklen;
7204
7205                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7206                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7207                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7208                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7209                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7210                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7211                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7212                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7213                 }
7214
7215                 /* Buffer overflow protection */
7216                 if (buf1[0] != NULLCHAR) {
7217                     if (strlen(buf1) >= sizeof(programStats.movelist)
7218                         && appData.debugMode) {
7219                         fprintf(debugFP,
7220                                 "PV is too long; using the first %u bytes.\n",
7221                                 (unsigned) sizeof(programStats.movelist) - 1);
7222                     }
7223
7224                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7225                 } else {
7226                     sprintf(programStats.movelist, " no PV\n");
7227                 }
7228
7229                 if (programStats.seen_stat) {
7230                     programStats.ok_to_send = 1;
7231                 }
7232
7233                 if (strchr(programStats.movelist, '(') != NULL) {
7234                     programStats.line_is_book = 1;
7235                     programStats.nr_moves = 0;
7236                     programStats.moves_left = 0;
7237                 } else {
7238                     programStats.line_is_book = 0;
7239                 }
7240
7241                 SendProgramStatsToFrontend( cps, &programStats );
7242
7243                 /* 
7244                     [AS] Protect the thinkOutput buffer from overflow... this
7245                     is only useful if buf1 hasn't overflowed first!
7246                 */
7247                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7248                         plylev, 
7249                         (gameMode == TwoMachinesPlay ?
7250                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7251                         ((double) curscore) / 100.0,
7252                         prefixHint ? lastHint : "",
7253                         prefixHint ? " " : "" );
7254
7255                 if( buf1[0] != NULLCHAR ) {
7256                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7257
7258                     if( strlen(buf1) > max_len ) {
7259                         if( appData.debugMode) {
7260                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7261                         }
7262                         buf1[max_len+1] = '\0';
7263                     }
7264
7265                     strcat( thinkOutput, buf1 );
7266                 }
7267
7268                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7269                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7270                     DisplayMove(currentMove - 1);
7271                 }
7272                 return;
7273
7274             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7275                 /* crafty (9.25+) says "(only move) <move>"
7276                  * if there is only 1 legal move
7277                  */
7278                 sscanf(p, "(only move) %s", buf1);
7279                 sprintf(thinkOutput, "%s (only move)", buf1);
7280                 sprintf(programStats.movelist, "%s (only move)", buf1);
7281                 programStats.depth = 1;
7282                 programStats.nr_moves = 1;
7283                 programStats.moves_left = 1;
7284                 programStats.nodes = 1;
7285                 programStats.time = 1;
7286                 programStats.got_only_move = 1;
7287
7288                 /* Not really, but we also use this member to
7289                    mean "line isn't going to change" (Crafty
7290                    isn't searching, so stats won't change) */
7291                 programStats.line_is_book = 1;
7292
7293                 SendProgramStatsToFrontend( cps, &programStats );
7294                 
7295                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7296                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7297                     DisplayMove(currentMove - 1);
7298                 }
7299                 return;
7300             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7301                               &time, &nodes, &plylev, &mvleft,
7302                               &mvtot, mvname) >= 5) {
7303                 /* The stat01: line is from Crafty (9.29+) in response
7304                    to the "." command */
7305                 programStats.seen_stat = 1;
7306                 cps->maybeThinking = TRUE;
7307
7308                 if (programStats.got_only_move || !appData.periodicUpdates)
7309                   return;
7310
7311                 programStats.depth = plylev;
7312                 programStats.time = time;
7313                 programStats.nodes = nodes;
7314                 programStats.moves_left = mvleft;
7315                 programStats.nr_moves = mvtot;
7316                 strcpy(programStats.move_name, mvname);
7317                 programStats.ok_to_send = 1;
7318                 programStats.movelist[0] = '\0';
7319
7320                 SendProgramStatsToFrontend( cps, &programStats );
7321
7322                 return;
7323
7324             } else if (strncmp(message,"++",2) == 0) {
7325                 /* Crafty 9.29+ outputs this */
7326                 programStats.got_fail = 2;
7327                 return;
7328
7329             } else if (strncmp(message,"--",2) == 0) {
7330                 /* Crafty 9.29+ outputs this */
7331                 programStats.got_fail = 1;
7332                 return;
7333
7334             } else if (thinkOutput[0] != NULLCHAR &&
7335                        strncmp(message, "    ", 4) == 0) {
7336                 unsigned message_len;
7337
7338                 p = message;
7339                 while (*p && *p == ' ') p++;
7340
7341                 message_len = strlen( p );
7342
7343                 /* [AS] Avoid buffer overflow */
7344                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7345                     strcat(thinkOutput, " ");
7346                     strcat(thinkOutput, p);
7347                 }
7348
7349                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7350                     strcat(programStats.movelist, " ");
7351                     strcat(programStats.movelist, p);
7352                 }
7353
7354                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7355                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7356                     DisplayMove(currentMove - 1);
7357                 }
7358                 return;
7359             }
7360         }
7361         else {
7362             buf1[0] = NULLCHAR;
7363
7364             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7365                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7366             {
7367                 ChessProgramStats cpstats;
7368
7369                 if (plyext != ' ' && plyext != '\t') {
7370                     time *= 100;
7371                 }
7372
7373                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7374                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7375                     curscore = -curscore;
7376                 }
7377
7378                 cpstats.depth = plylev;
7379                 cpstats.nodes = nodes;
7380                 cpstats.time = time;
7381                 cpstats.score = curscore;
7382                 cpstats.got_only_move = 0;
7383                 cpstats.movelist[0] = '\0';
7384
7385                 if (buf1[0] != NULLCHAR) {
7386                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7387                 }
7388
7389                 cpstats.ok_to_send = 0;
7390                 cpstats.line_is_book = 0;
7391                 cpstats.nr_moves = 0;
7392                 cpstats.moves_left = 0;
7393
7394                 SendProgramStatsToFrontend( cps, &cpstats );
7395             }
7396         }
7397     }
7398 }
7399
7400
7401 /* Parse a game score from the character string "game", and
7402    record it as the history of the current game.  The game
7403    score is NOT assumed to start from the standard position. 
7404    The display is not updated in any way.
7405    */
7406 void
7407 ParseGameHistory(game)
7408      char *game;
7409 {
7410     ChessMove moveType;
7411     int fromX, fromY, toX, toY, boardIndex;
7412     char promoChar;
7413     char *p, *q;
7414     char buf[MSG_SIZ];
7415
7416     if (appData.debugMode)
7417       fprintf(debugFP, "Parsing game history: %s\n", game);
7418
7419     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7420     gameInfo.site = StrSave(appData.icsHost);
7421     gameInfo.date = PGNDate();
7422     gameInfo.round = StrSave("-");
7423
7424     /* Parse out names of players */
7425     while (*game == ' ') game++;
7426     p = buf;
7427     while (*game != ' ') *p++ = *game++;
7428     *p = NULLCHAR;
7429     gameInfo.white = StrSave(buf);
7430     while (*game == ' ') game++;
7431     p = buf;
7432     while (*game != ' ' && *game != '\n') *p++ = *game++;
7433     *p = NULLCHAR;
7434     gameInfo.black = StrSave(buf);
7435
7436     /* Parse moves */
7437     boardIndex = blackPlaysFirst ? 1 : 0;
7438     yynewstr(game);
7439     for (;;) {
7440         yyboardindex = boardIndex;
7441         moveType = (ChessMove) yylex();
7442         switch (moveType) {
7443           case IllegalMove:             /* maybe suicide chess, etc. */
7444   if (appData.debugMode) {
7445     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7446     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7447     setbuf(debugFP, NULL);
7448   }
7449           case WhitePromotionChancellor:
7450           case BlackPromotionChancellor:
7451           case WhitePromotionArchbishop:
7452           case BlackPromotionArchbishop:
7453           case WhitePromotionQueen:
7454           case BlackPromotionQueen:
7455           case WhitePromotionRook:
7456           case BlackPromotionRook:
7457           case WhitePromotionBishop:
7458           case BlackPromotionBishop:
7459           case WhitePromotionKnight:
7460           case BlackPromotionKnight:
7461           case WhitePromotionKing:
7462           case BlackPromotionKing:
7463           case NormalMove:
7464           case WhiteCapturesEnPassant:
7465           case BlackCapturesEnPassant:
7466           case WhiteKingSideCastle:
7467           case WhiteQueenSideCastle:
7468           case BlackKingSideCastle:
7469           case BlackQueenSideCastle:
7470           case WhiteKingSideCastleWild:
7471           case WhiteQueenSideCastleWild:
7472           case BlackKingSideCastleWild:
7473           case BlackQueenSideCastleWild:
7474           /* PUSH Fabien */
7475           case WhiteHSideCastleFR:
7476           case WhiteASideCastleFR:
7477           case BlackHSideCastleFR:
7478           case BlackASideCastleFR:
7479           /* POP Fabien */
7480             fromX = currentMoveString[0] - AAA;
7481             fromY = currentMoveString[1] - ONE;
7482             toX = currentMoveString[2] - AAA;
7483             toY = currentMoveString[3] - ONE;
7484             promoChar = currentMoveString[4];
7485             break;
7486           case WhiteDrop:
7487           case BlackDrop:
7488             fromX = moveType == WhiteDrop ?
7489               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7490             (int) CharToPiece(ToLower(currentMoveString[0]));
7491             fromY = DROP_RANK;
7492             toX = currentMoveString[2] - AAA;
7493             toY = currentMoveString[3] - ONE;
7494             promoChar = NULLCHAR;
7495             break;
7496           case AmbiguousMove:
7497             /* bug? */
7498             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7499   if (appData.debugMode) {
7500     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7501     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7502     setbuf(debugFP, NULL);
7503   }
7504             DisplayError(buf, 0);
7505             return;
7506           case ImpossibleMove:
7507             /* bug? */
7508             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7509   if (appData.debugMode) {
7510     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7511     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7512     setbuf(debugFP, NULL);
7513   }
7514             DisplayError(buf, 0);
7515             return;
7516           case (ChessMove) 0:   /* end of file */
7517             if (boardIndex < backwardMostMove) {
7518                 /* Oops, gap.  How did that happen? */
7519                 DisplayError(_("Gap in move list"), 0);
7520                 return;
7521             }
7522             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7523             if (boardIndex > forwardMostMove) {
7524                 forwardMostMove = boardIndex;
7525             }
7526             return;
7527           case ElapsedTime:
7528             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7529                 strcat(parseList[boardIndex-1], " ");
7530                 strcat(parseList[boardIndex-1], yy_text);
7531             }
7532             continue;
7533           case Comment:
7534           case PGNTag:
7535           case NAG:
7536           default:
7537             /* ignore */
7538             continue;
7539           case WhiteWins:
7540           case BlackWins:
7541           case GameIsDrawn:
7542           case GameUnfinished:
7543             if (gameMode == IcsExamining) {
7544                 if (boardIndex < backwardMostMove) {
7545                     /* Oops, gap.  How did that happen? */
7546                     return;
7547                 }
7548                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7549                 return;
7550             }
7551             gameInfo.result = moveType;
7552             p = strchr(yy_text, '{');
7553             if (p == NULL) p = strchr(yy_text, '(');
7554             if (p == NULL) {
7555                 p = yy_text;
7556                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7557             } else {
7558                 q = strchr(p, *p == '{' ? '}' : ')');
7559                 if (q != NULL) *q = NULLCHAR;
7560                 p++;
7561             }
7562             gameInfo.resultDetails = StrSave(p);
7563             continue;
7564         }
7565         if (boardIndex >= forwardMostMove &&
7566             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7567             backwardMostMove = blackPlaysFirst ? 1 : 0;
7568             return;
7569         }
7570         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7571                                  fromY, fromX, toY, toX, promoChar,
7572                                  parseList[boardIndex]);
7573         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7574         /* currentMoveString is set as a side-effect of yylex */
7575         strcpy(moveList[boardIndex], currentMoveString);
7576         strcat(moveList[boardIndex], "\n");
7577         boardIndex++;
7578         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7579         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7580           case MT_NONE:
7581           case MT_STALEMATE:
7582           default:
7583             break;
7584           case MT_CHECK:
7585             if(gameInfo.variant != VariantShogi)
7586                 strcat(parseList[boardIndex - 1], "+");
7587             break;
7588           case MT_CHECKMATE:
7589           case MT_STAINMATE:
7590             strcat(parseList[boardIndex - 1], "#");
7591             break;
7592         }
7593     }
7594 }
7595
7596
7597 /* Apply a move to the given board  */
7598 void
7599 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7600      int fromX, fromY, toX, toY;
7601      int promoChar;
7602      Board board;
7603 {
7604   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7605   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7606
7607     /* [HGM] compute & store e.p. status and castling rights for new position */
7608     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7609     { int i;
7610
7611       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7612       oldEP = (signed char)board[EP_STATUS];
7613       board[EP_STATUS] = EP_NONE;
7614
7615       if( board[toY][toX] != EmptySquare ) 
7616            board[EP_STATUS] = EP_CAPTURE;  
7617
7618       if( board[fromY][fromX] == WhitePawn ) {
7619            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7620                board[EP_STATUS] = EP_PAWN_MOVE;
7621            if( toY-fromY==2) {
7622                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7623                         gameInfo.variant != VariantBerolina || toX < fromX)
7624                       board[EP_STATUS] = toX | berolina;
7625                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7626                         gameInfo.variant != VariantBerolina || toX > fromX) 
7627                       board[EP_STATUS] = toX;
7628            }
7629       } else 
7630       if( board[fromY][fromX] == BlackPawn ) {
7631            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7632                board[EP_STATUS] = EP_PAWN_MOVE; 
7633            if( toY-fromY== -2) {
7634                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7635                         gameInfo.variant != VariantBerolina || toX < fromX)
7636                       board[EP_STATUS] = toX | berolina;
7637                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7638                         gameInfo.variant != VariantBerolina || toX > fromX) 
7639                       board[EP_STATUS] = toX;
7640            }
7641        }
7642
7643        for(i=0; i<nrCastlingRights; i++) {
7644            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7645               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7646              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7647        }
7648
7649     }
7650
7651   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7652   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7653        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7654          
7655   if (fromX == toX && fromY == toY) return;
7656
7657   if (fromY == DROP_RANK) {
7658         /* must be first */
7659         piece = board[toY][toX] = (ChessSquare) fromX;
7660   } else {
7661      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7662      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7663      if(gameInfo.variant == VariantKnightmate)
7664          king += (int) WhiteUnicorn - (int) WhiteKing;
7665
7666     /* Code added by Tord: */
7667     /* FRC castling assumed when king captures friendly rook. */
7668     if (board[fromY][fromX] == WhiteKing &&
7669              board[toY][toX] == WhiteRook) {
7670       board[fromY][fromX] = EmptySquare;
7671       board[toY][toX] = EmptySquare;
7672       if(toX > fromX) {
7673         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7674       } else {
7675         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7676       }
7677     } else if (board[fromY][fromX] == BlackKing &&
7678                board[toY][toX] == BlackRook) {
7679       board[fromY][fromX] = EmptySquare;
7680       board[toY][toX] = EmptySquare;
7681       if(toX > fromX) {
7682         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7683       } else {
7684         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7685       }
7686     /* End of code added by Tord */
7687
7688     } else if (board[fromY][fromX] == king
7689         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7690         && toY == fromY && toX > fromX+1) {
7691         board[fromY][fromX] = EmptySquare;
7692         board[toY][toX] = king;
7693         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7694         board[fromY][BOARD_RGHT-1] = EmptySquare;
7695     } else if (board[fromY][fromX] == king
7696         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7697                && toY == fromY && toX < fromX-1) {
7698         board[fromY][fromX] = EmptySquare;
7699         board[toY][toX] = king;
7700         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7701         board[fromY][BOARD_LEFT] = EmptySquare;
7702     } else if (board[fromY][fromX] == WhitePawn
7703                && toY >= BOARD_HEIGHT-promoRank
7704                && gameInfo.variant != VariantXiangqi
7705                ) {
7706         /* white pawn promotion */
7707         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7708         if (board[toY][toX] == EmptySquare) {
7709             board[toY][toX] = WhiteQueen;
7710         }
7711         if(gameInfo.variant==VariantBughouse ||
7712            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7713             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7714         board[fromY][fromX] = EmptySquare;
7715     } else if ((fromY == BOARD_HEIGHT-4)
7716                && (toX != fromX)
7717                && gameInfo.variant != VariantXiangqi
7718                && gameInfo.variant != VariantBerolina
7719                && (board[fromY][fromX] == WhitePawn)
7720                && (board[toY][toX] == EmptySquare)) {
7721         board[fromY][fromX] = EmptySquare;
7722         board[toY][toX] = WhitePawn;
7723         captured = board[toY - 1][toX];
7724         board[toY - 1][toX] = EmptySquare;
7725     } else if ((fromY == BOARD_HEIGHT-4)
7726                && (toX == fromX)
7727                && gameInfo.variant == VariantBerolina
7728                && (board[fromY][fromX] == WhitePawn)
7729                && (board[toY][toX] == EmptySquare)) {
7730         board[fromY][fromX] = EmptySquare;
7731         board[toY][toX] = WhitePawn;
7732         if(oldEP & EP_BEROLIN_A) {
7733                 captured = board[fromY][fromX-1];
7734                 board[fromY][fromX-1] = EmptySquare;
7735         }else{  captured = board[fromY][fromX+1];
7736                 board[fromY][fromX+1] = EmptySquare;
7737         }
7738     } else if (board[fromY][fromX] == king
7739         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7740                && toY == fromY && toX > fromX+1) {
7741         board[fromY][fromX] = EmptySquare;
7742         board[toY][toX] = king;
7743         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7744         board[fromY][BOARD_RGHT-1] = EmptySquare;
7745     } else if (board[fromY][fromX] == king
7746         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7747                && toY == fromY && toX < fromX-1) {
7748         board[fromY][fromX] = EmptySquare;
7749         board[toY][toX] = king;
7750         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7751         board[fromY][BOARD_LEFT] = EmptySquare;
7752     } else if (fromY == 7 && fromX == 3
7753                && board[fromY][fromX] == BlackKing
7754                && toY == 7 && toX == 5) {
7755         board[fromY][fromX] = EmptySquare;
7756         board[toY][toX] = BlackKing;
7757         board[fromY][7] = EmptySquare;
7758         board[toY][4] = BlackRook;
7759     } else if (fromY == 7 && fromX == 3
7760                && board[fromY][fromX] == BlackKing
7761                && toY == 7 && toX == 1) {
7762         board[fromY][fromX] = EmptySquare;
7763         board[toY][toX] = BlackKing;
7764         board[fromY][0] = EmptySquare;
7765         board[toY][2] = BlackRook;
7766     } else if (board[fromY][fromX] == BlackPawn
7767                && toY < promoRank
7768                && gameInfo.variant != VariantXiangqi
7769                ) {
7770         /* black pawn promotion */
7771         board[toY][toX] = CharToPiece(ToLower(promoChar));
7772         if (board[toY][toX] == EmptySquare) {
7773             board[toY][toX] = BlackQueen;
7774         }
7775         if(gameInfo.variant==VariantBughouse ||
7776            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7777             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7778         board[fromY][fromX] = EmptySquare;
7779     } else if ((fromY == 3)
7780                && (toX != fromX)
7781                && gameInfo.variant != VariantXiangqi
7782                && gameInfo.variant != VariantBerolina
7783                && (board[fromY][fromX] == BlackPawn)
7784                && (board[toY][toX] == EmptySquare)) {
7785         board[fromY][fromX] = EmptySquare;
7786         board[toY][toX] = BlackPawn;
7787         captured = board[toY + 1][toX];
7788         board[toY + 1][toX] = EmptySquare;
7789     } else if ((fromY == 3)
7790                && (toX == fromX)
7791                && gameInfo.variant == VariantBerolina
7792                && (board[fromY][fromX] == BlackPawn)
7793                && (board[toY][toX] == EmptySquare)) {
7794         board[fromY][fromX] = EmptySquare;
7795         board[toY][toX] = BlackPawn;
7796         if(oldEP & EP_BEROLIN_A) {
7797                 captured = board[fromY][fromX-1];
7798                 board[fromY][fromX-1] = EmptySquare;
7799         }else{  captured = board[fromY][fromX+1];
7800                 board[fromY][fromX+1] = EmptySquare;
7801         }
7802     } else {
7803         board[toY][toX] = board[fromY][fromX];
7804         board[fromY][fromX] = EmptySquare;
7805     }
7806
7807     /* [HGM] now we promote for Shogi, if needed */
7808     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7809         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7810   }
7811
7812     if (gameInfo.holdingsWidth != 0) {
7813
7814       /* !!A lot more code needs to be written to support holdings  */
7815       /* [HGM] OK, so I have written it. Holdings are stored in the */
7816       /* penultimate board files, so they are automaticlly stored   */
7817       /* in the game history.                                       */
7818       if (fromY == DROP_RANK) {
7819         /* Delete from holdings, by decreasing count */
7820         /* and erasing image if necessary            */
7821         p = (int) fromX;
7822         if(p < (int) BlackPawn) { /* white drop */
7823              p -= (int)WhitePawn;
7824                  p = PieceToNumber((ChessSquare)p);
7825              if(p >= gameInfo.holdingsSize) p = 0;
7826              if(--board[p][BOARD_WIDTH-2] <= 0)
7827                   board[p][BOARD_WIDTH-1] = EmptySquare;
7828              if((int)board[p][BOARD_WIDTH-2] < 0)
7829                         board[p][BOARD_WIDTH-2] = 0;
7830         } else {                  /* black drop */
7831              p -= (int)BlackPawn;
7832                  p = PieceToNumber((ChessSquare)p);
7833              if(p >= gameInfo.holdingsSize) p = 0;
7834              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7835                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7836              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7837                         board[BOARD_HEIGHT-1-p][1] = 0;
7838         }
7839       }
7840       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7841           && gameInfo.variant != VariantBughouse        ) {
7842         /* [HGM] holdings: Add to holdings, if holdings exist */
7843         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7844                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7845                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7846         }
7847         p = (int) captured;
7848         if (p >= (int) BlackPawn) {
7849           p -= (int)BlackPawn;
7850           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7851                   /* in Shogi restore piece to its original  first */
7852                   captured = (ChessSquare) (DEMOTED captured);
7853                   p = DEMOTED p;
7854           }
7855           p = PieceToNumber((ChessSquare)p);
7856           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7857           board[p][BOARD_WIDTH-2]++;
7858           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7859         } else {
7860           p -= (int)WhitePawn;
7861           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7862                   captured = (ChessSquare) (DEMOTED captured);
7863                   p = DEMOTED p;
7864           }
7865           p = PieceToNumber((ChessSquare)p);
7866           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7867           board[BOARD_HEIGHT-1-p][1]++;
7868           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7869         }
7870       }
7871     } else if (gameInfo.variant == VariantAtomic) {
7872       if (captured != EmptySquare) {
7873         int y, x;
7874         for (y = toY-1; y <= toY+1; y++) {
7875           for (x = toX-1; x <= toX+1; x++) {
7876             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7877                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7878               board[y][x] = EmptySquare;
7879             }
7880           }
7881         }
7882         board[toY][toX] = EmptySquare;
7883       }
7884     }
7885     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7886         /* [HGM] Shogi promotions */
7887         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7888     }
7889
7890     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7891                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7892         // [HGM] superchess: take promotion piece out of holdings
7893         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7894         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7895             if(!--board[k][BOARD_WIDTH-2])
7896                 board[k][BOARD_WIDTH-1] = EmptySquare;
7897         } else {
7898             if(!--board[BOARD_HEIGHT-1-k][1])
7899                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7900         }
7901     }
7902
7903 }
7904
7905 /* Updates forwardMostMove */
7906 void
7907 MakeMove(fromX, fromY, toX, toY, promoChar)
7908      int fromX, fromY, toX, toY;
7909      int promoChar;
7910 {
7911 //    forwardMostMove++; // [HGM] bare: moved downstream
7912
7913     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7914         int timeLeft; static int lastLoadFlag=0; int king, piece;
7915         piece = boards[forwardMostMove][fromY][fromX];
7916         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7917         if(gameInfo.variant == VariantKnightmate)
7918             king += (int) WhiteUnicorn - (int) WhiteKing;
7919         if(forwardMostMove == 0) {
7920             if(blackPlaysFirst) 
7921                 fprintf(serverMoves, "%s;", second.tidy);
7922             fprintf(serverMoves, "%s;", first.tidy);
7923             if(!blackPlaysFirst) 
7924                 fprintf(serverMoves, "%s;", second.tidy);
7925         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7926         lastLoadFlag = loadFlag;
7927         // print base move
7928         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7929         // print castling suffix
7930         if( toY == fromY && piece == king ) {
7931             if(toX-fromX > 1)
7932                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7933             if(fromX-toX >1)
7934                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7935         }
7936         // e.p. suffix
7937         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7938              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7939              boards[forwardMostMove][toY][toX] == EmptySquare
7940              && fromX != toX )
7941                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7942         // promotion suffix
7943         if(promoChar != NULLCHAR)
7944                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7945         if(!loadFlag) {
7946             fprintf(serverMoves, "/%d/%d",
7947                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7948             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7949             else                      timeLeft = blackTimeRemaining/1000;
7950             fprintf(serverMoves, "/%d", timeLeft);
7951         }
7952         fflush(serverMoves);
7953     }
7954
7955     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7956       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7957                         0, 1);
7958       return;
7959     }
7960     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7961     if (commentList[forwardMostMove+1] != NULL) {
7962         free(commentList[forwardMostMove+1]);
7963         commentList[forwardMostMove+1] = NULL;
7964     }
7965     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7966     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7967     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7968     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7969     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7970     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7971     gameInfo.result = GameUnfinished;
7972     if (gameInfo.resultDetails != NULL) {
7973         free(gameInfo.resultDetails);
7974         gameInfo.resultDetails = NULL;
7975     }
7976     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7977                               moveList[forwardMostMove - 1]);
7978     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7979                              PosFlags(forwardMostMove - 1),
7980                              fromY, fromX, toY, toX, promoChar,
7981                              parseList[forwardMostMove - 1]);
7982     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7983       case MT_NONE:
7984       case MT_STALEMATE:
7985       default:
7986         break;
7987       case MT_CHECK:
7988         if(gameInfo.variant != VariantShogi)
7989             strcat(parseList[forwardMostMove - 1], "+");
7990         break;
7991       case MT_CHECKMATE:
7992       case MT_STAINMATE:
7993         strcat(parseList[forwardMostMove - 1], "#");
7994         break;
7995     }
7996     if (appData.debugMode) {
7997         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7998     }
7999
8000 }
8001
8002 /* Updates currentMove if not pausing */
8003 void
8004 ShowMove(fromX, fromY, toX, toY)
8005 {
8006     int instant = (gameMode == PlayFromGameFile) ?
8007         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8008     if(appData.noGUI) return;
8009     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8010         if (!instant) {
8011             if (forwardMostMove == currentMove + 1) {
8012                 AnimateMove(boards[forwardMostMove - 1],
8013                             fromX, fromY, toX, toY);
8014             }
8015             if (appData.highlightLastMove) {
8016                 SetHighlights(fromX, fromY, toX, toY);
8017             }
8018         }
8019         currentMove = forwardMostMove;
8020     }
8021
8022     if (instant) return;
8023
8024     DisplayMove(currentMove - 1);
8025     DrawPosition(FALSE, boards[currentMove]);
8026     DisplayBothClocks();
8027     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8028 }
8029
8030 void SendEgtPath(ChessProgramState *cps)
8031 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8032         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8033
8034         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8035
8036         while(*p) {
8037             char c, *q = name+1, *r, *s;
8038
8039             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8040             while(*p && *p != ',') *q++ = *p++;
8041             *q++ = ':'; *q = 0;
8042             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8043                 strcmp(name, ",nalimov:") == 0 ) {
8044                 // take nalimov path from the menu-changeable option first, if it is defined
8045                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8046                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8047             } else
8048             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8049                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8050                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8051                 s = r = StrStr(s, ":") + 1; // beginning of path info
8052                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8053                 c = *r; *r = 0;             // temporarily null-terminate path info
8054                     *--q = 0;               // strip of trailig ':' from name
8055                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8056                 *r = c;
8057                 SendToProgram(buf,cps);     // send egtbpath command for this format
8058             }
8059             if(*p == ',') p++; // read away comma to position for next format name
8060         }
8061 }
8062
8063 void
8064 InitChessProgram(cps, setup)
8065      ChessProgramState *cps;
8066      int setup; /* [HGM] needed to setup FRC opening position */
8067 {
8068     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8069     if (appData.noChessProgram) return;
8070     hintRequested = FALSE;
8071     bookRequested = FALSE;
8072
8073     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8074     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8075     if(cps->memSize) { /* [HGM] memory */
8076         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8077         SendToProgram(buf, cps);
8078     }
8079     SendEgtPath(cps); /* [HGM] EGT */
8080     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8081         sprintf(buf, "cores %d\n", appData.smpCores);
8082         SendToProgram(buf, cps);
8083     }
8084
8085     SendToProgram(cps->initString, cps);
8086     if (gameInfo.variant != VariantNormal &&
8087         gameInfo.variant != VariantLoadable
8088         /* [HGM] also send variant if board size non-standard */
8089         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8090                                             ) {
8091       char *v = VariantName(gameInfo.variant);
8092       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8093         /* [HGM] in protocol 1 we have to assume all variants valid */
8094         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8095         DisplayFatalError(buf, 0, 1);
8096         return;
8097       }
8098
8099       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8100       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8101       if( gameInfo.variant == VariantXiangqi )
8102            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8103       if( gameInfo.variant == VariantShogi )
8104            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8105       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8106            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8107       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8108                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8109            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8110       if( gameInfo.variant == VariantCourier )
8111            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8112       if( gameInfo.variant == VariantSuper )
8113            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8114       if( gameInfo.variant == VariantGreat )
8115            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8116
8117       if(overruled) {
8118            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8119                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8120            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8121            if(StrStr(cps->variants, b) == NULL) { 
8122                // specific sized variant not known, check if general sizing allowed
8123                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8124                    if(StrStr(cps->variants, "boardsize") == NULL) {
8125                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8126                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8127                        DisplayFatalError(buf, 0, 1);
8128                        return;
8129                    }
8130                    /* [HGM] here we really should compare with the maximum supported board size */
8131                }
8132            }
8133       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8134       sprintf(buf, "variant %s\n", b);
8135       SendToProgram(buf, cps);
8136     }
8137     currentlyInitializedVariant = gameInfo.variant;
8138
8139     /* [HGM] send opening position in FRC to first engine */
8140     if(setup) {
8141           SendToProgram("force\n", cps);
8142           SendBoard(cps, 0);
8143           /* engine is now in force mode! Set flag to wake it up after first move. */
8144           setboardSpoiledMachineBlack = 1;
8145     }
8146
8147     if (cps->sendICS) {
8148       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8149       SendToProgram(buf, cps);
8150     }
8151     cps->maybeThinking = FALSE;
8152     cps->offeredDraw = 0;
8153     if (!appData.icsActive) {
8154         SendTimeControl(cps, movesPerSession, timeControl,
8155                         timeIncrement, appData.searchDepth,
8156                         searchTime);
8157     }
8158     if (appData.showThinking 
8159         // [HGM] thinking: four options require thinking output to be sent
8160         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8161                                 ) {
8162         SendToProgram("post\n", cps);
8163     }
8164     SendToProgram("hard\n", cps);
8165     if (!appData.ponderNextMove) {
8166         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8167            it without being sure what state we are in first.  "hard"
8168            is not a toggle, so that one is OK.
8169          */
8170         SendToProgram("easy\n", cps);
8171     }
8172     if (cps->usePing) {
8173       sprintf(buf, "ping %d\n", ++cps->lastPing);
8174       SendToProgram(buf, cps);
8175     }
8176     cps->initDone = TRUE;
8177 }   
8178
8179
8180 void
8181 StartChessProgram(cps)
8182      ChessProgramState *cps;
8183 {
8184     char buf[MSG_SIZ];
8185     int err;
8186
8187     if (appData.noChessProgram) return;
8188     cps->initDone = FALSE;
8189
8190     if (strcmp(cps->host, "localhost") == 0) {
8191         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8192     } else if (*appData.remoteShell == NULLCHAR) {
8193         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8194     } else {
8195         if (*appData.remoteUser == NULLCHAR) {
8196           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8197                     cps->program);
8198         } else {
8199           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8200                     cps->host, appData.remoteUser, cps->program);
8201         }
8202         err = StartChildProcess(buf, "", &cps->pr);
8203     }
8204     
8205     if (err != 0) {
8206         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8207         DisplayFatalError(buf, err, 1);
8208         cps->pr = NoProc;
8209         cps->isr = NULL;
8210         return;
8211     }
8212     
8213     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8214     if (cps->protocolVersion > 1) {
8215       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8216       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8217       cps->comboCnt = 0;  //                and values of combo boxes
8218       SendToProgram(buf, cps);
8219     } else {
8220       SendToProgram("xboard\n", cps);
8221     }
8222 }
8223
8224
8225 void
8226 TwoMachinesEventIfReady P((void))
8227 {
8228   if (first.lastPing != first.lastPong) {
8229     DisplayMessage("", _("Waiting for first chess program"));
8230     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8231     return;
8232   }
8233   if (second.lastPing != second.lastPong) {
8234     DisplayMessage("", _("Waiting for second chess program"));
8235     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8236     return;
8237   }
8238   ThawUI();
8239   TwoMachinesEvent();
8240 }
8241
8242 void
8243 NextMatchGame P((void))
8244 {
8245     int index; /* [HGM] autoinc: step load index during match */
8246     Reset(FALSE, TRUE);
8247     if (*appData.loadGameFile != NULLCHAR) {
8248         index = appData.loadGameIndex;
8249         if(index < 0) { // [HGM] autoinc
8250             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8251             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8252         } 
8253         LoadGameFromFile(appData.loadGameFile,
8254                          index,
8255                          appData.loadGameFile, FALSE);
8256     } else if (*appData.loadPositionFile != NULLCHAR) {
8257         index = appData.loadPositionIndex;
8258         if(index < 0) { // [HGM] autoinc
8259             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8260             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8261         } 
8262         LoadPositionFromFile(appData.loadPositionFile,
8263                              index,
8264                              appData.loadPositionFile);
8265     }
8266     TwoMachinesEventIfReady();
8267 }
8268
8269 void UserAdjudicationEvent( int result )
8270 {
8271     ChessMove gameResult = GameIsDrawn;
8272
8273     if( result > 0 ) {
8274         gameResult = WhiteWins;
8275     }
8276     else if( result < 0 ) {
8277         gameResult = BlackWins;
8278     }
8279
8280     if( gameMode == TwoMachinesPlay ) {
8281         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8282     }
8283 }
8284
8285
8286 // [HGM] save: calculate checksum of game to make games easily identifiable
8287 int StringCheckSum(char *s)
8288 {
8289         int i = 0;
8290         if(s==NULL) return 0;
8291         while(*s) i = i*259 + *s++;
8292         return i;
8293 }
8294
8295 int GameCheckSum()
8296 {
8297         int i, sum=0;
8298         for(i=backwardMostMove; i<forwardMostMove; i++) {
8299                 sum += pvInfoList[i].depth;
8300                 sum += StringCheckSum(parseList[i]);
8301                 sum += StringCheckSum(commentList[i]);
8302                 sum *= 261;
8303         }
8304         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8305         return sum + StringCheckSum(commentList[i]);
8306 } // end of save patch
8307
8308 void
8309 GameEnds(result, resultDetails, whosays)
8310      ChessMove result;
8311      char *resultDetails;
8312      int whosays;
8313 {
8314     GameMode nextGameMode;
8315     int isIcsGame;
8316     char buf[MSG_SIZ];
8317
8318     if(endingGame) return; /* [HGM] crash: forbid recursion */
8319     endingGame = 1;
8320
8321     if (appData.debugMode) {
8322       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8323               result, resultDetails ? resultDetails : "(null)", whosays);
8324     }
8325
8326     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8327         /* If we are playing on ICS, the server decides when the
8328            game is over, but the engine can offer to draw, claim 
8329            a draw, or resign. 
8330          */
8331 #if ZIPPY
8332         if (appData.zippyPlay && first.initDone) {
8333             if (result == GameIsDrawn) {
8334                 /* In case draw still needs to be claimed */
8335                 SendToICS(ics_prefix);
8336                 SendToICS("draw\n");
8337             } else if (StrCaseStr(resultDetails, "resign")) {
8338                 SendToICS(ics_prefix);
8339                 SendToICS("resign\n");
8340             }
8341         }
8342 #endif
8343         endingGame = 0; /* [HGM] crash */
8344         return;
8345     }
8346
8347     /* If we're loading the game from a file, stop */
8348     if (whosays == GE_FILE) {
8349       (void) StopLoadGameTimer();
8350       gameFileFP = NULL;
8351     }
8352
8353     /* Cancel draw offers */
8354     first.offeredDraw = second.offeredDraw = 0;
8355
8356     /* If this is an ICS game, only ICS can really say it's done;
8357        if not, anyone can. */
8358     isIcsGame = (gameMode == IcsPlayingWhite || 
8359                  gameMode == IcsPlayingBlack || 
8360                  gameMode == IcsObserving    || 
8361                  gameMode == IcsExamining);
8362
8363     if (!isIcsGame || whosays == GE_ICS) {
8364         /* OK -- not an ICS game, or ICS said it was done */
8365         StopClocks();
8366         if (!isIcsGame && !appData.noChessProgram) 
8367           SetUserThinkingEnables();
8368     
8369         /* [HGM] if a machine claims the game end we verify this claim */
8370         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8371             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8372                 char claimer;
8373                 ChessMove trueResult = (ChessMove) -1;
8374
8375                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8376                                             first.twoMachinesColor[0] :
8377                                             second.twoMachinesColor[0] ;
8378
8379                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8380                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8381                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8382                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8383                 } else
8384                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8385                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8386                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8387                 } else
8388                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8389                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8390                 }
8391
8392                 // now verify win claims, but not in drop games, as we don't understand those yet
8393                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8394                                                  || gameInfo.variant == VariantGreat) &&
8395                     (result == WhiteWins && claimer == 'w' ||
8396                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8397                       if (appData.debugMode) {
8398                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8399                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8400                       }
8401                       if(result != trueResult) {
8402                               sprintf(buf, "False win claim: '%s'", resultDetails);
8403                               result = claimer == 'w' ? BlackWins : WhiteWins;
8404                               resultDetails = buf;
8405                       }
8406                 } else
8407                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8408                     && (forwardMostMove <= backwardMostMove ||
8409                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8410                         (claimer=='b')==(forwardMostMove&1))
8411                                                                                   ) {
8412                       /* [HGM] verify: draws that were not flagged are false claims */
8413                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8414                       result = claimer == 'w' ? BlackWins : WhiteWins;
8415                       resultDetails = buf;
8416                 }
8417                 /* (Claiming a loss is accepted no questions asked!) */
8418             }
8419             /* [HGM] bare: don't allow bare King to win */
8420             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8421                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8422                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8423                && result != GameIsDrawn)
8424             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8425                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8426                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8427                         if(p >= 0 && p <= (int)WhiteKing) k++;
8428                 }
8429                 if (appData.debugMode) {
8430                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8431                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8432                 }
8433                 if(k <= 1) {
8434                         result = GameIsDrawn;
8435                         sprintf(buf, "%s but bare king", resultDetails);
8436                         resultDetails = buf;
8437                 }
8438             }
8439         }
8440
8441
8442         if(serverMoves != NULL && !loadFlag) { char c = '=';
8443             if(result==WhiteWins) c = '+';
8444             if(result==BlackWins) c = '-';
8445             if(resultDetails != NULL)
8446                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8447         }
8448         if (resultDetails != NULL) {
8449             gameInfo.result = result;
8450             gameInfo.resultDetails = StrSave(resultDetails);
8451
8452             /* display last move only if game was not loaded from file */
8453             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8454                 DisplayMove(currentMove - 1);
8455     
8456             if (forwardMostMove != 0) {
8457                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8458                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8459                                                                 ) {
8460                     if (*appData.saveGameFile != NULLCHAR) {
8461                         SaveGameToFile(appData.saveGameFile, TRUE);
8462                     } else if (appData.autoSaveGames) {
8463                         AutoSaveGame();
8464                     }
8465                     if (*appData.savePositionFile != NULLCHAR) {
8466                         SavePositionToFile(appData.savePositionFile);
8467                     }
8468                 }
8469             }
8470
8471             /* Tell program how game ended in case it is learning */
8472             /* [HGM] Moved this to after saving the PGN, just in case */
8473             /* engine died and we got here through time loss. In that */
8474             /* case we will get a fatal error writing the pipe, which */
8475             /* would otherwise lose us the PGN.                       */
8476             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8477             /* output during GameEnds should never be fatal anymore   */
8478             if (gameMode == MachinePlaysWhite ||
8479                 gameMode == MachinePlaysBlack ||
8480                 gameMode == TwoMachinesPlay ||
8481                 gameMode == IcsPlayingWhite ||
8482                 gameMode == IcsPlayingBlack ||
8483                 gameMode == BeginningOfGame) {
8484                 char buf[MSG_SIZ];
8485                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8486                         resultDetails);
8487                 if (first.pr != NoProc) {
8488                     SendToProgram(buf, &first);
8489                 }
8490                 if (second.pr != NoProc &&
8491                     gameMode == TwoMachinesPlay) {
8492                     SendToProgram(buf, &second);
8493                 }
8494             }
8495         }
8496
8497         if (appData.icsActive) {
8498             if (appData.quietPlay &&
8499                 (gameMode == IcsPlayingWhite ||
8500                  gameMode == IcsPlayingBlack)) {
8501                 SendToICS(ics_prefix);
8502                 SendToICS("set shout 1\n");
8503             }
8504             nextGameMode = IcsIdle;
8505             ics_user_moved = FALSE;
8506             /* clean up premove.  It's ugly when the game has ended and the
8507              * premove highlights are still on the board.
8508              */
8509             if (gotPremove) {
8510               gotPremove = FALSE;
8511               ClearPremoveHighlights();
8512               DrawPosition(FALSE, boards[currentMove]);
8513             }
8514             if (whosays == GE_ICS) {
8515                 switch (result) {
8516                 case WhiteWins:
8517                     if (gameMode == IcsPlayingWhite)
8518                         PlayIcsWinSound();
8519                     else if(gameMode == IcsPlayingBlack)
8520                         PlayIcsLossSound();
8521                     break;
8522                 case BlackWins:
8523                     if (gameMode == IcsPlayingBlack)
8524                         PlayIcsWinSound();
8525                     else if(gameMode == IcsPlayingWhite)
8526                         PlayIcsLossSound();
8527                     break;
8528                 case GameIsDrawn:
8529                     PlayIcsDrawSound();
8530                     break;
8531                 default:
8532                     PlayIcsUnfinishedSound();
8533                 }
8534             }
8535         } else if (gameMode == EditGame ||
8536                    gameMode == PlayFromGameFile || 
8537                    gameMode == AnalyzeMode || 
8538                    gameMode == AnalyzeFile) {
8539             nextGameMode = gameMode;
8540         } else {
8541             nextGameMode = EndOfGame;
8542         }
8543         pausing = FALSE;
8544         ModeHighlight();
8545     } else {
8546         nextGameMode = gameMode;
8547     }
8548
8549     if (appData.noChessProgram) {
8550         gameMode = nextGameMode;
8551         ModeHighlight();
8552         endingGame = 0; /* [HGM] crash */
8553         return;
8554     }
8555
8556     if (first.reuse) {
8557         /* Put first chess program into idle state */
8558         if (first.pr != NoProc &&
8559             (gameMode == MachinePlaysWhite ||
8560              gameMode == MachinePlaysBlack ||
8561              gameMode == TwoMachinesPlay ||
8562              gameMode == IcsPlayingWhite ||
8563              gameMode == IcsPlayingBlack ||
8564              gameMode == BeginningOfGame)) {
8565             SendToProgram("force\n", &first);
8566             if (first.usePing) {
8567               char buf[MSG_SIZ];
8568               sprintf(buf, "ping %d\n", ++first.lastPing);
8569               SendToProgram(buf, &first);
8570             }
8571         }
8572     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8573         /* Kill off first chess program */
8574         if (first.isr != NULL)
8575           RemoveInputSource(first.isr);
8576         first.isr = NULL;
8577     
8578         if (first.pr != NoProc) {
8579             ExitAnalyzeMode();
8580             DoSleep( appData.delayBeforeQuit );
8581             SendToProgram("quit\n", &first);
8582             DoSleep( appData.delayAfterQuit );
8583             DestroyChildProcess(first.pr, first.useSigterm);
8584         }
8585         first.pr = NoProc;
8586     }
8587     if (second.reuse) {
8588         /* Put second chess program into idle state */
8589         if (second.pr != NoProc &&
8590             gameMode == TwoMachinesPlay) {
8591             SendToProgram("force\n", &second);
8592             if (second.usePing) {
8593               char buf[MSG_SIZ];
8594               sprintf(buf, "ping %d\n", ++second.lastPing);
8595               SendToProgram(buf, &second);
8596             }
8597         }
8598     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8599         /* Kill off second chess program */
8600         if (second.isr != NULL)
8601           RemoveInputSource(second.isr);
8602         second.isr = NULL;
8603     
8604         if (second.pr != NoProc) {
8605             DoSleep( appData.delayBeforeQuit );
8606             SendToProgram("quit\n", &second);
8607             DoSleep( appData.delayAfterQuit );
8608             DestroyChildProcess(second.pr, second.useSigterm);
8609         }
8610         second.pr = NoProc;
8611     }
8612
8613     if (matchMode && gameMode == TwoMachinesPlay) {
8614         switch (result) {
8615         case WhiteWins:
8616           if (first.twoMachinesColor[0] == 'w') {
8617             first.matchWins++;
8618           } else {
8619             second.matchWins++;
8620           }
8621           break;
8622         case BlackWins:
8623           if (first.twoMachinesColor[0] == 'b') {
8624             first.matchWins++;
8625           } else {
8626             second.matchWins++;
8627           }
8628           break;
8629         default:
8630           break;
8631         }
8632         if (matchGame < appData.matchGames) {
8633             char *tmp;
8634             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8635                 tmp = first.twoMachinesColor;
8636                 first.twoMachinesColor = second.twoMachinesColor;
8637                 second.twoMachinesColor = tmp;
8638             }
8639             gameMode = nextGameMode;
8640             matchGame++;
8641             if(appData.matchPause>10000 || appData.matchPause<10)
8642                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8643             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8644             endingGame = 0; /* [HGM] crash */
8645             return;
8646         } else {
8647             char buf[MSG_SIZ];
8648             gameMode = nextGameMode;
8649             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8650                     first.tidy, second.tidy,
8651                     first.matchWins, second.matchWins,
8652                     appData.matchGames - (first.matchWins + second.matchWins));
8653             DisplayFatalError(buf, 0, 0);
8654         }
8655     }
8656     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8657         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8658       ExitAnalyzeMode();
8659     gameMode = nextGameMode;
8660     ModeHighlight();
8661     endingGame = 0;  /* [HGM] crash */
8662 }
8663
8664 /* Assumes program was just initialized (initString sent).
8665    Leaves program in force mode. */
8666 void
8667 FeedMovesToProgram(cps, upto) 
8668      ChessProgramState *cps;
8669      int upto;
8670 {
8671     int i;
8672     
8673     if (appData.debugMode)
8674       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8675               startedFromSetupPosition ? "position and " : "",
8676               backwardMostMove, upto, cps->which);
8677     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8678         // [HGM] variantswitch: make engine aware of new variant
8679         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8680                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8681         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8682         SendToProgram(buf, cps);
8683         currentlyInitializedVariant = gameInfo.variant;
8684     }
8685     SendToProgram("force\n", cps);
8686     if (startedFromSetupPosition) {
8687         SendBoard(cps, backwardMostMove);
8688     if (appData.debugMode) {
8689         fprintf(debugFP, "feedMoves\n");
8690     }
8691     }
8692     for (i = backwardMostMove; i < upto; i++) {
8693         SendMoveToProgram(i, cps);
8694     }
8695 }
8696
8697
8698 void
8699 ResurrectChessProgram()
8700 {
8701      /* The chess program may have exited.
8702         If so, restart it and feed it all the moves made so far. */
8703
8704     if (appData.noChessProgram || first.pr != NoProc) return;
8705     
8706     StartChessProgram(&first);
8707     InitChessProgram(&first, FALSE);
8708     FeedMovesToProgram(&first, currentMove);
8709
8710     if (!first.sendTime) {
8711         /* can't tell gnuchess what its clock should read,
8712            so we bow to its notion. */
8713         ResetClocks();
8714         timeRemaining[0][currentMove] = whiteTimeRemaining;
8715         timeRemaining[1][currentMove] = blackTimeRemaining;
8716     }
8717
8718     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8719                 appData.icsEngineAnalyze) && first.analysisSupport) {
8720       SendToProgram("analyze\n", &first);
8721       first.analyzing = TRUE;
8722     }
8723 }
8724
8725 /*
8726  * Button procedures
8727  */
8728 void
8729 Reset(redraw, init)
8730      int redraw, init;
8731 {
8732     int i;
8733
8734     if (appData.debugMode) {
8735         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8736                 redraw, init, gameMode);
8737     }
8738     CleanupTail(); // [HGM] vari: delete any stored variations
8739     pausing = pauseExamInvalid = FALSE;
8740     startedFromSetupPosition = blackPlaysFirst = FALSE;
8741     firstMove = TRUE;
8742     whiteFlag = blackFlag = FALSE;
8743     userOfferedDraw = FALSE;
8744     hintRequested = bookRequested = FALSE;
8745     first.maybeThinking = FALSE;
8746     second.maybeThinking = FALSE;
8747     first.bookSuspend = FALSE; // [HGM] book
8748     second.bookSuspend = FALSE;
8749     thinkOutput[0] = NULLCHAR;
8750     lastHint[0] = NULLCHAR;
8751     ClearGameInfo(&gameInfo);
8752     gameInfo.variant = StringToVariant(appData.variant);
8753     ics_user_moved = ics_clock_paused = FALSE;
8754     ics_getting_history = H_FALSE;
8755     ics_gamenum = -1;
8756     white_holding[0] = black_holding[0] = NULLCHAR;
8757     ClearProgramStats();
8758     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8759     
8760     ResetFrontEnd();
8761     ClearHighlights();
8762     flipView = appData.flipView;
8763     ClearPremoveHighlights();
8764     gotPremove = FALSE;
8765     alarmSounded = FALSE;
8766
8767     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8768     if(appData.serverMovesName != NULL) {
8769         /* [HGM] prepare to make moves file for broadcasting */
8770         clock_t t = clock();
8771         if(serverMoves != NULL) fclose(serverMoves);
8772         serverMoves = fopen(appData.serverMovesName, "r");
8773         if(serverMoves != NULL) {
8774             fclose(serverMoves);
8775             /* delay 15 sec before overwriting, so all clients can see end */
8776             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8777         }
8778         serverMoves = fopen(appData.serverMovesName, "w");
8779     }
8780
8781     ExitAnalyzeMode();
8782     gameMode = BeginningOfGame;
8783     ModeHighlight();
8784     if(appData.icsActive) gameInfo.variant = VariantNormal;
8785     currentMove = forwardMostMove = backwardMostMove = 0;
8786     InitPosition(redraw);
8787     for (i = 0; i < MAX_MOVES; i++) {
8788         if (commentList[i] != NULL) {
8789             free(commentList[i]);
8790             commentList[i] = NULL;
8791         }
8792     }
8793     ResetClocks();
8794     timeRemaining[0][0] = whiteTimeRemaining;
8795     timeRemaining[1][0] = blackTimeRemaining;
8796     if (first.pr == NULL) {
8797         StartChessProgram(&first);
8798     }
8799     if (init) {
8800             InitChessProgram(&first, startedFromSetupPosition);
8801     }
8802     DisplayTitle("");
8803     DisplayMessage("", "");
8804     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8805     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8806 }
8807
8808 void
8809 AutoPlayGameLoop()
8810 {
8811     for (;;) {
8812         if (!AutoPlayOneMove())
8813           return;
8814         if (matchMode || appData.timeDelay == 0)
8815           continue;
8816         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8817           return;
8818         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8819         break;
8820     }
8821 }
8822
8823
8824 int
8825 AutoPlayOneMove()
8826 {
8827     int fromX, fromY, toX, toY;
8828
8829     if (appData.debugMode) {
8830       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8831     }
8832
8833     if (gameMode != PlayFromGameFile)
8834       return FALSE;
8835
8836     if (currentMove >= forwardMostMove) {
8837       gameMode = EditGame;
8838       ModeHighlight();
8839
8840       /* [AS] Clear current move marker at the end of a game */
8841       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8842
8843       return FALSE;
8844     }
8845     
8846     toX = moveList[currentMove][2] - AAA;
8847     toY = moveList[currentMove][3] - ONE;
8848
8849     if (moveList[currentMove][1] == '@') {
8850         if (appData.highlightLastMove) {
8851             SetHighlights(-1, -1, toX, toY);
8852         }
8853     } else {
8854         fromX = moveList[currentMove][0] - AAA;
8855         fromY = moveList[currentMove][1] - ONE;
8856
8857         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8858
8859         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8860
8861         if (appData.highlightLastMove) {
8862             SetHighlights(fromX, fromY, toX, toY);
8863         }
8864     }
8865     DisplayMove(currentMove);
8866     SendMoveToProgram(currentMove++, &first);
8867     DisplayBothClocks();
8868     DrawPosition(FALSE, boards[currentMove]);
8869     // [HGM] PV info: always display, routine tests if empty
8870     DisplayComment(currentMove - 1, commentList[currentMove]);
8871     return TRUE;
8872 }
8873
8874
8875 int
8876 LoadGameOneMove(readAhead)
8877      ChessMove readAhead;
8878 {
8879     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8880     char promoChar = NULLCHAR;
8881     ChessMove moveType;
8882     char move[MSG_SIZ];
8883     char *p, *q;
8884     
8885     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8886         gameMode != AnalyzeMode && gameMode != Training) {
8887         gameFileFP = NULL;
8888         return FALSE;
8889     }
8890     
8891     yyboardindex = forwardMostMove;
8892     if (readAhead != (ChessMove)0) {
8893       moveType = readAhead;
8894     } else {
8895       if (gameFileFP == NULL)
8896           return FALSE;
8897       moveType = (ChessMove) yylex();
8898     }
8899     
8900     done = FALSE;
8901     switch (moveType) {
8902       case Comment:
8903         if (appData.debugMode) 
8904           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8905         p = yy_text;
8906
8907         /* append the comment but don't display it */
8908         AppendComment(currentMove, p, FALSE);
8909         return TRUE;
8910
8911       case WhiteCapturesEnPassant:
8912       case BlackCapturesEnPassant:
8913       case WhitePromotionChancellor:
8914       case BlackPromotionChancellor:
8915       case WhitePromotionArchbishop:
8916       case BlackPromotionArchbishop:
8917       case WhitePromotionCentaur:
8918       case BlackPromotionCentaur:
8919       case WhitePromotionQueen:
8920       case BlackPromotionQueen:
8921       case WhitePromotionRook:
8922       case BlackPromotionRook:
8923       case WhitePromotionBishop:
8924       case BlackPromotionBishop:
8925       case WhitePromotionKnight:
8926       case BlackPromotionKnight:
8927       case WhitePromotionKing:
8928       case BlackPromotionKing:
8929       case NormalMove:
8930       case WhiteKingSideCastle:
8931       case WhiteQueenSideCastle:
8932       case BlackKingSideCastle:
8933       case BlackQueenSideCastle:
8934       case WhiteKingSideCastleWild:
8935       case WhiteQueenSideCastleWild:
8936       case BlackKingSideCastleWild:
8937       case BlackQueenSideCastleWild:
8938       /* PUSH Fabien */
8939       case WhiteHSideCastleFR:
8940       case WhiteASideCastleFR:
8941       case BlackHSideCastleFR:
8942       case BlackASideCastleFR:
8943       /* POP Fabien */
8944         if (appData.debugMode)
8945           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8946         fromX = currentMoveString[0] - AAA;
8947         fromY = currentMoveString[1] - ONE;
8948         toX = currentMoveString[2] - AAA;
8949         toY = currentMoveString[3] - ONE;
8950         promoChar = currentMoveString[4];
8951         break;
8952
8953       case WhiteDrop:
8954       case BlackDrop:
8955         if (appData.debugMode)
8956           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8957         fromX = moveType == WhiteDrop ?
8958           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8959         (int) CharToPiece(ToLower(currentMoveString[0]));
8960         fromY = DROP_RANK;
8961         toX = currentMoveString[2] - AAA;
8962         toY = currentMoveString[3] - ONE;
8963         break;
8964
8965       case WhiteWins:
8966       case BlackWins:
8967       case GameIsDrawn:
8968       case GameUnfinished:
8969         if (appData.debugMode)
8970           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8971         p = strchr(yy_text, '{');
8972         if (p == NULL) p = strchr(yy_text, '(');
8973         if (p == NULL) {
8974             p = yy_text;
8975             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8976         } else {
8977             q = strchr(p, *p == '{' ? '}' : ')');
8978             if (q != NULL) *q = NULLCHAR;
8979             p++;
8980         }
8981         GameEnds(moveType, p, GE_FILE);
8982         done = TRUE;
8983         if (cmailMsgLoaded) {
8984             ClearHighlights();
8985             flipView = WhiteOnMove(currentMove);
8986             if (moveType == GameUnfinished) flipView = !flipView;
8987             if (appData.debugMode)
8988               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8989         }
8990         break;
8991
8992       case (ChessMove) 0:       /* end of file */
8993         if (appData.debugMode)
8994           fprintf(debugFP, "Parser hit end of file\n");
8995         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8996           case MT_NONE:
8997           case MT_CHECK:
8998             break;
8999           case MT_CHECKMATE:
9000           case MT_STAINMATE:
9001             if (WhiteOnMove(currentMove)) {
9002                 GameEnds(BlackWins, "Black mates", GE_FILE);
9003             } else {
9004                 GameEnds(WhiteWins, "White mates", GE_FILE);
9005             }
9006             break;
9007           case MT_STALEMATE:
9008             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9009             break;
9010         }
9011         done = TRUE;
9012         break;
9013
9014       case MoveNumberOne:
9015         if (lastLoadGameStart == GNUChessGame) {
9016             /* GNUChessGames have numbers, but they aren't move numbers */
9017             if (appData.debugMode)
9018               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9019                       yy_text, (int) moveType);
9020             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9021         }
9022         /* else fall thru */
9023
9024       case XBoardGame:
9025       case GNUChessGame:
9026       case PGNTag:
9027         /* Reached start of next game in file */
9028         if (appData.debugMode)
9029           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9030         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9031           case MT_NONE:
9032           case MT_CHECK:
9033             break;
9034           case MT_CHECKMATE:
9035           case MT_STAINMATE:
9036             if (WhiteOnMove(currentMove)) {
9037                 GameEnds(BlackWins, "Black mates", GE_FILE);
9038             } else {
9039                 GameEnds(WhiteWins, "White mates", GE_FILE);
9040             }
9041             break;
9042           case MT_STALEMATE:
9043             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9044             break;
9045         }
9046         done = TRUE;
9047         break;
9048
9049       case PositionDiagram:     /* should not happen; ignore */
9050       case ElapsedTime:         /* ignore */
9051       case NAG:                 /* ignore */
9052         if (appData.debugMode)
9053           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9054                   yy_text, (int) moveType);
9055         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9056
9057       case IllegalMove:
9058         if (appData.testLegality) {
9059             if (appData.debugMode)
9060               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9061             sprintf(move, _("Illegal move: %d.%s%s"),
9062                     (forwardMostMove / 2) + 1,
9063                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9064             DisplayError(move, 0);
9065             done = TRUE;
9066         } else {
9067             if (appData.debugMode)
9068               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9069                       yy_text, currentMoveString);
9070             fromX = currentMoveString[0] - AAA;
9071             fromY = currentMoveString[1] - ONE;
9072             toX = currentMoveString[2] - AAA;
9073             toY = currentMoveString[3] - ONE;
9074             promoChar = currentMoveString[4];
9075         }
9076         break;
9077
9078       case AmbiguousMove:
9079         if (appData.debugMode)
9080           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9081         sprintf(move, _("Ambiguous move: %d.%s%s"),
9082                 (forwardMostMove / 2) + 1,
9083                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9084         DisplayError(move, 0);
9085         done = TRUE;
9086         break;
9087
9088       default:
9089       case ImpossibleMove:
9090         if (appData.debugMode)
9091           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9092         sprintf(move, _("Illegal move: %d.%s%s"),
9093                 (forwardMostMove / 2) + 1,
9094                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9095         DisplayError(move, 0);
9096         done = TRUE;
9097         break;
9098     }
9099
9100     if (done) {
9101         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9102             DrawPosition(FALSE, boards[currentMove]);
9103             DisplayBothClocks();
9104             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9105               DisplayComment(currentMove - 1, commentList[currentMove]);
9106         }
9107         (void) StopLoadGameTimer();
9108         gameFileFP = NULL;
9109         cmailOldMove = forwardMostMove;
9110         return FALSE;
9111     } else {
9112         /* currentMoveString is set as a side-effect of yylex */
9113         strcat(currentMoveString, "\n");
9114         strcpy(moveList[forwardMostMove], currentMoveString);
9115         
9116         thinkOutput[0] = NULLCHAR;
9117         MakeMove(fromX, fromY, toX, toY, promoChar);
9118         currentMove = forwardMostMove;
9119         return TRUE;
9120     }
9121 }
9122
9123 /* Load the nth game from the given file */
9124 int
9125 LoadGameFromFile(filename, n, title, useList)
9126      char *filename;
9127      int n;
9128      char *title;
9129      /*Boolean*/ int useList;
9130 {
9131     FILE *f;
9132     char buf[MSG_SIZ];
9133
9134     if (strcmp(filename, "-") == 0) {
9135         f = stdin;
9136         title = "stdin";
9137     } else {
9138         f = fopen(filename, "rb");
9139         if (f == NULL) {
9140           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9141             DisplayError(buf, errno);
9142             return FALSE;
9143         }
9144     }
9145     if (fseek(f, 0, 0) == -1) {
9146         /* f is not seekable; probably a pipe */
9147         useList = FALSE;
9148     }
9149     if (useList && n == 0) {
9150         int error = GameListBuild(f);
9151         if (error) {
9152             DisplayError(_("Cannot build game list"), error);
9153         } else if (!ListEmpty(&gameList) &&
9154                    ((ListGame *) gameList.tailPred)->number > 1) {
9155             GameListPopUp(f, title);
9156             return TRUE;
9157         }
9158         GameListDestroy();
9159         n = 1;
9160     }
9161     if (n == 0) n = 1;
9162     return LoadGame(f, n, title, FALSE);
9163 }
9164
9165
9166 void
9167 MakeRegisteredMove()
9168 {
9169     int fromX, fromY, toX, toY;
9170     char promoChar;
9171     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9172         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9173           case CMAIL_MOVE:
9174           case CMAIL_DRAW:
9175             if (appData.debugMode)
9176               fprintf(debugFP, "Restoring %s for game %d\n",
9177                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9178     
9179             thinkOutput[0] = NULLCHAR;
9180             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9181             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9182             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9183             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9184             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9185             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9186             MakeMove(fromX, fromY, toX, toY, promoChar);
9187             ShowMove(fromX, fromY, toX, toY);
9188               
9189             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9190               case MT_NONE:
9191               case MT_CHECK:
9192                 break;
9193                 
9194               case MT_CHECKMATE:
9195               case MT_STAINMATE:
9196                 if (WhiteOnMove(currentMove)) {
9197                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9198                 } else {
9199                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9200                 }
9201                 break;
9202                 
9203               case MT_STALEMATE:
9204                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9205                 break;
9206             }
9207
9208             break;
9209             
9210           case CMAIL_RESIGN:
9211             if (WhiteOnMove(currentMove)) {
9212                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9213             } else {
9214                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9215             }
9216             break;
9217             
9218           case CMAIL_ACCEPT:
9219             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9220             break;
9221               
9222           default:
9223             break;
9224         }
9225     }
9226
9227     return;
9228 }
9229
9230 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9231 int
9232 CmailLoadGame(f, gameNumber, title, useList)
9233      FILE *f;
9234      int gameNumber;
9235      char *title;
9236      int useList;
9237 {
9238     int retVal;
9239
9240     if (gameNumber > nCmailGames) {
9241         DisplayError(_("No more games in this message"), 0);
9242         return FALSE;
9243     }
9244     if (f == lastLoadGameFP) {
9245         int offset = gameNumber - lastLoadGameNumber;
9246         if (offset == 0) {
9247             cmailMsg[0] = NULLCHAR;
9248             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9249                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9250                 nCmailMovesRegistered--;
9251             }
9252             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9253             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9254                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9255             }
9256         } else {
9257             if (! RegisterMove()) return FALSE;
9258         }
9259     }
9260
9261     retVal = LoadGame(f, gameNumber, title, useList);
9262
9263     /* Make move registered during previous look at this game, if any */
9264     MakeRegisteredMove();
9265
9266     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9267         commentList[currentMove]
9268           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9269         DisplayComment(currentMove - 1, commentList[currentMove]);
9270     }
9271
9272     return retVal;
9273 }
9274
9275 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9276 int
9277 ReloadGame(offset)
9278      int offset;
9279 {
9280     int gameNumber = lastLoadGameNumber + offset;
9281     if (lastLoadGameFP == NULL) {
9282         DisplayError(_("No game has been loaded yet"), 0);
9283         return FALSE;
9284     }
9285     if (gameNumber <= 0) {
9286         DisplayError(_("Can't back up any further"), 0);
9287         return FALSE;
9288     }
9289     if (cmailMsgLoaded) {
9290         return CmailLoadGame(lastLoadGameFP, gameNumber,
9291                              lastLoadGameTitle, lastLoadGameUseList);
9292     } else {
9293         return LoadGame(lastLoadGameFP, gameNumber,
9294                         lastLoadGameTitle, lastLoadGameUseList);
9295     }
9296 }
9297
9298
9299
9300 /* Load the nth game from open file f */
9301 int
9302 LoadGame(f, gameNumber, title, useList)
9303      FILE *f;
9304      int gameNumber;
9305      char *title;
9306      int useList;
9307 {
9308     ChessMove cm;
9309     char buf[MSG_SIZ];
9310     int gn = gameNumber;
9311     ListGame *lg = NULL;
9312     int numPGNTags = 0;
9313     int err;
9314     GameMode oldGameMode;
9315     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9316
9317     if (appData.debugMode) 
9318         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9319
9320     if (gameMode == Training )
9321         SetTrainingModeOff();
9322
9323     oldGameMode = gameMode;
9324     if (gameMode != BeginningOfGame) {
9325       Reset(FALSE, TRUE);
9326     }
9327
9328     gameFileFP = f;
9329     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9330         fclose(lastLoadGameFP);
9331     }
9332
9333     if (useList) {
9334         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9335         
9336         if (lg) {
9337             fseek(f, lg->offset, 0);
9338             GameListHighlight(gameNumber);
9339             gn = 1;
9340         }
9341         else {
9342             DisplayError(_("Game number out of range"), 0);
9343             return FALSE;
9344         }
9345     } else {
9346         GameListDestroy();
9347         if (fseek(f, 0, 0) == -1) {
9348             if (f == lastLoadGameFP ?
9349                 gameNumber == lastLoadGameNumber + 1 :
9350                 gameNumber == 1) {
9351                 gn = 1;
9352             } else {
9353                 DisplayError(_("Can't seek on game file"), 0);
9354                 return FALSE;
9355             }
9356         }
9357     }
9358     lastLoadGameFP = f;
9359     lastLoadGameNumber = gameNumber;
9360     strcpy(lastLoadGameTitle, title);
9361     lastLoadGameUseList = useList;
9362
9363     yynewfile(f);
9364
9365     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9366       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9367                 lg->gameInfo.black);
9368             DisplayTitle(buf);
9369     } else if (*title != NULLCHAR) {
9370         if (gameNumber > 1) {
9371             sprintf(buf, "%s %d", title, gameNumber);
9372             DisplayTitle(buf);
9373         } else {
9374             DisplayTitle(title);
9375         }
9376     }
9377
9378     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9379         gameMode = PlayFromGameFile;
9380         ModeHighlight();
9381     }
9382
9383     currentMove = forwardMostMove = backwardMostMove = 0;
9384     CopyBoard(boards[0], initialPosition);
9385     StopClocks();
9386
9387     /*
9388      * Skip the first gn-1 games in the file.
9389      * Also skip over anything that precedes an identifiable 
9390      * start of game marker, to avoid being confused by 
9391      * garbage at the start of the file.  Currently 
9392      * recognized start of game markers are the move number "1",
9393      * the pattern "gnuchess .* game", the pattern
9394      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9395      * A game that starts with one of the latter two patterns
9396      * will also have a move number 1, possibly
9397      * following a position diagram.
9398      * 5-4-02: Let's try being more lenient and allowing a game to
9399      * start with an unnumbered move.  Does that break anything?
9400      */
9401     cm = lastLoadGameStart = (ChessMove) 0;
9402     while (gn > 0) {
9403         yyboardindex = forwardMostMove;
9404         cm = (ChessMove) yylex();
9405         switch (cm) {
9406           case (ChessMove) 0:
9407             if (cmailMsgLoaded) {
9408                 nCmailGames = CMAIL_MAX_GAMES - gn;
9409             } else {
9410                 Reset(TRUE, TRUE);
9411                 DisplayError(_("Game not found in file"), 0);
9412             }
9413             return FALSE;
9414
9415           case GNUChessGame:
9416           case XBoardGame:
9417             gn--;
9418             lastLoadGameStart = cm;
9419             break;
9420             
9421           case MoveNumberOne:
9422             switch (lastLoadGameStart) {
9423               case GNUChessGame:
9424               case XBoardGame:
9425               case PGNTag:
9426                 break;
9427               case MoveNumberOne:
9428               case (ChessMove) 0:
9429                 gn--;           /* count this game */
9430                 lastLoadGameStart = cm;
9431                 break;
9432               default:
9433                 /* impossible */
9434                 break;
9435             }
9436             break;
9437
9438           case PGNTag:
9439             switch (lastLoadGameStart) {
9440               case GNUChessGame:
9441               case PGNTag:
9442               case MoveNumberOne:
9443               case (ChessMove) 0:
9444                 gn--;           /* count this game */
9445                 lastLoadGameStart = cm;
9446                 break;
9447               case XBoardGame:
9448                 lastLoadGameStart = cm; /* game counted already */
9449                 break;
9450               default:
9451                 /* impossible */
9452                 break;
9453             }
9454             if (gn > 0) {
9455                 do {
9456                     yyboardindex = forwardMostMove;
9457                     cm = (ChessMove) yylex();
9458                 } while (cm == PGNTag || cm == Comment);
9459             }
9460             break;
9461
9462           case WhiteWins:
9463           case BlackWins:
9464           case GameIsDrawn:
9465             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9466                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9467                     != CMAIL_OLD_RESULT) {
9468                     nCmailResults ++ ;
9469                     cmailResult[  CMAIL_MAX_GAMES
9470                                 - gn - 1] = CMAIL_OLD_RESULT;
9471                 }
9472             }
9473             break;
9474
9475           case NormalMove:
9476             /* Only a NormalMove can be at the start of a game
9477              * without a position diagram. */
9478             if (lastLoadGameStart == (ChessMove) 0) {
9479               gn--;
9480               lastLoadGameStart = MoveNumberOne;
9481             }
9482             break;
9483
9484           default:
9485             break;
9486         }
9487     }
9488     
9489     if (appData.debugMode)
9490       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9491
9492     if (cm == XBoardGame) {
9493         /* Skip any header junk before position diagram and/or move 1 */
9494         for (;;) {
9495             yyboardindex = forwardMostMove;
9496             cm = (ChessMove) yylex();
9497
9498             if (cm == (ChessMove) 0 ||
9499                 cm == GNUChessGame || cm == XBoardGame) {
9500                 /* Empty game; pretend end-of-file and handle later */
9501                 cm = (ChessMove) 0;
9502                 break;
9503             }
9504
9505             if (cm == MoveNumberOne || cm == PositionDiagram ||
9506                 cm == PGNTag || cm == Comment)
9507               break;
9508         }
9509     } else if (cm == GNUChessGame) {
9510         if (gameInfo.event != NULL) {
9511             free(gameInfo.event);
9512         }
9513         gameInfo.event = StrSave(yy_text);
9514     }   
9515
9516     startedFromSetupPosition = FALSE;
9517     while (cm == PGNTag) {
9518         if (appData.debugMode) 
9519           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9520         err = ParsePGNTag(yy_text, &gameInfo);
9521         if (!err) numPGNTags++;
9522
9523         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9524         if(gameInfo.variant != oldVariant) {
9525             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9526             InitPosition(TRUE);
9527             oldVariant = gameInfo.variant;
9528             if (appData.debugMode) 
9529               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9530         }
9531
9532
9533         if (gameInfo.fen != NULL) {
9534           Board initial_position;
9535           startedFromSetupPosition = TRUE;
9536           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9537             Reset(TRUE, TRUE);
9538             DisplayError(_("Bad FEN position in file"), 0);
9539             return FALSE;
9540           }
9541           CopyBoard(boards[0], initial_position);
9542           if (blackPlaysFirst) {
9543             currentMove = forwardMostMove = backwardMostMove = 1;
9544             CopyBoard(boards[1], initial_position);
9545             strcpy(moveList[0], "");
9546             strcpy(parseList[0], "");
9547             timeRemaining[0][1] = whiteTimeRemaining;
9548             timeRemaining[1][1] = blackTimeRemaining;
9549             if (commentList[0] != NULL) {
9550               commentList[1] = commentList[0];
9551               commentList[0] = NULL;
9552             }
9553           } else {
9554             currentMove = forwardMostMove = backwardMostMove = 0;
9555           }
9556           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9557           {   int i;
9558               initialRulePlies = FENrulePlies;
9559               for( i=0; i< nrCastlingRights; i++ )
9560                   initialRights[i] = initial_position[CASTLING][i];
9561           }
9562           yyboardindex = forwardMostMove;
9563           free(gameInfo.fen);
9564           gameInfo.fen = NULL;
9565         }
9566
9567         yyboardindex = forwardMostMove;
9568         cm = (ChessMove) yylex();
9569
9570         /* Handle comments interspersed among the tags */
9571         while (cm == Comment) {
9572             char *p;
9573             if (appData.debugMode) 
9574               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9575             p = yy_text;
9576             AppendComment(currentMove, p, FALSE);
9577             yyboardindex = forwardMostMove;
9578             cm = (ChessMove) yylex();
9579         }
9580     }
9581
9582     /* don't rely on existence of Event tag since if game was
9583      * pasted from clipboard the Event tag may not exist
9584      */
9585     if (numPGNTags > 0){
9586         char *tags;
9587         if (gameInfo.variant == VariantNormal) {
9588           gameInfo.variant = StringToVariant(gameInfo.event);
9589         }
9590         if (!matchMode) {
9591           if( appData.autoDisplayTags ) {
9592             tags = PGNTags(&gameInfo);
9593             TagsPopUp(tags, CmailMsg());
9594             free(tags);
9595           }
9596         }
9597     } else {
9598         /* Make something up, but don't display it now */
9599         SetGameInfo();
9600         TagsPopDown();
9601     }
9602
9603     if (cm == PositionDiagram) {
9604         int i, j;
9605         char *p;
9606         Board initial_position;
9607
9608         if (appData.debugMode)
9609           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9610
9611         if (!startedFromSetupPosition) {
9612             p = yy_text;
9613             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9614               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9615                 switch (*p) {
9616                   case '[':
9617                   case '-':
9618                   case ' ':
9619                   case '\t':
9620                   case '\n':
9621                   case '\r':
9622                     break;
9623                   default:
9624                     initial_position[i][j++] = CharToPiece(*p);
9625                     break;
9626                 }
9627             while (*p == ' ' || *p == '\t' ||
9628                    *p == '\n' || *p == '\r') p++;
9629         
9630             if (strncmp(p, "black", strlen("black"))==0)
9631               blackPlaysFirst = TRUE;
9632             else
9633               blackPlaysFirst = FALSE;
9634             startedFromSetupPosition = TRUE;
9635         
9636             CopyBoard(boards[0], initial_position);
9637             if (blackPlaysFirst) {
9638                 currentMove = forwardMostMove = backwardMostMove = 1;
9639                 CopyBoard(boards[1], initial_position);
9640                 strcpy(moveList[0], "");
9641                 strcpy(parseList[0], "");
9642                 timeRemaining[0][1] = whiteTimeRemaining;
9643                 timeRemaining[1][1] = blackTimeRemaining;
9644                 if (commentList[0] != NULL) {
9645                     commentList[1] = commentList[0];
9646                     commentList[0] = NULL;
9647                 }
9648             } else {
9649                 currentMove = forwardMostMove = backwardMostMove = 0;
9650             }
9651         }
9652         yyboardindex = forwardMostMove;
9653         cm = (ChessMove) yylex();
9654     }
9655
9656     if (first.pr == NoProc) {
9657         StartChessProgram(&first);
9658     }
9659     InitChessProgram(&first, FALSE);
9660     SendToProgram("force\n", &first);
9661     if (startedFromSetupPosition) {
9662         SendBoard(&first, forwardMostMove);
9663     if (appData.debugMode) {
9664         fprintf(debugFP, "Load Game\n");
9665     }
9666         DisplayBothClocks();
9667     }      
9668
9669     /* [HGM] server: flag to write setup moves in broadcast file as one */
9670     loadFlag = appData.suppressLoadMoves;
9671
9672     while (cm == Comment) {
9673         char *p;
9674         if (appData.debugMode) 
9675           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9676         p = yy_text;
9677         AppendComment(currentMove, p, FALSE);
9678         yyboardindex = forwardMostMove;
9679         cm = (ChessMove) yylex();
9680     }
9681
9682     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9683         cm == WhiteWins || cm == BlackWins ||
9684         cm == GameIsDrawn || cm == GameUnfinished) {
9685         DisplayMessage("", _("No moves in game"));
9686         if (cmailMsgLoaded) {
9687             if (appData.debugMode)
9688               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9689             ClearHighlights();
9690             flipView = FALSE;
9691         }
9692         DrawPosition(FALSE, boards[currentMove]);
9693         DisplayBothClocks();
9694         gameMode = EditGame;
9695         ModeHighlight();
9696         gameFileFP = NULL;
9697         cmailOldMove = 0;
9698         return TRUE;
9699     }
9700
9701     // [HGM] PV info: routine tests if comment empty
9702     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9703         DisplayComment(currentMove - 1, commentList[currentMove]);
9704     }
9705     if (!matchMode && appData.timeDelay != 0) 
9706       DrawPosition(FALSE, boards[currentMove]);
9707
9708     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9709       programStats.ok_to_send = 1;
9710     }
9711
9712     /* if the first token after the PGN tags is a move
9713      * and not move number 1, retrieve it from the parser 
9714      */
9715     if (cm != MoveNumberOne)
9716         LoadGameOneMove(cm);
9717
9718     /* load the remaining moves from the file */
9719     while (LoadGameOneMove((ChessMove)0)) {
9720       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9721       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9722     }
9723
9724     /* rewind to the start of the game */
9725     currentMove = backwardMostMove;
9726
9727     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9728
9729     if (oldGameMode == AnalyzeFile ||
9730         oldGameMode == AnalyzeMode) {
9731       AnalyzeFileEvent();
9732     }
9733
9734     if (matchMode || appData.timeDelay == 0) {
9735       ToEndEvent();
9736       gameMode = EditGame;
9737       ModeHighlight();
9738     } else if (appData.timeDelay > 0) {
9739       AutoPlayGameLoop();
9740     }
9741
9742     if (appData.debugMode) 
9743         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9744
9745     loadFlag = 0; /* [HGM] true game starts */
9746     return TRUE;
9747 }
9748
9749 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9750 int
9751 ReloadPosition(offset)
9752      int offset;
9753 {
9754     int positionNumber = lastLoadPositionNumber + offset;
9755     if (lastLoadPositionFP == NULL) {
9756         DisplayError(_("No position has been loaded yet"), 0);
9757         return FALSE;
9758     }
9759     if (positionNumber <= 0) {
9760         DisplayError(_("Can't back up any further"), 0);
9761         return FALSE;
9762     }
9763     return LoadPosition(lastLoadPositionFP, positionNumber,
9764                         lastLoadPositionTitle);
9765 }
9766
9767 /* Load the nth position from the given file */
9768 int
9769 LoadPositionFromFile(filename, n, title)
9770      char *filename;
9771      int n;
9772      char *title;
9773 {
9774     FILE *f;
9775     char buf[MSG_SIZ];
9776
9777     if (strcmp(filename, "-") == 0) {
9778         return LoadPosition(stdin, n, "stdin");
9779     } else {
9780         f = fopen(filename, "rb");
9781         if (f == NULL) {
9782             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9783             DisplayError(buf, errno);
9784             return FALSE;
9785         } else {
9786             return LoadPosition(f, n, title);
9787         }
9788     }
9789 }
9790
9791 /* Load the nth position from the given open file, and close it */
9792 int
9793 LoadPosition(f, positionNumber, title)
9794      FILE *f;
9795      int positionNumber;
9796      char *title;
9797 {
9798     char *p, line[MSG_SIZ];
9799     Board initial_position;
9800     int i, j, fenMode, pn;
9801     
9802     if (gameMode == Training )
9803         SetTrainingModeOff();
9804
9805     if (gameMode != BeginningOfGame) {
9806         Reset(FALSE, TRUE);
9807     }
9808     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9809         fclose(lastLoadPositionFP);
9810     }
9811     if (positionNumber == 0) positionNumber = 1;
9812     lastLoadPositionFP = f;
9813     lastLoadPositionNumber = positionNumber;
9814     strcpy(lastLoadPositionTitle, title);
9815     if (first.pr == NoProc) {
9816       StartChessProgram(&first);
9817       InitChessProgram(&first, FALSE);
9818     }    
9819     pn = positionNumber;
9820     if (positionNumber < 0) {
9821         /* Negative position number means to seek to that byte offset */
9822         if (fseek(f, -positionNumber, 0) == -1) {
9823             DisplayError(_("Can't seek on position file"), 0);
9824             return FALSE;
9825         };
9826         pn = 1;
9827     } else {
9828         if (fseek(f, 0, 0) == -1) {
9829             if (f == lastLoadPositionFP ?
9830                 positionNumber == lastLoadPositionNumber + 1 :
9831                 positionNumber == 1) {
9832                 pn = 1;
9833             } else {
9834                 DisplayError(_("Can't seek on position file"), 0);
9835                 return FALSE;
9836             }
9837         }
9838     }
9839     /* See if this file is FEN or old-style xboard */
9840     if (fgets(line, MSG_SIZ, f) == NULL) {
9841         DisplayError(_("Position not found in file"), 0);
9842         return FALSE;
9843     }
9844     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9845     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9846
9847     if (pn >= 2) {
9848         if (fenMode || line[0] == '#') pn--;
9849         while (pn > 0) {
9850             /* skip positions before number pn */
9851             if (fgets(line, MSG_SIZ, f) == NULL) {
9852                 Reset(TRUE, TRUE);
9853                 DisplayError(_("Position not found in file"), 0);
9854                 return FALSE;
9855             }
9856             if (fenMode || line[0] == '#') pn--;
9857         }
9858     }
9859
9860     if (fenMode) {
9861         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9862             DisplayError(_("Bad FEN position in file"), 0);
9863             return FALSE;
9864         }
9865     } else {
9866         (void) fgets(line, MSG_SIZ, f);
9867         (void) fgets(line, MSG_SIZ, f);
9868     
9869         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9870             (void) fgets(line, MSG_SIZ, f);
9871             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9872                 if (*p == ' ')
9873                   continue;
9874                 initial_position[i][j++] = CharToPiece(*p);
9875             }
9876         }
9877     
9878         blackPlaysFirst = FALSE;
9879         if (!feof(f)) {
9880             (void) fgets(line, MSG_SIZ, f);
9881             if (strncmp(line, "black", strlen("black"))==0)
9882               blackPlaysFirst = TRUE;
9883         }
9884     }
9885     startedFromSetupPosition = TRUE;
9886     
9887     SendToProgram("force\n", &first);
9888     CopyBoard(boards[0], initial_position);
9889     if (blackPlaysFirst) {
9890         currentMove = forwardMostMove = backwardMostMove = 1;
9891         strcpy(moveList[0], "");
9892         strcpy(parseList[0], "");
9893         CopyBoard(boards[1], initial_position);
9894         DisplayMessage("", _("Black to play"));
9895     } else {
9896         currentMove = forwardMostMove = backwardMostMove = 0;
9897         DisplayMessage("", _("White to play"));
9898     }
9899     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9900     SendBoard(&first, forwardMostMove);
9901     if (appData.debugMode) {
9902 int i, j;
9903   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9904   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9905         fprintf(debugFP, "Load Position\n");
9906     }
9907
9908     if (positionNumber > 1) {
9909         sprintf(line, "%s %d", title, positionNumber);
9910         DisplayTitle(line);
9911     } else {
9912         DisplayTitle(title);
9913     }
9914     gameMode = EditGame;
9915     ModeHighlight();
9916     ResetClocks();
9917     timeRemaining[0][1] = whiteTimeRemaining;
9918     timeRemaining[1][1] = blackTimeRemaining;
9919     DrawPosition(FALSE, boards[currentMove]);
9920    
9921     return TRUE;
9922 }
9923
9924
9925 void
9926 CopyPlayerNameIntoFileName(dest, src)
9927      char **dest, *src;
9928 {
9929     while (*src != NULLCHAR && *src != ',') {
9930         if (*src == ' ') {
9931             *(*dest)++ = '_';
9932             src++;
9933         } else {
9934             *(*dest)++ = *src++;
9935         }
9936     }
9937 }
9938
9939 char *DefaultFileName(ext)
9940      char *ext;
9941 {
9942     static char def[MSG_SIZ];
9943     char *p;
9944
9945     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9946         p = def;
9947         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9948         *p++ = '-';
9949         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9950         *p++ = '.';
9951         strcpy(p, ext);
9952     } else {
9953         def[0] = NULLCHAR;
9954     }
9955     return def;
9956 }
9957
9958 /* Save the current game to the given file */
9959 int
9960 SaveGameToFile(filename, append)
9961      char *filename;
9962      int append;
9963 {
9964     FILE *f;
9965     char buf[MSG_SIZ];
9966
9967     if (strcmp(filename, "-") == 0) {
9968         return SaveGame(stdout, 0, NULL);
9969     } else {
9970         f = fopen(filename, append ? "a" : "w");
9971         if (f == NULL) {
9972             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9973             DisplayError(buf, errno);
9974             return FALSE;
9975         } else {
9976             return SaveGame(f, 0, NULL);
9977         }
9978     }
9979 }
9980
9981 char *
9982 SavePart(str)
9983      char *str;
9984 {
9985     static char buf[MSG_SIZ];
9986     char *p;
9987     
9988     p = strchr(str, ' ');
9989     if (p == NULL) return str;
9990     strncpy(buf, str, p - str);
9991     buf[p - str] = NULLCHAR;
9992     return buf;
9993 }
9994
9995 #define PGN_MAX_LINE 75
9996
9997 #define PGN_SIDE_WHITE  0
9998 #define PGN_SIDE_BLACK  1
9999
10000 /* [AS] */
10001 static int FindFirstMoveOutOfBook( int side )
10002 {
10003     int result = -1;
10004
10005     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10006         int index = backwardMostMove;
10007         int has_book_hit = 0;
10008
10009         if( (index % 2) != side ) {
10010             index++;
10011         }
10012
10013         while( index < forwardMostMove ) {
10014             /* Check to see if engine is in book */
10015             int depth = pvInfoList[index].depth;
10016             int score = pvInfoList[index].score;
10017             int in_book = 0;
10018
10019             if( depth <= 2 ) {
10020                 in_book = 1;
10021             }
10022             else if( score == 0 && depth == 63 ) {
10023                 in_book = 1; /* Zappa */
10024             }
10025             else if( score == 2 && depth == 99 ) {
10026                 in_book = 1; /* Abrok */
10027             }
10028
10029             has_book_hit += in_book;
10030
10031             if( ! in_book ) {
10032                 result = index;
10033
10034                 break;
10035             }
10036
10037             index += 2;
10038         }
10039     }
10040
10041     return result;
10042 }
10043
10044 /* [AS] */
10045 void GetOutOfBookInfo( char * buf )
10046 {
10047     int oob[2];
10048     int i;
10049     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10050
10051     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10052     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10053
10054     *buf = '\0';
10055
10056     if( oob[0] >= 0 || oob[1] >= 0 ) {
10057         for( i=0; i<2; i++ ) {
10058             int idx = oob[i];
10059
10060             if( idx >= 0 ) {
10061                 if( i > 0 && oob[0] >= 0 ) {
10062                     strcat( buf, "   " );
10063                 }
10064
10065                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10066                 sprintf( buf+strlen(buf), "%s%.2f", 
10067                     pvInfoList[idx].score >= 0 ? "+" : "",
10068                     pvInfoList[idx].score / 100.0 );
10069             }
10070         }
10071     }
10072 }
10073
10074 /* Save game in PGN style and close the file */
10075 int
10076 SaveGamePGN(f)
10077      FILE *f;
10078 {
10079     int i, offset, linelen, newblock;
10080     time_t tm;
10081 //    char *movetext;
10082     char numtext[32];
10083     int movelen, numlen, blank;
10084     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10085
10086     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10087     
10088     tm = time((time_t *) NULL);
10089     
10090     PrintPGNTags(f, &gameInfo);
10091     
10092     if (backwardMostMove > 0 || startedFromSetupPosition) {
10093         char *fen = PositionToFEN(backwardMostMove, NULL);
10094         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10095         fprintf(f, "\n{--------------\n");
10096         PrintPosition(f, backwardMostMove);
10097         fprintf(f, "--------------}\n");
10098         free(fen);
10099     }
10100     else {
10101         /* [AS] Out of book annotation */
10102         if( appData.saveOutOfBookInfo ) {
10103             char buf[64];
10104
10105             GetOutOfBookInfo( buf );
10106
10107             if( buf[0] != '\0' ) {
10108                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10109             }
10110         }
10111
10112         fprintf(f, "\n");
10113     }
10114
10115     i = backwardMostMove;
10116     linelen = 0;
10117     newblock = TRUE;
10118
10119     while (i < forwardMostMove) {
10120         /* Print comments preceding this move */
10121         if (commentList[i] != NULL) {
10122             if (linelen > 0) fprintf(f, "\n");
10123             fprintf(f, "%s", commentList[i]);
10124             linelen = 0;
10125             newblock = TRUE;
10126         }
10127
10128         /* Format move number */
10129         if ((i % 2) == 0) {
10130             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10131         } else {
10132             if (newblock) {
10133                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10134             } else {
10135                 numtext[0] = NULLCHAR;
10136             }
10137         }
10138         numlen = strlen(numtext);
10139         newblock = FALSE;
10140
10141         /* Print move number */
10142         blank = linelen > 0 && numlen > 0;
10143         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10144             fprintf(f, "\n");
10145             linelen = 0;
10146             blank = 0;
10147         }
10148         if (blank) {
10149             fprintf(f, " ");
10150             linelen++;
10151         }
10152         fprintf(f, "%s", numtext);
10153         linelen += numlen;
10154
10155         /* Get move */
10156         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10157         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10158
10159         /* Print move */
10160         blank = linelen > 0 && movelen > 0;
10161         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10162             fprintf(f, "\n");
10163             linelen = 0;
10164             blank = 0;
10165         }
10166         if (blank) {
10167             fprintf(f, " ");
10168             linelen++;
10169         }
10170         fprintf(f, "%s", move_buffer);
10171         linelen += movelen;
10172
10173         /* [AS] Add PV info if present */
10174         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10175             /* [HGM] add time */
10176             char buf[MSG_SIZ]; int seconds;
10177
10178             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10179
10180             if( seconds <= 0) buf[0] = 0; else
10181             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10182                 seconds = (seconds + 4)/10; // round to full seconds
10183                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10184                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10185             }
10186
10187             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10188                 pvInfoList[i].score >= 0 ? "+" : "",
10189                 pvInfoList[i].score / 100.0,
10190                 pvInfoList[i].depth,
10191                 buf );
10192
10193             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10194
10195             /* Print score/depth */
10196             blank = linelen > 0 && movelen > 0;
10197             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10198                 fprintf(f, "\n");
10199                 linelen = 0;
10200                 blank = 0;
10201             }
10202             if (blank) {
10203                 fprintf(f, " ");
10204                 linelen++;
10205             }
10206             fprintf(f, "%s", move_buffer);
10207             linelen += movelen;
10208         }
10209
10210         i++;
10211     }
10212     
10213     /* Start a new line */
10214     if (linelen > 0) fprintf(f, "\n");
10215
10216     /* Print comments after last move */
10217     if (commentList[i] != NULL) {
10218         fprintf(f, "%s\n", commentList[i]);
10219     }
10220
10221     /* Print result */
10222     if (gameInfo.resultDetails != NULL &&
10223         gameInfo.resultDetails[0] != NULLCHAR) {
10224         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10225                 PGNResult(gameInfo.result));
10226     } else {
10227         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10228     }
10229
10230     fclose(f);
10231     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10232     return TRUE;
10233 }
10234
10235 /* Save game in old style and close the file */
10236 int
10237 SaveGameOldStyle(f)
10238      FILE *f;
10239 {
10240     int i, offset;
10241     time_t tm;
10242     
10243     tm = time((time_t *) NULL);
10244     
10245     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10246     PrintOpponents(f);
10247     
10248     if (backwardMostMove > 0 || startedFromSetupPosition) {
10249         fprintf(f, "\n[--------------\n");
10250         PrintPosition(f, backwardMostMove);
10251         fprintf(f, "--------------]\n");
10252     } else {
10253         fprintf(f, "\n");
10254     }
10255
10256     i = backwardMostMove;
10257     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10258
10259     while (i < forwardMostMove) {
10260         if (commentList[i] != NULL) {
10261             fprintf(f, "[%s]\n", commentList[i]);
10262         }
10263
10264         if ((i % 2) == 1) {
10265             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10266             i++;
10267         } else {
10268             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10269             i++;
10270             if (commentList[i] != NULL) {
10271                 fprintf(f, "\n");
10272                 continue;
10273             }
10274             if (i >= forwardMostMove) {
10275                 fprintf(f, "\n");
10276                 break;
10277             }
10278             fprintf(f, "%s\n", parseList[i]);
10279             i++;
10280         }
10281     }
10282     
10283     if (commentList[i] != NULL) {
10284         fprintf(f, "[%s]\n", commentList[i]);
10285     }
10286
10287     /* This isn't really the old style, but it's close enough */
10288     if (gameInfo.resultDetails != NULL &&
10289         gameInfo.resultDetails[0] != NULLCHAR) {
10290         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10291                 gameInfo.resultDetails);
10292     } else {
10293         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10294     }
10295
10296     fclose(f);
10297     return TRUE;
10298 }
10299
10300 /* Save the current game to open file f and close the file */
10301 int
10302 SaveGame(f, dummy, dummy2)
10303      FILE *f;
10304      int dummy;
10305      char *dummy2;
10306 {
10307     if (gameMode == EditPosition) EditPositionDone(TRUE);
10308     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10309     if (appData.oldSaveStyle)
10310       return SaveGameOldStyle(f);
10311     else
10312       return SaveGamePGN(f);
10313 }
10314
10315 /* Save the current position to the given file */
10316 int
10317 SavePositionToFile(filename)
10318      char *filename;
10319 {
10320     FILE *f;
10321     char buf[MSG_SIZ];
10322
10323     if (strcmp(filename, "-") == 0) {
10324         return SavePosition(stdout, 0, NULL);
10325     } else {
10326         f = fopen(filename, "a");
10327         if (f == NULL) {
10328             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10329             DisplayError(buf, errno);
10330             return FALSE;
10331         } else {
10332             SavePosition(f, 0, NULL);
10333             return TRUE;
10334         }
10335     }
10336 }
10337
10338 /* Save the current position to the given open file and close the file */
10339 int
10340 SavePosition(f, dummy, dummy2)
10341      FILE *f;
10342      int dummy;
10343      char *dummy2;
10344 {
10345     time_t tm;
10346     char *fen;
10347     
10348     if (gameMode == EditPosition) EditPositionDone(TRUE);
10349     if (appData.oldSaveStyle) {
10350         tm = time((time_t *) NULL);
10351     
10352         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10353         PrintOpponents(f);
10354         fprintf(f, "[--------------\n");
10355         PrintPosition(f, currentMove);
10356         fprintf(f, "--------------]\n");
10357     } else {
10358         fen = PositionToFEN(currentMove, NULL);
10359         fprintf(f, "%s\n", fen);
10360         free(fen);
10361     }
10362     fclose(f);
10363     return TRUE;
10364 }
10365
10366 void
10367 ReloadCmailMsgEvent(unregister)
10368      int unregister;
10369 {
10370 #if !WIN32
10371     static char *inFilename = NULL;
10372     static char *outFilename;
10373     int i;
10374     struct stat inbuf, outbuf;
10375     int status;
10376     
10377     /* Any registered moves are unregistered if unregister is set, */
10378     /* i.e. invoked by the signal handler */
10379     if (unregister) {
10380         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10381             cmailMoveRegistered[i] = FALSE;
10382             if (cmailCommentList[i] != NULL) {
10383                 free(cmailCommentList[i]);
10384                 cmailCommentList[i] = NULL;
10385             }
10386         }
10387         nCmailMovesRegistered = 0;
10388     }
10389
10390     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10391         cmailResult[i] = CMAIL_NOT_RESULT;
10392     }
10393     nCmailResults = 0;
10394
10395     if (inFilename == NULL) {
10396         /* Because the filenames are static they only get malloced once  */
10397         /* and they never get freed                                      */
10398         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10399         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10400
10401         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10402         sprintf(outFilename, "%s.out", appData.cmailGameName);
10403     }
10404     
10405     status = stat(outFilename, &outbuf);
10406     if (status < 0) {
10407         cmailMailedMove = FALSE;
10408     } else {
10409         status = stat(inFilename, &inbuf);
10410         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10411     }
10412     
10413     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10414        counts the games, notes how each one terminated, etc.
10415        
10416        It would be nice to remove this kludge and instead gather all
10417        the information while building the game list.  (And to keep it
10418        in the game list nodes instead of having a bunch of fixed-size
10419        parallel arrays.)  Note this will require getting each game's
10420        termination from the PGN tags, as the game list builder does
10421        not process the game moves.  --mann
10422        */
10423     cmailMsgLoaded = TRUE;
10424     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10425     
10426     /* Load first game in the file or popup game menu */
10427     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10428
10429 #endif /* !WIN32 */
10430     return;
10431 }
10432
10433 int
10434 RegisterMove()
10435 {
10436     FILE *f;
10437     char string[MSG_SIZ];
10438
10439     if (   cmailMailedMove
10440         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10441         return TRUE;            /* Allow free viewing  */
10442     }
10443
10444     /* Unregister move to ensure that we don't leave RegisterMove        */
10445     /* with the move registered when the conditions for registering no   */
10446     /* longer hold                                                       */
10447     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10448         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10449         nCmailMovesRegistered --;
10450
10451         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10452           {
10453               free(cmailCommentList[lastLoadGameNumber - 1]);
10454               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10455           }
10456     }
10457
10458     if (cmailOldMove == -1) {
10459         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10460         return FALSE;
10461     }
10462
10463     if (currentMove > cmailOldMove + 1) {
10464         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10465         return FALSE;
10466     }
10467
10468     if (currentMove < cmailOldMove) {
10469         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10470         return FALSE;
10471     }
10472
10473     if (forwardMostMove > currentMove) {
10474         /* Silently truncate extra moves */
10475         TruncateGame();
10476     }
10477
10478     if (   (currentMove == cmailOldMove + 1)
10479         || (   (currentMove == cmailOldMove)
10480             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10481                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10482         if (gameInfo.result != GameUnfinished) {
10483             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10484         }
10485
10486         if (commentList[currentMove] != NULL) {
10487             cmailCommentList[lastLoadGameNumber - 1]
10488               = StrSave(commentList[currentMove]);
10489         }
10490         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10491
10492         if (appData.debugMode)
10493           fprintf(debugFP, "Saving %s for game %d\n",
10494                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10495
10496         sprintf(string,
10497                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10498         
10499         f = fopen(string, "w");
10500         if (appData.oldSaveStyle) {
10501             SaveGameOldStyle(f); /* also closes the file */
10502             
10503             sprintf(string, "%s.pos.out", appData.cmailGameName);
10504             f = fopen(string, "w");
10505             SavePosition(f, 0, NULL); /* also closes the file */
10506         } else {
10507             fprintf(f, "{--------------\n");
10508             PrintPosition(f, currentMove);
10509             fprintf(f, "--------------}\n\n");
10510             
10511             SaveGame(f, 0, NULL); /* also closes the file*/
10512         }
10513         
10514         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10515         nCmailMovesRegistered ++;
10516     } else if (nCmailGames == 1) {
10517         DisplayError(_("You have not made a move yet"), 0);
10518         return FALSE;
10519     }
10520
10521     return TRUE;
10522 }
10523
10524 void
10525 MailMoveEvent()
10526 {
10527 #if !WIN32
10528     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10529     FILE *commandOutput;
10530     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10531     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10532     int nBuffers;
10533     int i;
10534     int archived;
10535     char *arcDir;
10536
10537     if (! cmailMsgLoaded) {
10538         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10539         return;
10540     }
10541
10542     if (nCmailGames == nCmailResults) {
10543         DisplayError(_("No unfinished games"), 0);
10544         return;
10545     }
10546
10547 #if CMAIL_PROHIBIT_REMAIL
10548     if (cmailMailedMove) {
10549         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);
10550         DisplayError(msg, 0);
10551         return;
10552     }
10553 #endif
10554
10555     if (! (cmailMailedMove || RegisterMove())) return;
10556     
10557     if (   cmailMailedMove
10558         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10559         sprintf(string, partCommandString,
10560                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10561         commandOutput = popen(string, "r");
10562
10563         if (commandOutput == NULL) {
10564             DisplayError(_("Failed to invoke cmail"), 0);
10565         } else {
10566             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10567                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10568             }
10569             if (nBuffers > 1) {
10570                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10571                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10572                 nBytes = MSG_SIZ - 1;
10573             } else {
10574                 (void) memcpy(msg, buffer, nBytes);
10575             }
10576             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10577
10578             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10579                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10580
10581                 archived = TRUE;
10582                 for (i = 0; i < nCmailGames; i ++) {
10583                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10584                         archived = FALSE;
10585                     }
10586                 }
10587                 if (   archived
10588                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10589                         != NULL)) {
10590                     sprintf(buffer, "%s/%s.%s.archive",
10591                             arcDir,
10592                             appData.cmailGameName,
10593                             gameInfo.date);
10594                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10595                     cmailMsgLoaded = FALSE;
10596                 }
10597             }
10598
10599             DisplayInformation(msg);
10600             pclose(commandOutput);
10601         }
10602     } else {
10603         if ((*cmailMsg) != '\0') {
10604             DisplayInformation(cmailMsg);
10605         }
10606     }
10607
10608     return;
10609 #endif /* !WIN32 */
10610 }
10611
10612 char *
10613 CmailMsg()
10614 {
10615 #if WIN32
10616     return NULL;
10617 #else
10618     int  prependComma = 0;
10619     char number[5];
10620     char string[MSG_SIZ];       /* Space for game-list */
10621     int  i;
10622     
10623     if (!cmailMsgLoaded) return "";
10624
10625     if (cmailMailedMove) {
10626         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10627     } else {
10628         /* Create a list of games left */
10629         sprintf(string, "[");
10630         for (i = 0; i < nCmailGames; i ++) {
10631             if (! (   cmailMoveRegistered[i]
10632                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10633                 if (prependComma) {
10634                     sprintf(number, ",%d", i + 1);
10635                 } else {
10636                     sprintf(number, "%d", i + 1);
10637                     prependComma = 1;
10638                 }
10639                 
10640                 strcat(string, number);
10641             }
10642         }
10643         strcat(string, "]");
10644
10645         if (nCmailMovesRegistered + nCmailResults == 0) {
10646             switch (nCmailGames) {
10647               case 1:
10648                 sprintf(cmailMsg,
10649                         _("Still need to make move for game\n"));
10650                 break;
10651                 
10652               case 2:
10653                 sprintf(cmailMsg,
10654                         _("Still need to make moves for both games\n"));
10655                 break;
10656                 
10657               default:
10658                 sprintf(cmailMsg,
10659                         _("Still need to make moves for all %d games\n"),
10660                         nCmailGames);
10661                 break;
10662             }
10663         } else {
10664             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10665               case 1:
10666                 sprintf(cmailMsg,
10667                         _("Still need to make a move for game %s\n"),
10668                         string);
10669                 break;
10670                 
10671               case 0:
10672                 if (nCmailResults == nCmailGames) {
10673                     sprintf(cmailMsg, _("No unfinished games\n"));
10674                 } else {
10675                     sprintf(cmailMsg, _("Ready to send mail\n"));
10676                 }
10677                 break;
10678                 
10679               default:
10680                 sprintf(cmailMsg,
10681                         _("Still need to make moves for games %s\n"),
10682                         string);
10683             }
10684         }
10685     }
10686     return cmailMsg;
10687 #endif /* WIN32 */
10688 }
10689
10690 void
10691 ResetGameEvent()
10692 {
10693     if (gameMode == Training)
10694       SetTrainingModeOff();
10695
10696     Reset(TRUE, TRUE);
10697     cmailMsgLoaded = FALSE;
10698     if (appData.icsActive) {
10699       SendToICS(ics_prefix);
10700       SendToICS("refresh\n");
10701     }
10702 }
10703
10704 void
10705 ExitEvent(status)
10706      int status;
10707 {
10708     exiting++;
10709     if (exiting > 2) {
10710       /* Give up on clean exit */
10711       exit(status);
10712     }
10713     if (exiting > 1) {
10714       /* Keep trying for clean exit */
10715       return;
10716     }
10717
10718     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10719
10720     if (telnetISR != NULL) {
10721       RemoveInputSource(telnetISR);
10722     }
10723     if (icsPR != NoProc) {
10724       DestroyChildProcess(icsPR, TRUE);
10725     }
10726
10727     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10728     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10729
10730     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10731     /* make sure this other one finishes before killing it!                  */
10732     if(endingGame) { int count = 0;
10733         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10734         while(endingGame && count++ < 10) DoSleep(1);
10735         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10736     }
10737
10738     /* Kill off chess programs */
10739     if (first.pr != NoProc) {
10740         ExitAnalyzeMode();
10741         
10742         DoSleep( appData.delayBeforeQuit );
10743         SendToProgram("quit\n", &first);
10744         DoSleep( appData.delayAfterQuit );
10745         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10746     }
10747     if (second.pr != NoProc) {
10748         DoSleep( appData.delayBeforeQuit );
10749         SendToProgram("quit\n", &second);
10750         DoSleep( appData.delayAfterQuit );
10751         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10752     }
10753     if (first.isr != NULL) {
10754         RemoveInputSource(first.isr);
10755     }
10756     if (second.isr != NULL) {
10757         RemoveInputSource(second.isr);
10758     }
10759
10760     ShutDownFrontEnd();
10761     exit(status);
10762 }
10763
10764 void
10765 PauseEvent()
10766 {
10767     if (appData.debugMode)
10768         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10769     if (pausing) {
10770         pausing = FALSE;
10771         ModeHighlight();
10772         if (gameMode == MachinePlaysWhite ||
10773             gameMode == MachinePlaysBlack) {
10774             StartClocks();
10775         } else {
10776             DisplayBothClocks();
10777         }
10778         if (gameMode == PlayFromGameFile) {
10779             if (appData.timeDelay >= 0) 
10780                 AutoPlayGameLoop();
10781         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10782             Reset(FALSE, TRUE);
10783             SendToICS(ics_prefix);
10784             SendToICS("refresh\n");
10785         } else if (currentMove < forwardMostMove) {
10786             ForwardInner(forwardMostMove);
10787         }
10788         pauseExamInvalid = FALSE;
10789     } else {
10790         switch (gameMode) {
10791           default:
10792             return;
10793           case IcsExamining:
10794             pauseExamForwardMostMove = forwardMostMove;
10795             pauseExamInvalid = FALSE;
10796             /* fall through */
10797           case IcsObserving:
10798           case IcsPlayingWhite:
10799           case IcsPlayingBlack:
10800             pausing = TRUE;
10801             ModeHighlight();
10802             return;
10803           case PlayFromGameFile:
10804             (void) StopLoadGameTimer();
10805             pausing = TRUE;
10806             ModeHighlight();
10807             break;
10808           case BeginningOfGame:
10809             if (appData.icsActive) return;
10810             /* else fall through */
10811           case MachinePlaysWhite:
10812           case MachinePlaysBlack:
10813           case TwoMachinesPlay:
10814             if (forwardMostMove == 0)
10815               return;           /* don't pause if no one has moved */
10816             if ((gameMode == MachinePlaysWhite &&
10817                  !WhiteOnMove(forwardMostMove)) ||
10818                 (gameMode == MachinePlaysBlack &&
10819                  WhiteOnMove(forwardMostMove))) {
10820                 StopClocks();
10821             }
10822             pausing = TRUE;
10823             ModeHighlight();
10824             break;
10825         }
10826     }
10827 }
10828
10829 void
10830 EditCommentEvent()
10831 {
10832     char title[MSG_SIZ];
10833
10834     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10835         strcpy(title, _("Edit comment"));
10836     } else {
10837         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10838                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10839                 parseList[currentMove - 1]);
10840     }
10841
10842     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10843 }
10844
10845
10846 void
10847 EditTagsEvent()
10848 {
10849     char *tags = PGNTags(&gameInfo);
10850     EditTagsPopUp(tags);
10851     free(tags);
10852 }
10853
10854 void
10855 AnalyzeModeEvent()
10856 {
10857     if (appData.noChessProgram || gameMode == AnalyzeMode)
10858       return;
10859
10860     if (gameMode != AnalyzeFile) {
10861         if (!appData.icsEngineAnalyze) {
10862                EditGameEvent();
10863                if (gameMode != EditGame) return;
10864         }
10865         ResurrectChessProgram();
10866         SendToProgram("analyze\n", &first);
10867         first.analyzing = TRUE;
10868         /*first.maybeThinking = TRUE;*/
10869         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10870         EngineOutputPopUp();
10871     }
10872     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10873     pausing = FALSE;
10874     ModeHighlight();
10875     SetGameInfo();
10876
10877     StartAnalysisClock();
10878     GetTimeMark(&lastNodeCountTime);
10879     lastNodeCount = 0;
10880 }
10881
10882 void
10883 AnalyzeFileEvent()
10884 {
10885     if (appData.noChessProgram || gameMode == AnalyzeFile)
10886       return;
10887
10888     if (gameMode != AnalyzeMode) {
10889         EditGameEvent();
10890         if (gameMode != EditGame) return;
10891         ResurrectChessProgram();
10892         SendToProgram("analyze\n", &first);
10893         first.analyzing = TRUE;
10894         /*first.maybeThinking = TRUE;*/
10895         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10896         EngineOutputPopUp();
10897     }
10898     gameMode = AnalyzeFile;
10899     pausing = FALSE;
10900     ModeHighlight();
10901     SetGameInfo();
10902
10903     StartAnalysisClock();
10904     GetTimeMark(&lastNodeCountTime);
10905     lastNodeCount = 0;
10906 }
10907
10908 void
10909 MachineWhiteEvent()
10910 {
10911     char buf[MSG_SIZ];
10912     char *bookHit = NULL;
10913
10914     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10915       return;
10916
10917
10918     if (gameMode == PlayFromGameFile || 
10919         gameMode == TwoMachinesPlay  || 
10920         gameMode == Training         || 
10921         gameMode == AnalyzeMode      || 
10922         gameMode == EndOfGame)
10923         EditGameEvent();
10924
10925     if (gameMode == EditPosition) 
10926         EditPositionDone(TRUE);
10927
10928     if (!WhiteOnMove(currentMove)) {
10929         DisplayError(_("It is not White's turn"), 0);
10930         return;
10931     }
10932   
10933     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10934       ExitAnalyzeMode();
10935
10936     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10937         gameMode == AnalyzeFile)
10938         TruncateGame();
10939
10940     ResurrectChessProgram();    /* in case it isn't running */
10941     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10942         gameMode = MachinePlaysWhite;
10943         ResetClocks();
10944     } else
10945     gameMode = MachinePlaysWhite;
10946     pausing = FALSE;
10947     ModeHighlight();
10948     SetGameInfo();
10949     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10950     DisplayTitle(buf);
10951     if (first.sendName) {
10952       sprintf(buf, "name %s\n", gameInfo.black);
10953       SendToProgram(buf, &first);
10954     }
10955     if (first.sendTime) {
10956       if (first.useColors) {
10957         SendToProgram("black\n", &first); /*gnu kludge*/
10958       }
10959       SendTimeRemaining(&first, TRUE);
10960     }
10961     if (first.useColors) {
10962       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10963     }
10964     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10965     SetMachineThinkingEnables();
10966     first.maybeThinking = TRUE;
10967     StartClocks();
10968     firstMove = FALSE;
10969
10970     if (appData.autoFlipView && !flipView) {
10971       flipView = !flipView;
10972       DrawPosition(FALSE, NULL);
10973       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10974     }
10975
10976     if(bookHit) { // [HGM] book: simulate book reply
10977         static char bookMove[MSG_SIZ]; // a bit generous?
10978
10979         programStats.nodes = programStats.depth = programStats.time = 
10980         programStats.score = programStats.got_only_move = 0;
10981         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10982
10983         strcpy(bookMove, "move ");
10984         strcat(bookMove, bookHit);
10985         HandleMachineMove(bookMove, &first);
10986     }
10987 }
10988
10989 void
10990 MachineBlackEvent()
10991 {
10992     char buf[MSG_SIZ];
10993    char *bookHit = NULL;
10994
10995     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10996         return;
10997
10998
10999     if (gameMode == PlayFromGameFile || 
11000         gameMode == TwoMachinesPlay  || 
11001         gameMode == Training         || 
11002         gameMode == AnalyzeMode      || 
11003         gameMode == EndOfGame)
11004         EditGameEvent();
11005
11006     if (gameMode == EditPosition) 
11007         EditPositionDone(TRUE);
11008
11009     if (WhiteOnMove(currentMove)) {
11010         DisplayError(_("It is not Black's turn"), 0);
11011         return;
11012     }
11013     
11014     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11015       ExitAnalyzeMode();
11016
11017     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11018         gameMode == AnalyzeFile)
11019         TruncateGame();
11020
11021     ResurrectChessProgram();    /* in case it isn't running */
11022     gameMode = MachinePlaysBlack;
11023     pausing = FALSE;
11024     ModeHighlight();
11025     SetGameInfo();
11026     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11027     DisplayTitle(buf);
11028     if (first.sendName) {
11029       sprintf(buf, "name %s\n", gameInfo.white);
11030       SendToProgram(buf, &first);
11031     }
11032     if (first.sendTime) {
11033       if (first.useColors) {
11034         SendToProgram("white\n", &first); /*gnu kludge*/
11035       }
11036       SendTimeRemaining(&first, FALSE);
11037     }
11038     if (first.useColors) {
11039       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11040     }
11041     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11042     SetMachineThinkingEnables();
11043     first.maybeThinking = TRUE;
11044     StartClocks();
11045
11046     if (appData.autoFlipView && flipView) {
11047       flipView = !flipView;
11048       DrawPosition(FALSE, NULL);
11049       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11050     }
11051     if(bookHit) { // [HGM] book: simulate book reply
11052         static char bookMove[MSG_SIZ]; // a bit generous?
11053
11054         programStats.nodes = programStats.depth = programStats.time = 
11055         programStats.score = programStats.got_only_move = 0;
11056         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11057
11058         strcpy(bookMove, "move ");
11059         strcat(bookMove, bookHit);
11060         HandleMachineMove(bookMove, &first);
11061     }
11062 }
11063
11064
11065 void
11066 DisplayTwoMachinesTitle()
11067 {
11068     char buf[MSG_SIZ];
11069     if (appData.matchGames > 0) {
11070         if (first.twoMachinesColor[0] == 'w') {
11071             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11072                     gameInfo.white, gameInfo.black,
11073                     first.matchWins, second.matchWins,
11074                     matchGame - 1 - (first.matchWins + second.matchWins));
11075         } else {
11076             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11077                     gameInfo.white, gameInfo.black,
11078                     second.matchWins, first.matchWins,
11079                     matchGame - 1 - (first.matchWins + second.matchWins));
11080         }
11081     } else {
11082         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11083     }
11084     DisplayTitle(buf);
11085 }
11086
11087 void
11088 TwoMachinesEvent P((void))
11089 {
11090     int i;
11091     char buf[MSG_SIZ];
11092     ChessProgramState *onmove;
11093     char *bookHit = NULL;
11094     
11095     if (appData.noChessProgram) return;
11096
11097     switch (gameMode) {
11098       case TwoMachinesPlay:
11099         return;
11100       case MachinePlaysWhite:
11101       case MachinePlaysBlack:
11102         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11103             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11104             return;
11105         }
11106         /* fall through */
11107       case BeginningOfGame:
11108       case PlayFromGameFile:
11109       case EndOfGame:
11110         EditGameEvent();
11111         if (gameMode != EditGame) return;
11112         break;
11113       case EditPosition:
11114         EditPositionDone(TRUE);
11115         break;
11116       case AnalyzeMode:
11117       case AnalyzeFile:
11118         ExitAnalyzeMode();
11119         break;
11120       case EditGame:
11121       default:
11122         break;
11123     }
11124
11125 //    forwardMostMove = currentMove;
11126     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11127     ResurrectChessProgram();    /* in case first program isn't running */
11128
11129     if (second.pr == NULL) {
11130         StartChessProgram(&second);
11131         if (second.protocolVersion == 1) {
11132           TwoMachinesEventIfReady();
11133         } else {
11134           /* kludge: allow timeout for initial "feature" command */
11135           FreezeUI();
11136           DisplayMessage("", _("Starting second chess program"));
11137           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11138         }
11139         return;
11140     }
11141     DisplayMessage("", "");
11142     InitChessProgram(&second, FALSE);
11143     SendToProgram("force\n", &second);
11144     if (startedFromSetupPosition) {
11145         SendBoard(&second, backwardMostMove);
11146     if (appData.debugMode) {
11147         fprintf(debugFP, "Two Machines\n");
11148     }
11149     }
11150     for (i = backwardMostMove; i < forwardMostMove; i++) {
11151         SendMoveToProgram(i, &second);
11152     }
11153
11154     gameMode = TwoMachinesPlay;
11155     pausing = FALSE;
11156     ModeHighlight();
11157     SetGameInfo();
11158     DisplayTwoMachinesTitle();
11159     firstMove = TRUE;
11160     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11161         onmove = &first;
11162     } else {
11163         onmove = &second;
11164     }
11165
11166     SendToProgram(first.computerString, &first);
11167     if (first.sendName) {
11168       sprintf(buf, "name %s\n", second.tidy);
11169       SendToProgram(buf, &first);
11170     }
11171     SendToProgram(second.computerString, &second);
11172     if (second.sendName) {
11173       sprintf(buf, "name %s\n", first.tidy);
11174       SendToProgram(buf, &second);
11175     }
11176
11177     ResetClocks();
11178     if (!first.sendTime || !second.sendTime) {
11179         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11180         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11181     }
11182     if (onmove->sendTime) {
11183       if (onmove->useColors) {
11184         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11185       }
11186       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11187     }
11188     if (onmove->useColors) {
11189       SendToProgram(onmove->twoMachinesColor, onmove);
11190     }
11191     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11192 //    SendToProgram("go\n", onmove);
11193     onmove->maybeThinking = TRUE;
11194     SetMachineThinkingEnables();
11195
11196     StartClocks();
11197
11198     if(bookHit) { // [HGM] book: simulate book reply
11199         static char bookMove[MSG_SIZ]; // a bit generous?
11200
11201         programStats.nodes = programStats.depth = programStats.time = 
11202         programStats.score = programStats.got_only_move = 0;
11203         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11204
11205         strcpy(bookMove, "move ");
11206         strcat(bookMove, bookHit);
11207         savedMessage = bookMove; // args for deferred call
11208         savedState = onmove;
11209         ScheduleDelayedEvent(DeferredBookMove, 1);
11210     }
11211 }
11212
11213 void
11214 TrainingEvent()
11215 {
11216     if (gameMode == Training) {
11217       SetTrainingModeOff();
11218       gameMode = PlayFromGameFile;
11219       DisplayMessage("", _("Training mode off"));
11220     } else {
11221       gameMode = Training;
11222       animateTraining = appData.animate;
11223
11224       /* make sure we are not already at the end of the game */
11225       if (currentMove < forwardMostMove) {
11226         SetTrainingModeOn();
11227         DisplayMessage("", _("Training mode on"));
11228       } else {
11229         gameMode = PlayFromGameFile;
11230         DisplayError(_("Already at end of game"), 0);
11231       }
11232     }
11233     ModeHighlight();
11234 }
11235
11236 void
11237 IcsClientEvent()
11238 {
11239     if (!appData.icsActive) return;
11240     switch (gameMode) {
11241       case IcsPlayingWhite:
11242       case IcsPlayingBlack:
11243       case IcsObserving:
11244       case IcsIdle:
11245       case BeginningOfGame:
11246       case IcsExamining:
11247         return;
11248
11249       case EditGame:
11250         break;
11251
11252       case EditPosition:
11253         EditPositionDone(TRUE);
11254         break;
11255
11256       case AnalyzeMode:
11257       case AnalyzeFile:
11258         ExitAnalyzeMode();
11259         break;
11260         
11261       default:
11262         EditGameEvent();
11263         break;
11264     }
11265
11266     gameMode = IcsIdle;
11267     ModeHighlight();
11268     return;
11269 }
11270
11271
11272 void
11273 EditGameEvent()
11274 {
11275     int i;
11276
11277     switch (gameMode) {
11278       case Training:
11279         SetTrainingModeOff();
11280         break;
11281       case MachinePlaysWhite:
11282       case MachinePlaysBlack:
11283       case BeginningOfGame:
11284         SendToProgram("force\n", &first);
11285         SetUserThinkingEnables();
11286         break;
11287       case PlayFromGameFile:
11288         (void) StopLoadGameTimer();
11289         if (gameFileFP != NULL) {
11290             gameFileFP = NULL;
11291         }
11292         break;
11293       case EditPosition:
11294         EditPositionDone(TRUE);
11295         break;
11296       case AnalyzeMode:
11297       case AnalyzeFile:
11298         ExitAnalyzeMode();
11299         SendToProgram("force\n", &first);
11300         break;
11301       case TwoMachinesPlay:
11302         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11303         ResurrectChessProgram();
11304         SetUserThinkingEnables();
11305         break;
11306       case EndOfGame:
11307         ResurrectChessProgram();
11308         break;
11309       case IcsPlayingBlack:
11310       case IcsPlayingWhite:
11311         DisplayError(_("Warning: You are still playing a game"), 0);
11312         break;
11313       case IcsObserving:
11314         DisplayError(_("Warning: You are still observing a game"), 0);
11315         break;
11316       case IcsExamining:
11317         DisplayError(_("Warning: You are still examining a game"), 0);
11318         break;
11319       case IcsIdle:
11320         break;
11321       case EditGame:
11322       default:
11323         return;
11324     }
11325     
11326     pausing = FALSE;
11327     StopClocks();
11328     first.offeredDraw = second.offeredDraw = 0;
11329
11330     if (gameMode == PlayFromGameFile) {
11331         whiteTimeRemaining = timeRemaining[0][currentMove];
11332         blackTimeRemaining = timeRemaining[1][currentMove];
11333         DisplayTitle("");
11334     }
11335
11336     if (gameMode == MachinePlaysWhite ||
11337         gameMode == MachinePlaysBlack ||
11338         gameMode == TwoMachinesPlay ||
11339         gameMode == EndOfGame) {
11340         i = forwardMostMove;
11341         while (i > currentMove) {
11342             SendToProgram("undo\n", &first);
11343             i--;
11344         }
11345         whiteTimeRemaining = timeRemaining[0][currentMove];
11346         blackTimeRemaining = timeRemaining[1][currentMove];
11347         DisplayBothClocks();
11348         if (whiteFlag || blackFlag) {
11349             whiteFlag = blackFlag = 0;
11350         }
11351         DisplayTitle("");
11352     }           
11353     
11354     gameMode = EditGame;
11355     ModeHighlight();
11356     SetGameInfo();
11357 }
11358
11359
11360 void
11361 EditPositionEvent()
11362 {
11363     if (gameMode == EditPosition) {
11364         EditGameEvent();
11365         return;
11366     }
11367     
11368     EditGameEvent();
11369     if (gameMode != EditGame) return;
11370     
11371     gameMode = EditPosition;
11372     ModeHighlight();
11373     SetGameInfo();
11374     if (currentMove > 0)
11375       CopyBoard(boards[0], boards[currentMove]);
11376     
11377     blackPlaysFirst = !WhiteOnMove(currentMove);
11378     ResetClocks();
11379     currentMove = forwardMostMove = backwardMostMove = 0;
11380     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11381     DisplayMove(-1);
11382 }
11383
11384 void
11385 ExitAnalyzeMode()
11386 {
11387     /* [DM] icsEngineAnalyze - possible call from other functions */
11388     if (appData.icsEngineAnalyze) {
11389         appData.icsEngineAnalyze = FALSE;
11390
11391         DisplayMessage("",_("Close ICS engine analyze..."));
11392     }
11393     if (first.analysisSupport && first.analyzing) {
11394       SendToProgram("exit\n", &first);
11395       first.analyzing = FALSE;
11396     }
11397     thinkOutput[0] = NULLCHAR;
11398 }
11399
11400 void
11401 EditPositionDone(Boolean fakeRights)
11402 {
11403     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11404
11405     startedFromSetupPosition = TRUE;
11406     InitChessProgram(&first, FALSE);
11407     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11408       boards[0][EP_STATUS] = EP_NONE;
11409       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11410     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11411         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11412         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11413       } else boards[0][CASTLING][2] = NoRights;
11414     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11415         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11416         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11417       } else boards[0][CASTLING][5] = NoRights;
11418     }
11419     SendToProgram("force\n", &first);
11420     if (blackPlaysFirst) {
11421         strcpy(moveList[0], "");
11422         strcpy(parseList[0], "");
11423         currentMove = forwardMostMove = backwardMostMove = 1;
11424         CopyBoard(boards[1], boards[0]);
11425     } else {
11426         currentMove = forwardMostMove = backwardMostMove = 0;
11427     }
11428     SendBoard(&first, forwardMostMove);
11429     if (appData.debugMode) {
11430         fprintf(debugFP, "EditPosDone\n");
11431     }
11432     DisplayTitle("");
11433     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11434     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11435     gameMode = EditGame;
11436     ModeHighlight();
11437     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11438     ClearHighlights(); /* [AS] */
11439 }
11440
11441 /* Pause for `ms' milliseconds */
11442 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11443 void
11444 TimeDelay(ms)
11445      long ms;
11446 {
11447     TimeMark m1, m2;
11448
11449     GetTimeMark(&m1);
11450     do {
11451         GetTimeMark(&m2);
11452     } while (SubtractTimeMarks(&m2, &m1) < ms);
11453 }
11454
11455 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11456 void
11457 SendMultiLineToICS(buf)
11458      char *buf;
11459 {
11460     char temp[MSG_SIZ+1], *p;
11461     int len;
11462
11463     len = strlen(buf);
11464     if (len > MSG_SIZ)
11465       len = MSG_SIZ;
11466   
11467     strncpy(temp, buf, len);
11468     temp[len] = 0;
11469
11470     p = temp;
11471     while (*p) {
11472         if (*p == '\n' || *p == '\r')
11473           *p = ' ';
11474         ++p;
11475     }
11476
11477     strcat(temp, "\n");
11478     SendToICS(temp);
11479     SendToPlayer(temp, strlen(temp));
11480 }
11481
11482 void
11483 SetWhiteToPlayEvent()
11484 {
11485     if (gameMode == EditPosition) {
11486         blackPlaysFirst = FALSE;
11487         DisplayBothClocks();    /* works because currentMove is 0 */
11488     } else if (gameMode == IcsExamining) {
11489         SendToICS(ics_prefix);
11490         SendToICS("tomove white\n");
11491     }
11492 }
11493
11494 void
11495 SetBlackToPlayEvent()
11496 {
11497     if (gameMode == EditPosition) {
11498         blackPlaysFirst = TRUE;
11499         currentMove = 1;        /* kludge */
11500         DisplayBothClocks();
11501         currentMove = 0;
11502     } else if (gameMode == IcsExamining) {
11503         SendToICS(ics_prefix);
11504         SendToICS("tomove black\n");
11505     }
11506 }
11507
11508 void
11509 EditPositionMenuEvent(selection, x, y)
11510      ChessSquare selection;
11511      int x, y;
11512 {
11513     char buf[MSG_SIZ];
11514     ChessSquare piece = boards[0][y][x];
11515
11516     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11517
11518     switch (selection) {
11519       case ClearBoard:
11520         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11521             SendToICS(ics_prefix);
11522             SendToICS("bsetup clear\n");
11523         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11524             SendToICS(ics_prefix);
11525             SendToICS("clearboard\n");
11526         } else {
11527             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11528                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11529                 for (y = 0; y < BOARD_HEIGHT; y++) {
11530                     if (gameMode == IcsExamining) {
11531                         if (boards[currentMove][y][x] != EmptySquare) {
11532                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11533                                     AAA + x, ONE + y);
11534                             SendToICS(buf);
11535                         }
11536                     } else {
11537                         boards[0][y][x] = p;
11538                     }
11539                 }
11540             }
11541         }
11542         if (gameMode == EditPosition) {
11543             DrawPosition(FALSE, boards[0]);
11544         }
11545         break;
11546
11547       case WhitePlay:
11548         SetWhiteToPlayEvent();
11549         break;
11550
11551       case BlackPlay:
11552         SetBlackToPlayEvent();
11553         break;
11554
11555       case EmptySquare:
11556         if (gameMode == IcsExamining) {
11557             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11558             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11559             SendToICS(buf);
11560         } else {
11561             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11562                 if(x == BOARD_LEFT-2) {
11563                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11564                     boards[0][y][1] = 0;
11565                 } else
11566                 if(x == BOARD_RGHT+1) {
11567                     if(y >= gameInfo.holdingsSize) break;
11568                     boards[0][y][BOARD_WIDTH-2] = 0;
11569                 } else break;
11570             }
11571             boards[0][y][x] = EmptySquare;
11572             DrawPosition(FALSE, boards[0]);
11573         }
11574         break;
11575
11576       case PromotePiece:
11577         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11578            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11579             selection = (ChessSquare) (PROMOTED piece);
11580         } else if(piece == EmptySquare) selection = WhiteSilver;
11581         else selection = (ChessSquare)((int)piece - 1);
11582         goto defaultlabel;
11583
11584       case DemotePiece:
11585         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11586            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11587             selection = (ChessSquare) (DEMOTED piece);
11588         } else if(piece == EmptySquare) selection = BlackSilver;
11589         else selection = (ChessSquare)((int)piece + 1);       
11590         goto defaultlabel;
11591
11592       case WhiteQueen:
11593       case BlackQueen:
11594         if(gameInfo.variant == VariantShatranj ||
11595            gameInfo.variant == VariantXiangqi  ||
11596            gameInfo.variant == VariantCourier  ||
11597            gameInfo.variant == VariantMakruk     )
11598             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11599         goto defaultlabel;
11600
11601       case WhiteKing:
11602       case BlackKing:
11603         if(gameInfo.variant == VariantXiangqi)
11604             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11605         if(gameInfo.variant == VariantKnightmate)
11606             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11607       default:
11608         defaultlabel:
11609         if (gameMode == IcsExamining) {
11610             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11611             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11612                     PieceToChar(selection), AAA + x, ONE + y);
11613             SendToICS(buf);
11614         } else {
11615             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11616                 int n;
11617                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11618                     n = PieceToNumber(selection - BlackPawn);
11619                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11620                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11621                     boards[0][BOARD_HEIGHT-1-n][1]++;
11622                 } else
11623                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11624                     n = PieceToNumber(selection);
11625                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11626                     boards[0][n][BOARD_WIDTH-1] = selection;
11627                     boards[0][n][BOARD_WIDTH-2]++;
11628                 }
11629             } else
11630             boards[0][y][x] = selection;
11631             DrawPosition(TRUE, boards[0]);
11632         }
11633         break;
11634     }
11635 }
11636
11637
11638 void
11639 DropMenuEvent(selection, x, y)
11640      ChessSquare selection;
11641      int x, y;
11642 {
11643     ChessMove moveType;
11644
11645     switch (gameMode) {
11646       case IcsPlayingWhite:
11647       case MachinePlaysBlack:
11648         if (!WhiteOnMove(currentMove)) {
11649             DisplayMoveError(_("It is Black's turn"));
11650             return;
11651         }
11652         moveType = WhiteDrop;
11653         break;
11654       case IcsPlayingBlack:
11655       case MachinePlaysWhite:
11656         if (WhiteOnMove(currentMove)) {
11657             DisplayMoveError(_("It is White's turn"));
11658             return;
11659         }
11660         moveType = BlackDrop;
11661         break;
11662       case EditGame:
11663         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11664         break;
11665       default:
11666         return;
11667     }
11668
11669     if (moveType == BlackDrop && selection < BlackPawn) {
11670       selection = (ChessSquare) ((int) selection
11671                                  + (int) BlackPawn - (int) WhitePawn);
11672     }
11673     if (boards[currentMove][y][x] != EmptySquare) {
11674         DisplayMoveError(_("That square is occupied"));
11675         return;
11676     }
11677
11678     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11679 }
11680
11681 void
11682 AcceptEvent()
11683 {
11684     /* Accept a pending offer of any kind from opponent */
11685     
11686     if (appData.icsActive) {
11687         SendToICS(ics_prefix);
11688         SendToICS("accept\n");
11689     } else if (cmailMsgLoaded) {
11690         if (currentMove == cmailOldMove &&
11691             commentList[cmailOldMove] != NULL &&
11692             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11693                    "Black offers a draw" : "White offers a draw")) {
11694             TruncateGame();
11695             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11697         } else {
11698             DisplayError(_("There is no pending offer on this move"), 0);
11699             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11700         }
11701     } else {
11702         /* Not used for offers from chess program */
11703     }
11704 }
11705
11706 void
11707 DeclineEvent()
11708 {
11709     /* Decline a pending offer of any kind from opponent */
11710     
11711     if (appData.icsActive) {
11712         SendToICS(ics_prefix);
11713         SendToICS("decline\n");
11714     } else if (cmailMsgLoaded) {
11715         if (currentMove == cmailOldMove &&
11716             commentList[cmailOldMove] != NULL &&
11717             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11718                    "Black offers a draw" : "White offers a draw")) {
11719 #ifdef NOTDEF
11720             AppendComment(cmailOldMove, "Draw declined", TRUE);
11721             DisplayComment(cmailOldMove - 1, "Draw declined");
11722 #endif /*NOTDEF*/
11723         } else {
11724             DisplayError(_("There is no pending offer on this move"), 0);
11725         }
11726     } else {
11727         /* Not used for offers from chess program */
11728     }
11729 }
11730
11731 void
11732 RematchEvent()
11733 {
11734     /* Issue ICS rematch command */
11735     if (appData.icsActive) {
11736         SendToICS(ics_prefix);
11737         SendToICS("rematch\n");
11738     }
11739 }
11740
11741 void
11742 CallFlagEvent()
11743 {
11744     /* Call your opponent's flag (claim a win on time) */
11745     if (appData.icsActive) {
11746         SendToICS(ics_prefix);
11747         SendToICS("flag\n");
11748     } else {
11749         switch (gameMode) {
11750           default:
11751             return;
11752           case MachinePlaysWhite:
11753             if (whiteFlag) {
11754                 if (blackFlag)
11755                   GameEnds(GameIsDrawn, "Both players ran out of time",
11756                            GE_PLAYER);
11757                 else
11758                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11759             } else {
11760                 DisplayError(_("Your opponent is not out of time"), 0);
11761             }
11762             break;
11763           case MachinePlaysBlack:
11764             if (blackFlag) {
11765                 if (whiteFlag)
11766                   GameEnds(GameIsDrawn, "Both players ran out of time",
11767                            GE_PLAYER);
11768                 else
11769                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11770             } else {
11771                 DisplayError(_("Your opponent is not out of time"), 0);
11772             }
11773             break;
11774         }
11775     }
11776 }
11777
11778 void
11779 DrawEvent()
11780 {
11781     /* Offer draw or accept pending draw offer from opponent */
11782     
11783     if (appData.icsActive) {
11784         /* Note: tournament rules require draw offers to be
11785            made after you make your move but before you punch
11786            your clock.  Currently ICS doesn't let you do that;
11787            instead, you immediately punch your clock after making
11788            a move, but you can offer a draw at any time. */
11789         
11790         SendToICS(ics_prefix);
11791         SendToICS("draw\n");
11792         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11793     } else if (cmailMsgLoaded) {
11794         if (currentMove == cmailOldMove &&
11795             commentList[cmailOldMove] != NULL &&
11796             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11797                    "Black offers a draw" : "White offers a draw")) {
11798             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11799             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11800         } else if (currentMove == cmailOldMove + 1) {
11801             char *offer = WhiteOnMove(cmailOldMove) ?
11802               "White offers a draw" : "Black offers a draw";
11803             AppendComment(currentMove, offer, TRUE);
11804             DisplayComment(currentMove - 1, offer);
11805             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11806         } else {
11807             DisplayError(_("You must make your move before offering a draw"), 0);
11808             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11809         }
11810     } else if (first.offeredDraw) {
11811         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11812     } else {
11813         if (first.sendDrawOffers) {
11814             SendToProgram("draw\n", &first);
11815             userOfferedDraw = TRUE;
11816         }
11817     }
11818 }
11819
11820 void
11821 AdjournEvent()
11822 {
11823     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11824     
11825     if (appData.icsActive) {
11826         SendToICS(ics_prefix);
11827         SendToICS("adjourn\n");
11828     } else {
11829         /* Currently GNU Chess doesn't offer or accept Adjourns */
11830     }
11831 }
11832
11833
11834 void
11835 AbortEvent()
11836 {
11837     /* Offer Abort or accept pending Abort offer from opponent */
11838     
11839     if (appData.icsActive) {
11840         SendToICS(ics_prefix);
11841         SendToICS("abort\n");
11842     } else {
11843         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11844     }
11845 }
11846
11847 void
11848 ResignEvent()
11849 {
11850     /* Resign.  You can do this even if it's not your turn. */
11851     
11852     if (appData.icsActive) {
11853         SendToICS(ics_prefix);
11854         SendToICS("resign\n");
11855     } else {
11856         switch (gameMode) {
11857           case MachinePlaysWhite:
11858             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11859             break;
11860           case MachinePlaysBlack:
11861             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11862             break;
11863           case EditGame:
11864             if (cmailMsgLoaded) {
11865                 TruncateGame();
11866                 if (WhiteOnMove(cmailOldMove)) {
11867                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11868                 } else {
11869                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11870                 }
11871                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11872             }
11873             break;
11874           default:
11875             break;
11876         }
11877     }
11878 }
11879
11880
11881 void
11882 StopObservingEvent()
11883 {
11884     /* Stop observing current games */
11885     SendToICS(ics_prefix);
11886     SendToICS("unobserve\n");
11887 }
11888
11889 void
11890 StopExaminingEvent()
11891 {
11892     /* Stop observing current game */
11893     SendToICS(ics_prefix);
11894     SendToICS("unexamine\n");
11895 }
11896
11897 void
11898 ForwardInner(target)
11899      int target;
11900 {
11901     int limit;
11902
11903     if (appData.debugMode)
11904         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11905                 target, currentMove, forwardMostMove);
11906
11907     if (gameMode == EditPosition)
11908       return;
11909
11910     if (gameMode == PlayFromGameFile && !pausing)
11911       PauseEvent();
11912     
11913     if (gameMode == IcsExamining && pausing)
11914       limit = pauseExamForwardMostMove;
11915     else
11916       limit = forwardMostMove;
11917     
11918     if (target > limit) target = limit;
11919
11920     if (target > 0 && moveList[target - 1][0]) {
11921         int fromX, fromY, toX, toY;
11922         toX = moveList[target - 1][2] - AAA;
11923         toY = moveList[target - 1][3] - ONE;
11924         if (moveList[target - 1][1] == '@') {
11925             if (appData.highlightLastMove) {
11926                 SetHighlights(-1, -1, toX, toY);
11927             }
11928         } else {
11929             fromX = moveList[target - 1][0] - AAA;
11930             fromY = moveList[target - 1][1] - ONE;
11931             if (target == currentMove + 1) {
11932                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11933             }
11934             if (appData.highlightLastMove) {
11935                 SetHighlights(fromX, fromY, toX, toY);
11936             }
11937         }
11938     }
11939     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11940         gameMode == Training || gameMode == PlayFromGameFile || 
11941         gameMode == AnalyzeFile) {
11942         while (currentMove < target) {
11943             SendMoveToProgram(currentMove++, &first);
11944         }
11945     } else {
11946         currentMove = target;
11947     }
11948     
11949     if (gameMode == EditGame || gameMode == EndOfGame) {
11950         whiteTimeRemaining = timeRemaining[0][currentMove];
11951         blackTimeRemaining = timeRemaining[1][currentMove];
11952     }
11953     DisplayBothClocks();
11954     DisplayMove(currentMove - 1);
11955     DrawPosition(FALSE, boards[currentMove]);
11956     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11957     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11958         DisplayComment(currentMove - 1, commentList[currentMove]);
11959     }
11960 }
11961
11962
11963 void
11964 ForwardEvent()
11965 {
11966     if (gameMode == IcsExamining && !pausing) {
11967         SendToICS(ics_prefix);
11968         SendToICS("forward\n");
11969     } else {
11970         ForwardInner(currentMove + 1);
11971     }
11972 }
11973
11974 void
11975 ToEndEvent()
11976 {
11977     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11978         /* to optimze, we temporarily turn off analysis mode while we feed
11979          * the remaining moves to the engine. Otherwise we get analysis output
11980          * after each move.
11981          */ 
11982         if (first.analysisSupport) {
11983           SendToProgram("exit\nforce\n", &first);
11984           first.analyzing = FALSE;
11985         }
11986     }
11987         
11988     if (gameMode == IcsExamining && !pausing) {
11989         SendToICS(ics_prefix);
11990         SendToICS("forward 999999\n");
11991     } else {
11992         ForwardInner(forwardMostMove);
11993     }
11994
11995     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11996         /* we have fed all the moves, so reactivate analysis mode */
11997         SendToProgram("analyze\n", &first);
11998         first.analyzing = TRUE;
11999         /*first.maybeThinking = TRUE;*/
12000         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12001     }
12002 }
12003
12004 void
12005 BackwardInner(target)
12006      int target;
12007 {
12008     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12009
12010     if (appData.debugMode)
12011         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12012                 target, currentMove, forwardMostMove);
12013
12014     if (gameMode == EditPosition) return;
12015     if (currentMove <= backwardMostMove) {
12016         ClearHighlights();
12017         DrawPosition(full_redraw, boards[currentMove]);
12018         return;
12019     }
12020     if (gameMode == PlayFromGameFile && !pausing)
12021       PauseEvent();
12022     
12023     if (moveList[target][0]) {
12024         int fromX, fromY, toX, toY;
12025         toX = moveList[target][2] - AAA;
12026         toY = moveList[target][3] - ONE;
12027         if (moveList[target][1] == '@') {
12028             if (appData.highlightLastMove) {
12029                 SetHighlights(-1, -1, toX, toY);
12030             }
12031         } else {
12032             fromX = moveList[target][0] - AAA;
12033             fromY = moveList[target][1] - ONE;
12034             if (target == currentMove - 1) {
12035                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12036             }
12037             if (appData.highlightLastMove) {
12038                 SetHighlights(fromX, fromY, toX, toY);
12039             }
12040         }
12041     }
12042     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12043         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12044         while (currentMove > target) {
12045             SendToProgram("undo\n", &first);
12046             currentMove--;
12047         }
12048     } else {
12049         currentMove = target;
12050     }
12051     
12052     if (gameMode == EditGame || gameMode == EndOfGame) {
12053         whiteTimeRemaining = timeRemaining[0][currentMove];
12054         blackTimeRemaining = timeRemaining[1][currentMove];
12055     }
12056     DisplayBothClocks();
12057     DisplayMove(currentMove - 1);
12058     DrawPosition(full_redraw, boards[currentMove]);
12059     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12060     // [HGM] PV info: routine tests if comment empty
12061     DisplayComment(currentMove - 1, commentList[currentMove]);
12062 }
12063
12064 void
12065 BackwardEvent()
12066 {
12067     if (gameMode == IcsExamining && !pausing) {
12068         SendToICS(ics_prefix);
12069         SendToICS("backward\n");
12070     } else {
12071         BackwardInner(currentMove - 1);
12072     }
12073 }
12074
12075 void
12076 ToStartEvent()
12077 {
12078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12079         /* to optimize, we temporarily turn off analysis mode while we undo
12080          * all the moves. Otherwise we get analysis output after each undo.
12081          */ 
12082         if (first.analysisSupport) {
12083           SendToProgram("exit\nforce\n", &first);
12084           first.analyzing = FALSE;
12085         }
12086     }
12087
12088     if (gameMode == IcsExamining && !pausing) {
12089         SendToICS(ics_prefix);
12090         SendToICS("backward 999999\n");
12091     } else {
12092         BackwardInner(backwardMostMove);
12093     }
12094
12095     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12096         /* we have fed all the moves, so reactivate analysis mode */
12097         SendToProgram("analyze\n", &first);
12098         first.analyzing = TRUE;
12099         /*first.maybeThinking = TRUE;*/
12100         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12101     }
12102 }
12103
12104 void
12105 ToNrEvent(int to)
12106 {
12107   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12108   if (to >= forwardMostMove) to = forwardMostMove;
12109   if (to <= backwardMostMove) to = backwardMostMove;
12110   if (to < currentMove) {
12111     BackwardInner(to);
12112   } else {
12113     ForwardInner(to);
12114   }
12115 }
12116
12117 void
12118 RevertEvent()
12119 {
12120     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12121         return;
12122     }
12123     if (gameMode != IcsExamining) {
12124         DisplayError(_("You are not examining a game"), 0);
12125         return;
12126     }
12127     if (pausing) {
12128         DisplayError(_("You can't revert while pausing"), 0);
12129         return;
12130     }
12131     SendToICS(ics_prefix);
12132     SendToICS("revert\n");
12133 }
12134
12135 void
12136 RetractMoveEvent()
12137 {
12138     switch (gameMode) {
12139       case MachinePlaysWhite:
12140       case MachinePlaysBlack:
12141         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12142             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12143             return;
12144         }
12145         if (forwardMostMove < 2) return;
12146         currentMove = forwardMostMove = forwardMostMove - 2;
12147         whiteTimeRemaining = timeRemaining[0][currentMove];
12148         blackTimeRemaining = timeRemaining[1][currentMove];
12149         DisplayBothClocks();
12150         DisplayMove(currentMove - 1);
12151         ClearHighlights();/*!! could figure this out*/
12152         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12153         SendToProgram("remove\n", &first);
12154         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12155         break;
12156
12157       case BeginningOfGame:
12158       default:
12159         break;
12160
12161       case IcsPlayingWhite:
12162       case IcsPlayingBlack:
12163         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12164             SendToICS(ics_prefix);
12165             SendToICS("takeback 2\n");
12166         } else {
12167             SendToICS(ics_prefix);
12168             SendToICS("takeback 1\n");
12169         }
12170         break;
12171     }
12172 }
12173
12174 void
12175 MoveNowEvent()
12176 {
12177     ChessProgramState *cps;
12178
12179     switch (gameMode) {
12180       case MachinePlaysWhite:
12181         if (!WhiteOnMove(forwardMostMove)) {
12182             DisplayError(_("It is your turn"), 0);
12183             return;
12184         }
12185         cps = &first;
12186         break;
12187       case MachinePlaysBlack:
12188         if (WhiteOnMove(forwardMostMove)) {
12189             DisplayError(_("It is your turn"), 0);
12190             return;
12191         }
12192         cps = &first;
12193         break;
12194       case TwoMachinesPlay:
12195         if (WhiteOnMove(forwardMostMove) ==
12196             (first.twoMachinesColor[0] == 'w')) {
12197             cps = &first;
12198         } else {
12199             cps = &second;
12200         }
12201         break;
12202       case BeginningOfGame:
12203       default:
12204         return;
12205     }
12206     SendToProgram("?\n", cps);
12207 }
12208
12209 void
12210 TruncateGameEvent()
12211 {
12212     EditGameEvent();
12213     if (gameMode != EditGame) return;
12214     TruncateGame();
12215 }
12216
12217 void
12218 TruncateGame()
12219 {
12220     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12221     if (forwardMostMove > currentMove) {
12222         if (gameInfo.resultDetails != NULL) {
12223             free(gameInfo.resultDetails);
12224             gameInfo.resultDetails = NULL;
12225             gameInfo.result = GameUnfinished;
12226         }
12227         forwardMostMove = currentMove;
12228         HistorySet(parseList, backwardMostMove, forwardMostMove,
12229                    currentMove-1);
12230     }
12231 }
12232
12233 void
12234 HintEvent()
12235 {
12236     if (appData.noChessProgram) return;
12237     switch (gameMode) {
12238       case MachinePlaysWhite:
12239         if (WhiteOnMove(forwardMostMove)) {
12240             DisplayError(_("Wait until your turn"), 0);
12241             return;
12242         }
12243         break;
12244       case BeginningOfGame:
12245       case MachinePlaysBlack:
12246         if (!WhiteOnMove(forwardMostMove)) {
12247             DisplayError(_("Wait until your turn"), 0);
12248             return;
12249         }
12250         break;
12251       default:
12252         DisplayError(_("No hint available"), 0);
12253         return;
12254     }
12255     SendToProgram("hint\n", &first);
12256     hintRequested = TRUE;
12257 }
12258
12259 void
12260 BookEvent()
12261 {
12262     if (appData.noChessProgram) return;
12263     switch (gameMode) {
12264       case MachinePlaysWhite:
12265         if (WhiteOnMove(forwardMostMove)) {
12266             DisplayError(_("Wait until your turn"), 0);
12267             return;
12268         }
12269         break;
12270       case BeginningOfGame:
12271       case MachinePlaysBlack:
12272         if (!WhiteOnMove(forwardMostMove)) {
12273             DisplayError(_("Wait until your turn"), 0);
12274             return;
12275         }
12276         break;
12277       case EditPosition:
12278         EditPositionDone(TRUE);
12279         break;
12280       case TwoMachinesPlay:
12281         return;
12282       default:
12283         break;
12284     }
12285     SendToProgram("bk\n", &first);
12286     bookOutput[0] = NULLCHAR;
12287     bookRequested = TRUE;
12288 }
12289
12290 void
12291 AboutGameEvent()
12292 {
12293     char *tags = PGNTags(&gameInfo);
12294     TagsPopUp(tags, CmailMsg());
12295     free(tags);
12296 }
12297
12298 /* end button procedures */
12299
12300 void
12301 PrintPosition(fp, move)
12302      FILE *fp;
12303      int move;
12304 {
12305     int i, j;
12306     
12307     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12308         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12309             char c = PieceToChar(boards[move][i][j]);
12310             fputc(c == 'x' ? '.' : c, fp);
12311             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12312         }
12313     }
12314     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12315       fprintf(fp, "white to play\n");
12316     else
12317       fprintf(fp, "black to play\n");
12318 }
12319
12320 void
12321 PrintOpponents(fp)
12322      FILE *fp;
12323 {
12324     if (gameInfo.white != NULL) {
12325         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12326     } else {
12327         fprintf(fp, "\n");
12328     }
12329 }
12330
12331 /* Find last component of program's own name, using some heuristics */
12332 void
12333 TidyProgramName(prog, host, buf)
12334      char *prog, *host, buf[MSG_SIZ];
12335 {
12336     char *p, *q;
12337     int local = (strcmp(host, "localhost") == 0);
12338     while (!local && (p = strchr(prog, ';')) != NULL) {
12339         p++;
12340         while (*p == ' ') p++;
12341         prog = p;
12342     }
12343     if (*prog == '"' || *prog == '\'') {
12344         q = strchr(prog + 1, *prog);
12345     } else {
12346         q = strchr(prog, ' ');
12347     }
12348     if (q == NULL) q = prog + strlen(prog);
12349     p = q;
12350     while (p >= prog && *p != '/' && *p != '\\') p--;
12351     p++;
12352     if(p == prog && *p == '"') p++;
12353     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12354     memcpy(buf, p, q - p);
12355     buf[q - p] = NULLCHAR;
12356     if (!local) {
12357         strcat(buf, "@");
12358         strcat(buf, host);
12359     }
12360 }
12361
12362 char *
12363 TimeControlTagValue()
12364 {
12365     char buf[MSG_SIZ];
12366     if (!appData.clockMode) {
12367         strcpy(buf, "-");
12368     } else if (movesPerSession > 0) {
12369         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12370     } else if (timeIncrement == 0) {
12371         sprintf(buf, "%ld", timeControl/1000);
12372     } else {
12373         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12374     }
12375     return StrSave(buf);
12376 }
12377
12378 void
12379 SetGameInfo()
12380 {
12381     /* This routine is used only for certain modes */
12382     VariantClass v = gameInfo.variant;
12383     ChessMove r = GameUnfinished;
12384     char *p = NULL;
12385
12386     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12387         r = gameInfo.result; 
12388         p = gameInfo.resultDetails; 
12389         gameInfo.resultDetails = NULL;
12390     }
12391     ClearGameInfo(&gameInfo);
12392     gameInfo.variant = v;
12393
12394     switch (gameMode) {
12395       case MachinePlaysWhite:
12396         gameInfo.event = StrSave( appData.pgnEventHeader );
12397         gameInfo.site = StrSave(HostName());
12398         gameInfo.date = PGNDate();
12399         gameInfo.round = StrSave("-");
12400         gameInfo.white = StrSave(first.tidy);
12401         gameInfo.black = StrSave(UserName());
12402         gameInfo.timeControl = TimeControlTagValue();
12403         break;
12404
12405       case MachinePlaysBlack:
12406         gameInfo.event = StrSave( appData.pgnEventHeader );
12407         gameInfo.site = StrSave(HostName());
12408         gameInfo.date = PGNDate();
12409         gameInfo.round = StrSave("-");
12410         gameInfo.white = StrSave(UserName());
12411         gameInfo.black = StrSave(first.tidy);
12412         gameInfo.timeControl = TimeControlTagValue();
12413         break;
12414
12415       case TwoMachinesPlay:
12416         gameInfo.event = StrSave( appData.pgnEventHeader );
12417         gameInfo.site = StrSave(HostName());
12418         gameInfo.date = PGNDate();
12419         if (matchGame > 0) {
12420             char buf[MSG_SIZ];
12421             sprintf(buf, "%d", matchGame);
12422             gameInfo.round = StrSave(buf);
12423         } else {
12424             gameInfo.round = StrSave("-");
12425         }
12426         if (first.twoMachinesColor[0] == 'w') {
12427             gameInfo.white = StrSave(first.tidy);
12428             gameInfo.black = StrSave(second.tidy);
12429         } else {
12430             gameInfo.white = StrSave(second.tidy);
12431             gameInfo.black = StrSave(first.tidy);
12432         }
12433         gameInfo.timeControl = TimeControlTagValue();
12434         break;
12435
12436       case EditGame:
12437         gameInfo.event = StrSave("Edited game");
12438         gameInfo.site = StrSave(HostName());
12439         gameInfo.date = PGNDate();
12440         gameInfo.round = StrSave("-");
12441         gameInfo.white = StrSave("-");
12442         gameInfo.black = StrSave("-");
12443         gameInfo.result = r;
12444         gameInfo.resultDetails = p;
12445         break;
12446
12447       case EditPosition:
12448         gameInfo.event = StrSave("Edited position");
12449         gameInfo.site = StrSave(HostName());
12450         gameInfo.date = PGNDate();
12451         gameInfo.round = StrSave("-");
12452         gameInfo.white = StrSave("-");
12453         gameInfo.black = StrSave("-");
12454         break;
12455
12456       case IcsPlayingWhite:
12457       case IcsPlayingBlack:
12458       case IcsObserving:
12459       case IcsExamining:
12460         break;
12461
12462       case PlayFromGameFile:
12463         gameInfo.event = StrSave("Game from non-PGN file");
12464         gameInfo.site = StrSave(HostName());
12465         gameInfo.date = PGNDate();
12466         gameInfo.round = StrSave("-");
12467         gameInfo.white = StrSave("?");
12468         gameInfo.black = StrSave("?");
12469         break;
12470
12471       default:
12472         break;
12473     }
12474 }
12475
12476 void
12477 ReplaceComment(index, text)
12478      int index;
12479      char *text;
12480 {
12481     int len;
12482
12483     while (*text == '\n') text++;
12484     len = strlen(text);
12485     while (len > 0 && text[len - 1] == '\n') len--;
12486
12487     if (commentList[index] != NULL)
12488       free(commentList[index]);
12489
12490     if (len == 0) {
12491         commentList[index] = NULL;
12492         return;
12493     }
12494   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12495       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12496       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12497     commentList[index] = (char *) malloc(len + 2);
12498     strncpy(commentList[index], text, len);
12499     commentList[index][len] = '\n';
12500     commentList[index][len + 1] = NULLCHAR;
12501   } else { 
12502     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12503     char *p;
12504     commentList[index] = (char *) malloc(len + 6);
12505     strcpy(commentList[index], "{\n");
12506     strncpy(commentList[index]+2, text, len);
12507     commentList[index][len+2] = NULLCHAR;
12508     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12509     strcat(commentList[index], "\n}\n");
12510   }
12511 }
12512
12513 void
12514 CrushCRs(text)
12515      char *text;
12516 {
12517   char *p = text;
12518   char *q = text;
12519   char ch;
12520
12521   do {
12522     ch = *p++;
12523     if (ch == '\r') continue;
12524     *q++ = ch;
12525   } while (ch != '\0');
12526 }
12527
12528 void
12529 AppendComment(index, text, addBraces)
12530      int index;
12531      char *text;
12532      Boolean addBraces; // [HGM] braces: tells if we should add {}
12533 {
12534     int oldlen, len;
12535     char *old;
12536
12537 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12538     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12539
12540     CrushCRs(text);
12541     while (*text == '\n') text++;
12542     len = strlen(text);
12543     while (len > 0 && text[len - 1] == '\n') len--;
12544
12545     if (len == 0) return;
12546
12547     if (commentList[index] != NULL) {
12548         old = commentList[index];
12549         oldlen = strlen(old);
12550         while(commentList[index][oldlen-1] ==  '\n')
12551           commentList[index][--oldlen] = NULLCHAR;
12552         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12553         strcpy(commentList[index], old);
12554         free(old);
12555         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12556         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12557           if(addBraces) addBraces = FALSE; else { text++; len--; }
12558           while (*text == '\n') { text++; len--; }
12559           commentList[index][--oldlen] = NULLCHAR;
12560       }
12561         if(addBraces) strcat(commentList[index], "\n{\n");
12562         else          strcat(commentList[index], "\n");
12563         strcat(commentList[index], text);
12564         if(addBraces) strcat(commentList[index], "\n}\n");
12565         else          strcat(commentList[index], "\n");
12566     } else {
12567         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12568         if(addBraces)
12569              strcpy(commentList[index], "{\n");
12570         else commentList[index][0] = NULLCHAR;
12571         strcat(commentList[index], text);
12572         strcat(commentList[index], "\n");
12573         if(addBraces) strcat(commentList[index], "}\n");
12574     }
12575 }
12576
12577 static char * FindStr( char * text, char * sub_text )
12578 {
12579     char * result = strstr( text, sub_text );
12580
12581     if( result != NULL ) {
12582         result += strlen( sub_text );
12583     }
12584
12585     return result;
12586 }
12587
12588 /* [AS] Try to extract PV info from PGN comment */
12589 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12590 char *GetInfoFromComment( int index, char * text )
12591 {
12592     char * sep = text;
12593
12594     if( text != NULL && index > 0 ) {
12595         int score = 0;
12596         int depth = 0;
12597         int time = -1, sec = 0, deci;
12598         char * s_eval = FindStr( text, "[%eval " );
12599         char * s_emt = FindStr( text, "[%emt " );
12600
12601         if( s_eval != NULL || s_emt != NULL ) {
12602             /* New style */
12603             char delim;
12604
12605             if( s_eval != NULL ) {
12606                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12607                     return text;
12608                 }
12609
12610                 if( delim != ']' ) {
12611                     return text;
12612                 }
12613             }
12614
12615             if( s_emt != NULL ) {
12616             }
12617                 return text;
12618         }
12619         else {
12620             /* We expect something like: [+|-]nnn.nn/dd */
12621             int score_lo = 0;
12622
12623             if(*text != '{') return text; // [HGM] braces: must be normal comment
12624
12625             sep = strchr( text, '/' );
12626             if( sep == NULL || sep < (text+4) ) {
12627                 return text;
12628             }
12629
12630             time = -1; sec = -1; deci = -1;
12631             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12632                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12633                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12634                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12635                 return text;
12636             }
12637
12638             if( score_lo < 0 || score_lo >= 100 ) {
12639                 return text;
12640             }
12641
12642             if(sec >= 0) time = 600*time + 10*sec; else
12643             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12644
12645             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12646
12647             /* [HGM] PV time: now locate end of PV info */
12648             while( *++sep >= '0' && *sep <= '9'); // strip depth
12649             if(time >= 0)
12650             while( *++sep >= '0' && *sep <= '9'); // strip time
12651             if(sec >= 0)
12652             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12653             if(deci >= 0)
12654             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12655             while(*sep == ' ') sep++;
12656         }
12657
12658         if( depth <= 0 ) {
12659             return text;
12660         }
12661
12662         if( time < 0 ) {
12663             time = -1;
12664         }
12665
12666         pvInfoList[index-1].depth = depth;
12667         pvInfoList[index-1].score = score;
12668         pvInfoList[index-1].time  = 10*time; // centi-sec
12669         if(*sep == '}') *sep = 0; else *--sep = '{';
12670     }
12671     return sep;
12672 }
12673
12674 void
12675 SendToProgram(message, cps)
12676      char *message;
12677      ChessProgramState *cps;
12678 {
12679     int count, outCount, error;
12680     char buf[MSG_SIZ];
12681
12682     if (cps->pr == NULL) return;
12683     Attention(cps);
12684     
12685     if (appData.debugMode) {
12686         TimeMark now;
12687         GetTimeMark(&now);
12688         fprintf(debugFP, "%ld >%-6s: %s", 
12689                 SubtractTimeMarks(&now, &programStartTime),
12690                 cps->which, message);
12691     }
12692     
12693     count = strlen(message);
12694     outCount = OutputToProcess(cps->pr, message, count, &error);
12695     if (outCount < count && !exiting 
12696                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12697         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12698         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12699             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12700                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12701                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12702             } else {
12703                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12704             }
12705             gameInfo.resultDetails = StrSave(buf);
12706         }
12707         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12708     }
12709 }
12710
12711 void
12712 ReceiveFromProgram(isr, closure, message, count, error)
12713      InputSourceRef isr;
12714      VOIDSTAR closure;
12715      char *message;
12716      int count;
12717      int error;
12718 {
12719     char *end_str;
12720     char buf[MSG_SIZ];
12721     ChessProgramState *cps = (ChessProgramState *)closure;
12722
12723     if (isr != cps->isr) return; /* Killed intentionally */
12724     if (count <= 0) {
12725         if (count == 0) {
12726             sprintf(buf,
12727                     _("Error: %s chess program (%s) exited unexpectedly"),
12728                     cps->which, cps->program);
12729         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12730                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12731                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12732                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12733                 } else {
12734                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12735                 }
12736                 gameInfo.resultDetails = StrSave(buf);
12737             }
12738             RemoveInputSource(cps->isr);
12739             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12740         } else {
12741             sprintf(buf,
12742                     _("Error reading from %s chess program (%s)"),
12743                     cps->which, cps->program);
12744             RemoveInputSource(cps->isr);
12745
12746             /* [AS] Program is misbehaving badly... kill it */
12747             if( count == -2 ) {
12748                 DestroyChildProcess( cps->pr, 9 );
12749                 cps->pr = NoProc;
12750             }
12751
12752             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12753         }
12754         return;
12755     }
12756     
12757     if ((end_str = strchr(message, '\r')) != NULL)
12758       *end_str = NULLCHAR;
12759     if ((end_str = strchr(message, '\n')) != NULL)
12760       *end_str = NULLCHAR;
12761     
12762     if (appData.debugMode) {
12763         TimeMark now; int print = 1;
12764         char *quote = ""; char c; int i;
12765
12766         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12767                 char start = message[0];
12768                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12769                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12770                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12771                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12772                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12773                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12774                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12775                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12776                         { quote = "# "; print = (appData.engineComments == 2); }
12777                 message[0] = start; // restore original message
12778         }
12779         if(print) {
12780                 GetTimeMark(&now);
12781                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12782                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12783                         quote,
12784                         message);
12785         }
12786     }
12787
12788     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12789     if (appData.icsEngineAnalyze) {
12790         if (strstr(message, "whisper") != NULL ||
12791              strstr(message, "kibitz") != NULL || 
12792             strstr(message, "tellics") != NULL) return;
12793     }
12794
12795     HandleMachineMove(message, cps);
12796 }
12797
12798
12799 void
12800 SendTimeControl(cps, mps, tc, inc, sd, st)
12801      ChessProgramState *cps;
12802      int mps, inc, sd, st;
12803      long tc;
12804 {
12805     char buf[MSG_SIZ];
12806     int seconds;
12807
12808     if( timeControl_2 > 0 ) {
12809         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12810             tc = timeControl_2;
12811         }
12812     }
12813     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12814     inc /= cps->timeOdds;
12815     st  /= cps->timeOdds;
12816
12817     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12818
12819     if (st > 0) {
12820       /* Set exact time per move, normally using st command */
12821       if (cps->stKludge) {
12822         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12823         seconds = st % 60;
12824         if (seconds == 0) {
12825           sprintf(buf, "level 1 %d\n", st/60);
12826         } else {
12827           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12828         }
12829       } else {
12830         sprintf(buf, "st %d\n", st);
12831       }
12832     } else {
12833       /* Set conventional or incremental time control, using level command */
12834       if (seconds == 0) {
12835         /* Note old gnuchess bug -- minutes:seconds used to not work.
12836            Fixed in later versions, but still avoid :seconds
12837            when seconds is 0. */
12838         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12839       } else {
12840         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12841                 seconds, inc/1000);
12842       }
12843     }
12844     SendToProgram(buf, cps);
12845
12846     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12847     /* Orthogonally, limit search to given depth */
12848     if (sd > 0) {
12849       if (cps->sdKludge) {
12850         sprintf(buf, "depth\n%d\n", sd);
12851       } else {
12852         sprintf(buf, "sd %d\n", sd);
12853       }
12854       SendToProgram(buf, cps);
12855     }
12856
12857     if(cps->nps > 0) { /* [HGM] nps */
12858         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12859         else {
12860                 sprintf(buf, "nps %d\n", cps->nps);
12861               SendToProgram(buf, cps);
12862         }
12863     }
12864 }
12865
12866 ChessProgramState *WhitePlayer()
12867 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12868 {
12869     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12870        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12871         return &second;
12872     return &first;
12873 }
12874
12875 void
12876 SendTimeRemaining(cps, machineWhite)
12877      ChessProgramState *cps;
12878      int /*boolean*/ machineWhite;
12879 {
12880     char message[MSG_SIZ];
12881     long time, otime;
12882
12883     /* Note: this routine must be called when the clocks are stopped
12884        or when they have *just* been set or switched; otherwise
12885        it will be off by the time since the current tick started.
12886     */
12887     if (machineWhite) {
12888         time = whiteTimeRemaining / 10;
12889         otime = blackTimeRemaining / 10;
12890     } else {
12891         time = blackTimeRemaining / 10;
12892         otime = whiteTimeRemaining / 10;
12893     }
12894     /* [HGM] translate opponent's time by time-odds factor */
12895     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12896     if (appData.debugMode) {
12897         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12898     }
12899
12900     if (time <= 0) time = 1;
12901     if (otime <= 0) otime = 1;
12902     
12903     sprintf(message, "time %ld\n", time);
12904     SendToProgram(message, cps);
12905
12906     sprintf(message, "otim %ld\n", otime);
12907     SendToProgram(message, cps);
12908 }
12909
12910 int
12911 BoolFeature(p, name, loc, cps)
12912      char **p;
12913      char *name;
12914      int *loc;
12915      ChessProgramState *cps;
12916 {
12917   char buf[MSG_SIZ];
12918   int len = strlen(name);
12919   int val;
12920   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12921     (*p) += len + 1;
12922     sscanf(*p, "%d", &val);
12923     *loc = (val != 0);
12924     while (**p && **p != ' ') (*p)++;
12925     sprintf(buf, "accepted %s\n", name);
12926     SendToProgram(buf, cps);
12927     return TRUE;
12928   }
12929   return FALSE;
12930 }
12931
12932 int
12933 IntFeature(p, name, loc, cps)
12934      char **p;
12935      char *name;
12936      int *loc;
12937      ChessProgramState *cps;
12938 {
12939   char buf[MSG_SIZ];
12940   int len = strlen(name);
12941   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12942     (*p) += len + 1;
12943     sscanf(*p, "%d", loc);
12944     while (**p && **p != ' ') (*p)++;
12945     sprintf(buf, "accepted %s\n", name);
12946     SendToProgram(buf, cps);
12947     return TRUE;
12948   }
12949   return FALSE;
12950 }
12951
12952 int
12953 StringFeature(p, name, loc, cps)
12954      char **p;
12955      char *name;
12956      char loc[];
12957      ChessProgramState *cps;
12958 {
12959   char buf[MSG_SIZ];
12960   int len = strlen(name);
12961   if (strncmp((*p), name, len) == 0
12962       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12963     (*p) += len + 2;
12964     sscanf(*p, "%[^\"]", loc);
12965     while (**p && **p != '\"') (*p)++;
12966     if (**p == '\"') (*p)++;
12967     sprintf(buf, "accepted %s\n", name);
12968     SendToProgram(buf, cps);
12969     return TRUE;
12970   }
12971   return FALSE;
12972 }
12973
12974 int 
12975 ParseOption(Option *opt, ChessProgramState *cps)
12976 // [HGM] options: process the string that defines an engine option, and determine
12977 // name, type, default value, and allowed value range
12978 {
12979         char *p, *q, buf[MSG_SIZ];
12980         int n, min = (-1)<<31, max = 1<<31, def;
12981
12982         if(p = strstr(opt->name, " -spin ")) {
12983             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12984             if(max < min) max = min; // enforce consistency
12985             if(def < min) def = min;
12986             if(def > max) def = max;
12987             opt->value = def;
12988             opt->min = min;
12989             opt->max = max;
12990             opt->type = Spin;
12991         } else if((p = strstr(opt->name, " -slider "))) {
12992             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12993             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12994             if(max < min) max = min; // enforce consistency
12995             if(def < min) def = min;
12996             if(def > max) def = max;
12997             opt->value = def;
12998             opt->min = min;
12999             opt->max = max;
13000             opt->type = Spin; // Slider;
13001         } else if((p = strstr(opt->name, " -string "))) {
13002             opt->textValue = p+9;
13003             opt->type = TextBox;
13004         } else if((p = strstr(opt->name, " -file "))) {
13005             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13006             opt->textValue = p+7;
13007             opt->type = TextBox; // FileName;
13008         } else if((p = strstr(opt->name, " -path "))) {
13009             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13010             opt->textValue = p+7;
13011             opt->type = TextBox; // PathName;
13012         } else if(p = strstr(opt->name, " -check ")) {
13013             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13014             opt->value = (def != 0);
13015             opt->type = CheckBox;
13016         } else if(p = strstr(opt->name, " -combo ")) {
13017             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13018             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13019             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13020             opt->value = n = 0;
13021             while(q = StrStr(q, " /// ")) {
13022                 n++; *q = 0;    // count choices, and null-terminate each of them
13023                 q += 5;
13024                 if(*q == '*') { // remember default, which is marked with * prefix
13025                     q++;
13026                     opt->value = n;
13027                 }
13028                 cps->comboList[cps->comboCnt++] = q;
13029             }
13030             cps->comboList[cps->comboCnt++] = NULL;
13031             opt->max = n + 1;
13032             opt->type = ComboBox;
13033         } else if(p = strstr(opt->name, " -button")) {
13034             opt->type = Button;
13035         } else if(p = strstr(opt->name, " -save")) {
13036             opt->type = SaveButton;
13037         } else return FALSE;
13038         *p = 0; // terminate option name
13039         // now look if the command-line options define a setting for this engine option.
13040         if(cps->optionSettings && cps->optionSettings[0])
13041             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13042         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13043                 sprintf(buf, "option %s", p);
13044                 if(p = strstr(buf, ",")) *p = 0;
13045                 strcat(buf, "\n");
13046                 SendToProgram(buf, cps);
13047         }
13048         return TRUE;
13049 }
13050
13051 void
13052 FeatureDone(cps, val)
13053      ChessProgramState* cps;
13054      int val;
13055 {
13056   DelayedEventCallback cb = GetDelayedEvent();
13057   if ((cb == InitBackEnd3 && cps == &first) ||
13058       (cb == TwoMachinesEventIfReady && cps == &second)) {
13059     CancelDelayedEvent();
13060     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13061   }
13062   cps->initDone = val;
13063 }
13064
13065 /* Parse feature command from engine */
13066 void
13067 ParseFeatures(args, cps)
13068      char* args;
13069      ChessProgramState *cps;  
13070 {
13071   char *p = args;
13072   char *q;
13073   int val;
13074   char buf[MSG_SIZ];
13075
13076   for (;;) {
13077     while (*p == ' ') p++;
13078     if (*p == NULLCHAR) return;
13079
13080     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13081     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13082     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13083     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13084     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13085     if (BoolFeature(&p, "reuse", &val, cps)) {
13086       /* Engine can disable reuse, but can't enable it if user said no */
13087       if (!val) cps->reuse = FALSE;
13088       continue;
13089     }
13090     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13091     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13092       if (gameMode == TwoMachinesPlay) {
13093         DisplayTwoMachinesTitle();
13094       } else {
13095         DisplayTitle("");
13096       }
13097       continue;
13098     }
13099     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13100     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13101     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13102     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13103     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13104     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13105     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13106     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13107     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13108     if (IntFeature(&p, "done", &val, cps)) {
13109       FeatureDone(cps, val);
13110       continue;
13111     }
13112     /* Added by Tord: */
13113     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13114     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13115     /* End of additions by Tord */
13116
13117     /* [HGM] added features: */
13118     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13119     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13120     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13121     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13122     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13123     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13124     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13125         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13126             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13127             SendToProgram(buf, cps);
13128             continue;
13129         }
13130         if(cps->nrOptions >= MAX_OPTIONS) {
13131             cps->nrOptions--;
13132             sprintf(buf, "%s engine has too many options\n", cps->which);
13133             DisplayError(buf, 0);
13134         }
13135         continue;
13136     }
13137     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13138     /* End of additions by HGM */
13139
13140     /* unknown feature: complain and skip */
13141     q = p;
13142     while (*q && *q != '=') q++;
13143     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13144     SendToProgram(buf, cps);
13145     p = q;
13146     if (*p == '=') {
13147       p++;
13148       if (*p == '\"') {
13149         p++;
13150         while (*p && *p != '\"') p++;
13151         if (*p == '\"') p++;
13152       } else {
13153         while (*p && *p != ' ') p++;
13154       }
13155     }
13156   }
13157
13158 }
13159
13160 void
13161 PeriodicUpdatesEvent(newState)
13162      int newState;
13163 {
13164     if (newState == appData.periodicUpdates)
13165       return;
13166
13167     appData.periodicUpdates=newState;
13168
13169     /* Display type changes, so update it now */
13170 //    DisplayAnalysis();
13171
13172     /* Get the ball rolling again... */
13173     if (newState) {
13174         AnalysisPeriodicEvent(1);
13175         StartAnalysisClock();
13176     }
13177 }
13178
13179 void
13180 PonderNextMoveEvent(newState)
13181      int newState;
13182 {
13183     if (newState == appData.ponderNextMove) return;
13184     if (gameMode == EditPosition) EditPositionDone(TRUE);
13185     if (newState) {
13186         SendToProgram("hard\n", &first);
13187         if (gameMode == TwoMachinesPlay) {
13188             SendToProgram("hard\n", &second);
13189         }
13190     } else {
13191         SendToProgram("easy\n", &first);
13192         thinkOutput[0] = NULLCHAR;
13193         if (gameMode == TwoMachinesPlay) {
13194             SendToProgram("easy\n", &second);
13195         }
13196     }
13197     appData.ponderNextMove = newState;
13198 }
13199
13200 void
13201 NewSettingEvent(option, command, value)
13202      char *command;
13203      int option, value;
13204 {
13205     char buf[MSG_SIZ];
13206
13207     if (gameMode == EditPosition) EditPositionDone(TRUE);
13208     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13209     SendToProgram(buf, &first);
13210     if (gameMode == TwoMachinesPlay) {
13211         SendToProgram(buf, &second);
13212     }
13213 }
13214
13215 void
13216 ShowThinkingEvent()
13217 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13218 {
13219     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13220     int newState = appData.showThinking
13221         // [HGM] thinking: other features now need thinking output as well
13222         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13223     
13224     if (oldState == newState) return;
13225     oldState = newState;
13226     if (gameMode == EditPosition) EditPositionDone(TRUE);
13227     if (oldState) {
13228         SendToProgram("post\n", &first);
13229         if (gameMode == TwoMachinesPlay) {
13230             SendToProgram("post\n", &second);
13231         }
13232     } else {
13233         SendToProgram("nopost\n", &first);
13234         thinkOutput[0] = NULLCHAR;
13235         if (gameMode == TwoMachinesPlay) {
13236             SendToProgram("nopost\n", &second);
13237         }
13238     }
13239 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13240 }
13241
13242 void
13243 AskQuestionEvent(title, question, replyPrefix, which)
13244      char *title; char *question; char *replyPrefix; char *which;
13245 {
13246   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13247   if (pr == NoProc) return;
13248   AskQuestion(title, question, replyPrefix, pr);
13249 }
13250
13251 void
13252 DisplayMove(moveNumber)
13253      int moveNumber;
13254 {
13255     char message[MSG_SIZ];
13256     char res[MSG_SIZ];
13257     char cpThinkOutput[MSG_SIZ];
13258
13259     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13260     
13261     if (moveNumber == forwardMostMove - 1 || 
13262         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13263
13264         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13265
13266         if (strchr(cpThinkOutput, '\n')) {
13267             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13268         }
13269     } else {
13270         *cpThinkOutput = NULLCHAR;
13271     }
13272
13273     /* [AS] Hide thinking from human user */
13274     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13275         *cpThinkOutput = NULLCHAR;
13276         if( thinkOutput[0] != NULLCHAR ) {
13277             int i;
13278
13279             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13280                 cpThinkOutput[i] = '.';
13281             }
13282             cpThinkOutput[i] = NULLCHAR;
13283             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13284         }
13285     }
13286
13287     if (moveNumber == forwardMostMove - 1 &&
13288         gameInfo.resultDetails != NULL) {
13289         if (gameInfo.resultDetails[0] == NULLCHAR) {
13290             sprintf(res, " %s", PGNResult(gameInfo.result));
13291         } else {
13292             sprintf(res, " {%s} %s",
13293                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13294         }
13295     } else {
13296         res[0] = NULLCHAR;
13297     }
13298
13299     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13300         DisplayMessage(res, cpThinkOutput);
13301     } else {
13302         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13303                 WhiteOnMove(moveNumber) ? " " : ".. ",
13304                 parseList[moveNumber], res);
13305         DisplayMessage(message, cpThinkOutput);
13306     }
13307 }
13308
13309 void
13310 DisplayComment(moveNumber, text)
13311      int moveNumber;
13312      char *text;
13313 {
13314     char title[MSG_SIZ];
13315     char buf[8000]; // comment can be long!
13316     int score, depth;
13317     
13318     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13319       strcpy(title, "Comment");
13320     } else {
13321       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13322               WhiteOnMove(moveNumber) ? " " : ".. ",
13323               parseList[moveNumber]);
13324     }
13325     // [HGM] PV info: display PV info together with (or as) comment
13326     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13327       if(text == NULL) text = "";                                           
13328       score = pvInfoList[moveNumber].score;
13329       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13330               depth, (pvInfoList[moveNumber].time+50)/100, text);
13331       text = buf;
13332     }
13333     if (text != NULL && (appData.autoDisplayComment || commentUp))
13334         CommentPopUp(title, text);
13335 }
13336
13337 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13338  * might be busy thinking or pondering.  It can be omitted if your
13339  * gnuchess is configured to stop thinking immediately on any user
13340  * input.  However, that gnuchess feature depends on the FIONREAD
13341  * ioctl, which does not work properly on some flavors of Unix.
13342  */
13343 void
13344 Attention(cps)
13345      ChessProgramState *cps;
13346 {
13347 #if ATTENTION
13348     if (!cps->useSigint) return;
13349     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13350     switch (gameMode) {
13351       case MachinePlaysWhite:
13352       case MachinePlaysBlack:
13353       case TwoMachinesPlay:
13354       case IcsPlayingWhite:
13355       case IcsPlayingBlack:
13356       case AnalyzeMode:
13357       case AnalyzeFile:
13358         /* Skip if we know it isn't thinking */
13359         if (!cps->maybeThinking) return;
13360         if (appData.debugMode)
13361           fprintf(debugFP, "Interrupting %s\n", cps->which);
13362         InterruptChildProcess(cps->pr);
13363         cps->maybeThinking = FALSE;
13364         break;
13365       default:
13366         break;
13367     }
13368 #endif /*ATTENTION*/
13369 }
13370
13371 int
13372 CheckFlags()
13373 {
13374     if (whiteTimeRemaining <= 0) {
13375         if (!whiteFlag) {
13376             whiteFlag = TRUE;
13377             if (appData.icsActive) {
13378                 if (appData.autoCallFlag &&
13379                     gameMode == IcsPlayingBlack && !blackFlag) {
13380                   SendToICS(ics_prefix);
13381                   SendToICS("flag\n");
13382                 }
13383             } else {
13384                 if (blackFlag) {
13385                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13386                 } else {
13387                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13388                     if (appData.autoCallFlag) {
13389                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13390                         return TRUE;
13391                     }
13392                 }
13393             }
13394         }
13395     }
13396     if (blackTimeRemaining <= 0) {
13397         if (!blackFlag) {
13398             blackFlag = TRUE;
13399             if (appData.icsActive) {
13400                 if (appData.autoCallFlag &&
13401                     gameMode == IcsPlayingWhite && !whiteFlag) {
13402                   SendToICS(ics_prefix);
13403                   SendToICS("flag\n");
13404                 }
13405             } else {
13406                 if (whiteFlag) {
13407                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13408                 } else {
13409                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13410                     if (appData.autoCallFlag) {
13411                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13412                         return TRUE;
13413                     }
13414                 }
13415             }
13416         }
13417     }
13418     return FALSE;
13419 }
13420
13421 void
13422 CheckTimeControl()
13423 {
13424     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13425         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13426
13427     /*
13428      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13429      */
13430     if ( !WhiteOnMove(forwardMostMove) )
13431         /* White made time control */
13432         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13433         /* [HGM] time odds: correct new time quota for time odds! */
13434                                             / WhitePlayer()->timeOdds;
13435       else
13436         /* Black made time control */
13437         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13438                                             / WhitePlayer()->other->timeOdds;
13439 }
13440
13441 void
13442 DisplayBothClocks()
13443 {
13444     int wom = gameMode == EditPosition ?
13445       !blackPlaysFirst : WhiteOnMove(currentMove);
13446     DisplayWhiteClock(whiteTimeRemaining, wom);
13447     DisplayBlackClock(blackTimeRemaining, !wom);
13448 }
13449
13450
13451 /* Timekeeping seems to be a portability nightmare.  I think everyone
13452    has ftime(), but I'm really not sure, so I'm including some ifdefs
13453    to use other calls if you don't.  Clocks will be less accurate if
13454    you have neither ftime nor gettimeofday.
13455 */
13456
13457 /* VS 2008 requires the #include outside of the function */
13458 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13459 #include <sys/timeb.h>
13460 #endif
13461
13462 /* Get the current time as a TimeMark */
13463 void
13464 GetTimeMark(tm)
13465      TimeMark *tm;
13466 {
13467 #if HAVE_GETTIMEOFDAY
13468
13469     struct timeval timeVal;
13470     struct timezone timeZone;
13471
13472     gettimeofday(&timeVal, &timeZone);
13473     tm->sec = (long) timeVal.tv_sec; 
13474     tm->ms = (int) (timeVal.tv_usec / 1000L);
13475
13476 #else /*!HAVE_GETTIMEOFDAY*/
13477 #if HAVE_FTIME
13478
13479 // include <sys/timeb.h> / moved to just above start of function
13480     struct timeb timeB;
13481
13482     ftime(&timeB);
13483     tm->sec = (long) timeB.time;
13484     tm->ms = (int) timeB.millitm;
13485
13486 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13487     tm->sec = (long) time(NULL);
13488     tm->ms = 0;
13489 #endif
13490 #endif
13491 }
13492
13493 /* Return the difference in milliseconds between two
13494    time marks.  We assume the difference will fit in a long!
13495 */
13496 long
13497 SubtractTimeMarks(tm2, tm1)
13498      TimeMark *tm2, *tm1;
13499 {
13500     return 1000L*(tm2->sec - tm1->sec) +
13501            (long) (tm2->ms - tm1->ms);
13502 }
13503
13504
13505 /*
13506  * Code to manage the game clocks.
13507  *
13508  * In tournament play, black starts the clock and then white makes a move.
13509  * We give the human user a slight advantage if he is playing white---the
13510  * clocks don't run until he makes his first move, so it takes zero time.
13511  * Also, we don't account for network lag, so we could get out of sync
13512  * with GNU Chess's clock -- but then, referees are always right.  
13513  */
13514
13515 static TimeMark tickStartTM;
13516 static long intendedTickLength;
13517
13518 long
13519 NextTickLength(timeRemaining)
13520      long timeRemaining;
13521 {
13522     long nominalTickLength, nextTickLength;
13523
13524     if (timeRemaining > 0L && timeRemaining <= 10000L)
13525       nominalTickLength = 100L;
13526     else
13527       nominalTickLength = 1000L;
13528     nextTickLength = timeRemaining % nominalTickLength;
13529     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13530
13531     return nextTickLength;
13532 }
13533
13534 /* Adjust clock one minute up or down */
13535 void
13536 AdjustClock(Boolean which, int dir)
13537 {
13538     if(which) blackTimeRemaining += 60000*dir;
13539     else      whiteTimeRemaining += 60000*dir;
13540     DisplayBothClocks();
13541 }
13542
13543 /* Stop clocks and reset to a fresh time control */
13544 void
13545 ResetClocks() 
13546 {
13547     (void) StopClockTimer();
13548     if (appData.icsActive) {
13549         whiteTimeRemaining = blackTimeRemaining = 0;
13550     } else if (searchTime) {
13551         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13552         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13553     } else { /* [HGM] correct new time quote for time odds */
13554         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13555         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13556     }
13557     if (whiteFlag || blackFlag) {
13558         DisplayTitle("");
13559         whiteFlag = blackFlag = FALSE;
13560     }
13561     DisplayBothClocks();
13562 }
13563
13564 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13565
13566 /* Decrement running clock by amount of time that has passed */
13567 void
13568 DecrementClocks()
13569 {
13570     long timeRemaining;
13571     long lastTickLength, fudge;
13572     TimeMark now;
13573
13574     if (!appData.clockMode) return;
13575     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13576         
13577     GetTimeMark(&now);
13578
13579     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13580
13581     /* Fudge if we woke up a little too soon */
13582     fudge = intendedTickLength - lastTickLength;
13583     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13584
13585     if (WhiteOnMove(forwardMostMove)) {
13586         if(whiteNPS >= 0) lastTickLength = 0;
13587         timeRemaining = whiteTimeRemaining -= lastTickLength;
13588         DisplayWhiteClock(whiteTimeRemaining - fudge,
13589                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13590     } else {
13591         if(blackNPS >= 0) lastTickLength = 0;
13592         timeRemaining = blackTimeRemaining -= lastTickLength;
13593         DisplayBlackClock(blackTimeRemaining - fudge,
13594                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13595     }
13596
13597     if (CheckFlags()) return;
13598         
13599     tickStartTM = now;
13600     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13601     StartClockTimer(intendedTickLength);
13602
13603     /* if the time remaining has fallen below the alarm threshold, sound the
13604      * alarm. if the alarm has sounded and (due to a takeback or time control
13605      * with increment) the time remaining has increased to a level above the
13606      * threshold, reset the alarm so it can sound again. 
13607      */
13608     
13609     if (appData.icsActive && appData.icsAlarm) {
13610
13611         /* make sure we are dealing with the user's clock */
13612         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13613                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13614            )) return;
13615
13616         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13617             alarmSounded = FALSE;
13618         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13619             PlayAlarmSound();
13620             alarmSounded = TRUE;
13621         }
13622     }
13623 }
13624
13625
13626 /* A player has just moved, so stop the previously running
13627    clock and (if in clock mode) start the other one.
13628    We redisplay both clocks in case we're in ICS mode, because
13629    ICS gives us an update to both clocks after every move.
13630    Note that this routine is called *after* forwardMostMove
13631    is updated, so the last fractional tick must be subtracted
13632    from the color that is *not* on move now.
13633 */
13634 void
13635 SwitchClocks()
13636 {
13637     long lastTickLength;
13638     TimeMark now;
13639     int flagged = FALSE;
13640
13641     GetTimeMark(&now);
13642
13643     if (StopClockTimer() && appData.clockMode) {
13644         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13645         if (WhiteOnMove(forwardMostMove)) {
13646             if(blackNPS >= 0) lastTickLength = 0;
13647             blackTimeRemaining -= lastTickLength;
13648            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13649 //         if(pvInfoList[forwardMostMove-1].time == -1)
13650                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13651                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13652         } else {
13653            if(whiteNPS >= 0) lastTickLength = 0;
13654            whiteTimeRemaining -= lastTickLength;
13655            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13656 //         if(pvInfoList[forwardMostMove-1].time == -1)
13657                  pvInfoList[forwardMostMove-1].time = 
13658                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13659         }
13660         flagged = CheckFlags();
13661     }
13662     CheckTimeControl();
13663
13664     if (flagged || !appData.clockMode) return;
13665
13666     switch (gameMode) {
13667       case MachinePlaysBlack:
13668       case MachinePlaysWhite:
13669       case BeginningOfGame:
13670         if (pausing) return;
13671         break;
13672
13673       case EditGame:
13674       case PlayFromGameFile:
13675       case IcsExamining:
13676         return;
13677
13678       default:
13679         break;
13680     }
13681
13682     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13683         if(WhiteOnMove(forwardMostMove))
13684              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13685         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13686     }
13687
13688     tickStartTM = now;
13689     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13690       whiteTimeRemaining : blackTimeRemaining);
13691     StartClockTimer(intendedTickLength);
13692 }
13693         
13694
13695 /* Stop both clocks */
13696 void
13697 StopClocks()
13698 {       
13699     long lastTickLength;
13700     TimeMark now;
13701
13702     if (!StopClockTimer()) return;
13703     if (!appData.clockMode) return;
13704
13705     GetTimeMark(&now);
13706
13707     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13708     if (WhiteOnMove(forwardMostMove)) {
13709         if(whiteNPS >= 0) lastTickLength = 0;
13710         whiteTimeRemaining -= lastTickLength;
13711         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13712     } else {
13713         if(blackNPS >= 0) lastTickLength = 0;
13714         blackTimeRemaining -= lastTickLength;
13715         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13716     }
13717     CheckFlags();
13718 }
13719         
13720 /* Start clock of player on move.  Time may have been reset, so
13721    if clock is already running, stop and restart it. */
13722 void
13723 StartClocks()
13724 {
13725     (void) StopClockTimer(); /* in case it was running already */
13726     DisplayBothClocks();
13727     if (CheckFlags()) return;
13728
13729     if (!appData.clockMode) return;
13730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13731
13732     GetTimeMark(&tickStartTM);
13733     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13734       whiteTimeRemaining : blackTimeRemaining);
13735
13736    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13737     whiteNPS = blackNPS = -1; 
13738     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13739        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13740         whiteNPS = first.nps;
13741     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13742        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13743         blackNPS = first.nps;
13744     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13745         whiteNPS = second.nps;
13746     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13747         blackNPS = second.nps;
13748     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13749
13750     StartClockTimer(intendedTickLength);
13751 }
13752
13753 char *
13754 TimeString(ms)
13755      long ms;
13756 {
13757     long second, minute, hour, day;
13758     char *sign = "";
13759     static char buf[32];
13760     
13761     if (ms > 0 && ms <= 9900) {
13762       /* convert milliseconds to tenths, rounding up */
13763       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13764
13765       sprintf(buf, " %03.1f ", tenths/10.0);
13766       return buf;
13767     }
13768
13769     /* convert milliseconds to seconds, rounding up */
13770     /* use floating point to avoid strangeness of integer division
13771        with negative dividends on many machines */
13772     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13773
13774     if (second < 0) {
13775         sign = "-";
13776         second = -second;
13777     }
13778     
13779     day = second / (60 * 60 * 24);
13780     second = second % (60 * 60 * 24);
13781     hour = second / (60 * 60);
13782     second = second % (60 * 60);
13783     minute = second / 60;
13784     second = second % 60;
13785     
13786     if (day > 0)
13787       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13788               sign, day, hour, minute, second);
13789     else if (hour > 0)
13790       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13791     else
13792       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13793     
13794     return buf;
13795 }
13796
13797
13798 /*
13799  * This is necessary because some C libraries aren't ANSI C compliant yet.
13800  */
13801 char *
13802 StrStr(string, match)
13803      char *string, *match;
13804 {
13805     int i, length;
13806     
13807     length = strlen(match);
13808     
13809     for (i = strlen(string) - length; i >= 0; i--, string++)
13810       if (!strncmp(match, string, length))
13811         return string;
13812     
13813     return NULL;
13814 }
13815
13816 char *
13817 StrCaseStr(string, match)
13818      char *string, *match;
13819 {
13820     int i, j, length;
13821     
13822     length = strlen(match);
13823     
13824     for (i = strlen(string) - length; i >= 0; i--, string++) {
13825         for (j = 0; j < length; j++) {
13826             if (ToLower(match[j]) != ToLower(string[j]))
13827               break;
13828         }
13829         if (j == length) return string;
13830     }
13831
13832     return NULL;
13833 }
13834
13835 #ifndef _amigados
13836 int
13837 StrCaseCmp(s1, s2)
13838      char *s1, *s2;
13839 {
13840     char c1, c2;
13841     
13842     for (;;) {
13843         c1 = ToLower(*s1++);
13844         c2 = ToLower(*s2++);
13845         if (c1 > c2) return 1;
13846         if (c1 < c2) return -1;
13847         if (c1 == NULLCHAR) return 0;
13848     }
13849 }
13850
13851
13852 int
13853 ToLower(c)
13854      int c;
13855 {
13856     return isupper(c) ? tolower(c) : c;
13857 }
13858
13859
13860 int
13861 ToUpper(c)
13862      int c;
13863 {
13864     return islower(c) ? toupper(c) : c;
13865 }
13866 #endif /* !_amigados    */
13867
13868 char *
13869 StrSave(s)
13870      char *s;
13871 {
13872     char *ret;
13873
13874     if ((ret = (char *) malloc(strlen(s) + 1))) {
13875         strcpy(ret, s);
13876     }
13877     return ret;
13878 }
13879
13880 char *
13881 StrSavePtr(s, savePtr)
13882      char *s, **savePtr;
13883 {
13884     if (*savePtr) {
13885         free(*savePtr);
13886     }
13887     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13888         strcpy(*savePtr, s);
13889     }
13890     return(*savePtr);
13891 }
13892
13893 char *
13894 PGNDate()
13895 {
13896     time_t clock;
13897     struct tm *tm;
13898     char buf[MSG_SIZ];
13899
13900     clock = time((time_t *)NULL);
13901     tm = localtime(&clock);
13902     sprintf(buf, "%04d.%02d.%02d",
13903             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13904     return StrSave(buf);
13905 }
13906
13907
13908 char *
13909 PositionToFEN(move, overrideCastling)
13910      int move;
13911      char *overrideCastling;
13912 {
13913     int i, j, fromX, fromY, toX, toY;
13914     int whiteToPlay;
13915     char buf[128];
13916     char *p, *q;
13917     int emptycount;
13918     ChessSquare piece;
13919
13920     whiteToPlay = (gameMode == EditPosition) ?
13921       !blackPlaysFirst : (move % 2 == 0);
13922     p = buf;
13923
13924     /* Piece placement data */
13925     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13926         emptycount = 0;
13927         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13928             if (boards[move][i][j] == EmptySquare) {
13929                 emptycount++;
13930             } else { ChessSquare piece = boards[move][i][j];
13931                 if (emptycount > 0) {
13932                     if(emptycount<10) /* [HGM] can be >= 10 */
13933                         *p++ = '0' + emptycount;
13934                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13935                     emptycount = 0;
13936                 }
13937                 if(PieceToChar(piece) == '+') {
13938                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13939                     *p++ = '+';
13940                     piece = (ChessSquare)(DEMOTED piece);
13941                 } 
13942                 *p++ = PieceToChar(piece);
13943                 if(p[-1] == '~') {
13944                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13945                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13946                     *p++ = '~';
13947                 }
13948             }
13949         }
13950         if (emptycount > 0) {
13951             if(emptycount<10) /* [HGM] can be >= 10 */
13952                 *p++ = '0' + emptycount;
13953             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13954             emptycount = 0;
13955         }
13956         *p++ = '/';
13957     }
13958     *(p - 1) = ' ';
13959
13960     /* [HGM] print Crazyhouse or Shogi holdings */
13961     if( gameInfo.holdingsWidth ) {
13962         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13963         q = p;
13964         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13965             piece = boards[move][i][BOARD_WIDTH-1];
13966             if( piece != EmptySquare )
13967               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13968                   *p++ = PieceToChar(piece);
13969         }
13970         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13971             piece = boards[move][BOARD_HEIGHT-i-1][0];
13972             if( piece != EmptySquare )
13973               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13974                   *p++ = PieceToChar(piece);
13975         }
13976
13977         if( q == p ) *p++ = '-';
13978         *p++ = ']';
13979         *p++ = ' ';
13980     }
13981
13982     /* Active color */
13983     *p++ = whiteToPlay ? 'w' : 'b';
13984     *p++ = ' ';
13985
13986   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13987     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13988   } else {
13989   if(nrCastlingRights) {
13990      q = p;
13991      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13992        /* [HGM] write directly from rights */
13993            if(boards[move][CASTLING][2] != NoRights &&
13994               boards[move][CASTLING][0] != NoRights   )
13995                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13996            if(boards[move][CASTLING][2] != NoRights &&
13997               boards[move][CASTLING][1] != NoRights   )
13998                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13999            if(boards[move][CASTLING][5] != NoRights &&
14000               boards[move][CASTLING][3] != NoRights   )
14001                 *p++ = boards[move][CASTLING][3] + AAA;
14002            if(boards[move][CASTLING][5] != NoRights &&
14003               boards[move][CASTLING][4] != NoRights   )
14004                 *p++ = boards[move][CASTLING][4] + AAA;
14005      } else {
14006
14007         /* [HGM] write true castling rights */
14008         if( nrCastlingRights == 6 ) {
14009             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14010                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14011             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14012                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14013             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14014                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14015             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14016                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14017         }
14018      }
14019      if (q == p) *p++ = '-'; /* No castling rights */
14020      *p++ = ' ';
14021   }
14022
14023   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14024      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14025     /* En passant target square */
14026     if (move > backwardMostMove) {
14027         fromX = moveList[move - 1][0] - AAA;
14028         fromY = moveList[move - 1][1] - ONE;
14029         toX = moveList[move - 1][2] - AAA;
14030         toY = moveList[move - 1][3] - ONE;
14031         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14032             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14033             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14034             fromX == toX) {
14035             /* 2-square pawn move just happened */
14036             *p++ = toX + AAA;
14037             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14038         } else {
14039             *p++ = '-';
14040         }
14041     } else if(move == backwardMostMove) {
14042         // [HGM] perhaps we should always do it like this, and forget the above?
14043         if((signed char)boards[move][EP_STATUS] >= 0) {
14044             *p++ = boards[move][EP_STATUS] + AAA;
14045             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14046         } else {
14047             *p++ = '-';
14048         }
14049     } else {
14050         *p++ = '-';
14051     }
14052     *p++ = ' ';
14053   }
14054   }
14055
14056     /* [HGM] find reversible plies */
14057     {   int i = 0, j=move;
14058
14059         if (appData.debugMode) { int k;
14060             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14061             for(k=backwardMostMove; k<=forwardMostMove; k++)
14062                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14063
14064         }
14065
14066         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14067         if( j == backwardMostMove ) i += initialRulePlies;
14068         sprintf(p, "%d ", i);
14069         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14070     }
14071     /* Fullmove number */
14072     sprintf(p, "%d", (move / 2) + 1);
14073     
14074     return StrSave(buf);
14075 }
14076
14077 Boolean
14078 ParseFEN(board, blackPlaysFirst, fen)
14079     Board board;
14080      int *blackPlaysFirst;
14081      char *fen;
14082 {
14083     int i, j;
14084     char *p;
14085     int emptycount;
14086     ChessSquare piece;
14087
14088     p = fen;
14089
14090     /* [HGM] by default clear Crazyhouse holdings, if present */
14091     if(gameInfo.holdingsWidth) {
14092        for(i=0; i<BOARD_HEIGHT; i++) {
14093            board[i][0]             = EmptySquare; /* black holdings */
14094            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14095            board[i][1]             = (ChessSquare) 0; /* black counts */
14096            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14097        }
14098     }
14099
14100     /* Piece placement data */
14101     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14102         j = 0;
14103         for (;;) {
14104             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14105                 if (*p == '/') p++;
14106                 emptycount = gameInfo.boardWidth - j;
14107                 while (emptycount--)
14108                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14109                 break;
14110 #if(BOARD_FILES >= 10)
14111             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14112                 p++; emptycount=10;
14113                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14114                 while (emptycount--)
14115                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14116 #endif
14117             } else if (isdigit(*p)) {
14118                 emptycount = *p++ - '0';
14119                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14120                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14121                 while (emptycount--)
14122                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14123             } else if (*p == '+' || isalpha(*p)) {
14124                 if (j >= gameInfo.boardWidth) return FALSE;
14125                 if(*p=='+') {
14126                     piece = CharToPiece(*++p);
14127                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14128                     piece = (ChessSquare) (PROMOTED piece ); p++;
14129                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14130                 } else piece = CharToPiece(*p++);
14131
14132                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14133                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14134                     piece = (ChessSquare) (PROMOTED piece);
14135                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14136                     p++;
14137                 }
14138                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14139             } else {
14140                 return FALSE;
14141             }
14142         }
14143     }
14144     while (*p == '/' || *p == ' ') p++;
14145
14146     /* [HGM] look for Crazyhouse holdings here */
14147     while(*p==' ') p++;
14148     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14149         if(*p == '[') p++;
14150         if(*p == '-' ) *p++; /* empty holdings */ else {
14151             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14152             /* if we would allow FEN reading to set board size, we would   */
14153             /* have to add holdings and shift the board read so far here   */
14154             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14155                 *p++;
14156                 if((int) piece >= (int) BlackPawn ) {
14157                     i = (int)piece - (int)BlackPawn;
14158                     i = PieceToNumber((ChessSquare)i);
14159                     if( i >= gameInfo.holdingsSize ) return FALSE;
14160                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14161                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14162                 } else {
14163                     i = (int)piece - (int)WhitePawn;
14164                     i = PieceToNumber((ChessSquare)i);
14165                     if( i >= gameInfo.holdingsSize ) return FALSE;
14166                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14167                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14168                 }
14169             }
14170         }
14171         if(*p == ']') *p++;
14172     }
14173
14174     while(*p == ' ') p++;
14175
14176     /* Active color */
14177     switch (*p++) {
14178       case 'w':
14179         *blackPlaysFirst = FALSE;
14180         break;
14181       case 'b': 
14182         *blackPlaysFirst = TRUE;
14183         break;
14184       default:
14185         return FALSE;
14186     }
14187
14188     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14189     /* return the extra info in global variiables             */
14190
14191     /* set defaults in case FEN is incomplete */
14192     board[EP_STATUS] = EP_UNKNOWN;
14193     for(i=0; i<nrCastlingRights; i++ ) {
14194         board[CASTLING][i] =
14195             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14196     }   /* assume possible unless obviously impossible */
14197     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14198     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14199     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14200                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14201     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14202     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14203     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14204                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14205     FENrulePlies = 0;
14206
14207     while(*p==' ') p++;
14208     if(nrCastlingRights) {
14209       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14210           /* castling indicator present, so default becomes no castlings */
14211           for(i=0; i<nrCastlingRights; i++ ) {
14212                  board[CASTLING][i] = NoRights;
14213           }
14214       }
14215       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14216              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14217              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14218              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14219         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14220
14221         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14222             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14223             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14224         }
14225         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14226             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14227         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14228                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14229         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14230                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14231         switch(c) {
14232           case'K':
14233               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14234               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14235               board[CASTLING][2] = whiteKingFile;
14236               break;
14237           case'Q':
14238               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14239               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14240               board[CASTLING][2] = whiteKingFile;
14241               break;
14242           case'k':
14243               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14244               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14245               board[CASTLING][5] = blackKingFile;
14246               break;
14247           case'q':
14248               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14249               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14250               board[CASTLING][5] = blackKingFile;
14251           case '-':
14252               break;
14253           default: /* FRC castlings */
14254               if(c >= 'a') { /* black rights */
14255                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14256                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14257                   if(i == BOARD_RGHT) break;
14258                   board[CASTLING][5] = i;
14259                   c -= AAA;
14260                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14261                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14262                   if(c > i)
14263                       board[CASTLING][3] = c;
14264                   else
14265                       board[CASTLING][4] = c;
14266               } else { /* white rights */
14267                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14268                     if(board[0][i] == WhiteKing) break;
14269                   if(i == BOARD_RGHT) break;
14270                   board[CASTLING][2] = i;
14271                   c -= AAA - 'a' + 'A';
14272                   if(board[0][c] >= WhiteKing) break;
14273                   if(c > i)
14274                       board[CASTLING][0] = c;
14275                   else
14276                       board[CASTLING][1] = c;
14277               }
14278         }
14279       }
14280       for(i=0; i<nrCastlingRights; i++)
14281         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14282     if (appData.debugMode) {
14283         fprintf(debugFP, "FEN castling rights:");
14284         for(i=0; i<nrCastlingRights; i++)
14285         fprintf(debugFP, " %d", board[CASTLING][i]);
14286         fprintf(debugFP, "\n");
14287     }
14288
14289       while(*p==' ') p++;
14290     }
14291
14292     /* read e.p. field in games that know e.p. capture */
14293     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14294        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14295       if(*p=='-') {
14296         p++; board[EP_STATUS] = EP_NONE;
14297       } else {
14298          char c = *p++ - AAA;
14299
14300          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14301          if(*p >= '0' && *p <='9') *p++;
14302          board[EP_STATUS] = c;
14303       }
14304     }
14305
14306
14307     if(sscanf(p, "%d", &i) == 1) {
14308         FENrulePlies = i; /* 50-move ply counter */
14309         /* (The move number is still ignored)    */
14310     }
14311
14312     return TRUE;
14313 }
14314       
14315 void
14316 EditPositionPasteFEN(char *fen)
14317 {
14318   if (fen != NULL) {
14319     Board initial_position;
14320
14321     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14322       DisplayError(_("Bad FEN position in clipboard"), 0);
14323       return ;
14324     } else {
14325       int savedBlackPlaysFirst = blackPlaysFirst;
14326       EditPositionEvent();
14327       blackPlaysFirst = savedBlackPlaysFirst;
14328       CopyBoard(boards[0], initial_position);
14329       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14330       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14331       DisplayBothClocks();
14332       DrawPosition(FALSE, boards[currentMove]);
14333     }
14334   }
14335 }
14336
14337 static char cseq[12] = "\\   ";
14338
14339 Boolean set_cont_sequence(char *new_seq)
14340 {
14341     int len;
14342     Boolean ret;
14343
14344     // handle bad attempts to set the sequence
14345         if (!new_seq)
14346                 return 0; // acceptable error - no debug
14347
14348     len = strlen(new_seq);
14349     ret = (len > 0) && (len < sizeof(cseq));
14350     if (ret)
14351         strcpy(cseq, new_seq);
14352     else if (appData.debugMode)
14353         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14354     return ret;
14355 }
14356
14357 /*
14358     reformat a source message so words don't cross the width boundary.  internal
14359     newlines are not removed.  returns the wrapped size (no null character unless
14360     included in source message).  If dest is NULL, only calculate the size required
14361     for the dest buffer.  lp argument indicats line position upon entry, and it's
14362     passed back upon exit.
14363 */
14364 int wrap(char *dest, char *src, int count, int width, int *lp)
14365 {
14366     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14367
14368     cseq_len = strlen(cseq);
14369     old_line = line = *lp;
14370     ansi = len = clen = 0;
14371
14372     for (i=0; i < count; i++)
14373     {
14374         if (src[i] == '\033')
14375             ansi = 1;
14376
14377         // if we hit the width, back up
14378         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14379         {
14380             // store i & len in case the word is too long
14381             old_i = i, old_len = len;
14382
14383             // find the end of the last word
14384             while (i && src[i] != ' ' && src[i] != '\n')
14385             {
14386                 i--;
14387                 len--;
14388             }
14389
14390             // word too long?  restore i & len before splitting it
14391             if ((old_i-i+clen) >= width)
14392             {
14393                 i = old_i;
14394                 len = old_len;
14395             }
14396
14397             // extra space?
14398             if (i && src[i-1] == ' ')
14399                 len--;
14400
14401             if (src[i] != ' ' && src[i] != '\n')
14402             {
14403                 i--;
14404                 if (len)
14405                     len--;
14406             }
14407
14408             // now append the newline and continuation sequence
14409             if (dest)
14410                 dest[len] = '\n';
14411             len++;
14412             if (dest)
14413                 strncpy(dest+len, cseq, cseq_len);
14414             len += cseq_len;
14415             line = cseq_len;
14416             clen = cseq_len;
14417             continue;
14418         }
14419
14420         if (dest)
14421             dest[len] = src[i];
14422         len++;
14423         if (!ansi)
14424             line++;
14425         if (src[i] == '\n')
14426             line = 0;
14427         if (src[i] == 'm')
14428             ansi = 0;
14429     }
14430     if (dest && appData.debugMode)
14431     {
14432         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14433             count, width, line, len, *lp);
14434         show_bytes(debugFP, src, count);
14435         fprintf(debugFP, "\ndest: ");
14436         show_bytes(debugFP, dest, len);
14437         fprintf(debugFP, "\n");
14438     }
14439     *lp = dest ? line : old_line;
14440
14441     return len;
14442 }
14443
14444 // [HGM] vari: routines for shelving variations
14445
14446 void 
14447 PushTail(int firstMove, int lastMove)
14448 {
14449         int i, j, nrMoves = lastMove - firstMove;
14450
14451         if(appData.icsActive) { // only in local mode
14452                 forwardMostMove = currentMove; // mimic old ICS behavior
14453                 return;
14454         }
14455         if(storedGames >= MAX_VARIATIONS-1) return;
14456
14457         // push current tail of game on stack
14458         savedResult[storedGames] = gameInfo.result;
14459         savedDetails[storedGames] = gameInfo.resultDetails;
14460         gameInfo.resultDetails = NULL;
14461         savedFirst[storedGames] = firstMove;
14462         savedLast [storedGames] = lastMove;
14463         savedFramePtr[storedGames] = framePtr;
14464         framePtr -= nrMoves; // reserve space for the boards
14465         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14466             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14467             for(j=0; j<MOVE_LEN; j++)
14468                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14469             for(j=0; j<2*MOVE_LEN; j++)
14470                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14471             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14472             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14473             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14474             pvInfoList[firstMove+i-1].depth = 0;
14475             commentList[framePtr+i] = commentList[firstMove+i];
14476             commentList[firstMove+i] = NULL;
14477         }
14478
14479         storedGames++;
14480         forwardMostMove = currentMove; // truncte game so we can start variation
14481         if(storedGames == 1) GreyRevert(FALSE);
14482 }
14483
14484 Boolean
14485 PopTail(Boolean annotate)
14486 {
14487         int i, j, nrMoves;
14488         char buf[8000], moveBuf[20];
14489
14490         if(appData.icsActive) return FALSE; // only in local mode
14491         if(!storedGames) return FALSE; // sanity
14492
14493         storedGames--;
14494         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14495         nrMoves = savedLast[storedGames] - currentMove;
14496         if(annotate) {
14497                 int cnt = 10;
14498                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14499                 else strcpy(buf, "(");
14500                 for(i=currentMove; i<forwardMostMove; i++) {
14501                         if(WhiteOnMove(i))
14502                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14503                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14504                         strcat(buf, moveBuf);
14505                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14506                 }
14507                 strcat(buf, ")");
14508         }
14509         for(i=1; i<nrMoves; i++) { // copy last variation back
14510             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14511             for(j=0; j<MOVE_LEN; j++)
14512                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14513             for(j=0; j<2*MOVE_LEN; j++)
14514                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14515             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14516             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14517             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14518             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14519             commentList[currentMove+i] = commentList[framePtr+i];
14520             commentList[framePtr+i] = NULL;
14521         }
14522         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14523         framePtr = savedFramePtr[storedGames];
14524         gameInfo.result = savedResult[storedGames];
14525         if(gameInfo.resultDetails != NULL) {
14526             free(gameInfo.resultDetails);
14527       }
14528         gameInfo.resultDetails = savedDetails[storedGames];
14529         forwardMostMove = currentMove + nrMoves;
14530         if(storedGames == 0) GreyRevert(TRUE);
14531         return TRUE;
14532 }
14533
14534 void 
14535 CleanupTail()
14536 {       // remove all shelved variations
14537         int i;
14538         for(i=0; i<storedGames; i++) {
14539             if(savedDetails[i])
14540                 free(savedDetails[i]);
14541             savedDetails[i] = NULL;
14542         }
14543         for(i=framePtr; i<MAX_MOVES; i++) {
14544                 if(commentList[i]) free(commentList[i]);
14545                 commentList[i] = NULL;
14546         }
14547         framePtr = MAX_MOVES-1;
14548         storedGames = 0;
14549 }