add Winboard source files into tar-ball
[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 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 ChessSquare  FIDEArray[2][BOARD_FILES] = {
454     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
455         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
456     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
457         BlackKing, BlackBishop, BlackKnight, BlackRook }
458 };
459
460 ChessSquare twoKingsArray[2][BOARD_FILES] = {
461     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
462         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
463     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
464         BlackKing, BlackKing, BlackKnight, BlackRook }
465 };
466
467 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
469         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
470     { BlackRook, BlackMan, BlackBishop, BlackQueen,
471         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
472 };
473
474 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
475     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
482     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
483         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
485         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
486 };
487
488
489 #if (BOARD_FILES>=10)
490 ChessSquare ShogiArray[2][BOARD_FILES] = {
491     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
492         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
493     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
494         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
495 };
496
497 ChessSquare XiangqiArray[2][BOARD_FILES] = {
498     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
499         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
501         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
502 };
503
504 ChessSquare CapablancaArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
506         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
508         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
509 };
510
511 ChessSquare GreatArray[2][BOARD_FILES] = {
512     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
513         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
514     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
515         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
516 };
517
518 ChessSquare JanusArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
520         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
521     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
522         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
523 };
524
525 #ifdef GOTHIC
526 ChessSquare GothicArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
528         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
530         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
531 };
532 #else // !GOTHIC
533 #define GothicArray CapablancaArray
534 #endif // !GOTHIC
535
536 #ifdef FALCON
537 ChessSquare FalconArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
539         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
541         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
542 };
543 #else // !FALCON
544 #define FalconArray CapablancaArray
545 #endif // !FALCON
546
547 #else // !(BOARD_FILES>=10)
548 #define XiangqiPosition FIDEArray
549 #define CapablancaArray FIDEArray
550 #define GothicArray FIDEArray
551 #define GreatArray FIDEArray
552 #endif // !(BOARD_FILES>=10)
553
554 #if (BOARD_FILES>=12)
555 ChessSquare CourierArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
559         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
560 };
561 #else // !(BOARD_FILES>=12)
562 #define CourierArray CapablancaArray
563 #endif // !(BOARD_FILES>=12)
564
565
566 Board initialPosition;
567
568
569 /* Convert str to a rating. Checks for special cases of "----",
570
571    "++++", etc. Also strips ()'s */
572 int
573 string_to_rating(str)
574   char *str;
575 {
576   while(*str && !isdigit(*str)) ++str;
577   if (!*str)
578     return 0;   /* One of the special "no rating" cases */
579   else
580     return atoi(str);
581 }
582
583 void
584 ClearProgramStats()
585 {
586     /* Init programStats */
587     programStats.movelist[0] = 0;
588     programStats.depth = 0;
589     programStats.nr_moves = 0;
590     programStats.moves_left = 0;
591     programStats.nodes = 0;
592     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
593     programStats.score = 0;
594     programStats.got_only_move = 0;
595     programStats.got_fail = 0;
596     programStats.line_is_book = 0;
597 }
598
599 void
600 InitBackEnd1()
601 {
602     int matched, min, sec;
603
604     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
605
606     GetTimeMark(&programStartTime);
607     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
608
609     ClearProgramStats();
610     programStats.ok_to_send = 1;
611     programStats.seen_stat = 0;
612
613     /*
614      * Initialize game list
615      */
616     ListNew(&gameList);
617
618
619     /*
620      * Internet chess server status
621      */
622     if (appData.icsActive) {
623         appData.matchMode = FALSE;
624         appData.matchGames = 0;
625 #if ZIPPY       
626         appData.noChessProgram = !appData.zippyPlay;
627 #else
628         appData.zippyPlay = FALSE;
629         appData.zippyTalk = FALSE;
630         appData.noChessProgram = TRUE;
631 #endif
632         if (*appData.icsHelper != NULLCHAR) {
633             appData.useTelnet = TRUE;
634             appData.telnetProgram = appData.icsHelper;
635         }
636     } else {
637         appData.zippyTalk = appData.zippyPlay = FALSE;
638     }
639
640     /* [AS] Initialize pv info list [HGM] and game state */
641     {
642         int i, j;
643
644         for( i=0; i<MAX_MOVES; i++ ) {
645             pvInfoList[i].depth = -1;
646             boards[i][EP_STATUS] = EP_NONE;
647             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
648         }
649     }
650
651     /*
652      * Parse timeControl resource
653      */
654     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
655                           appData.movesPerSession)) {
656         char buf[MSG_SIZ];
657         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
658         DisplayFatalError(buf, 0, 2);
659     }
660
661     /*
662      * Parse searchTime resource
663      */
664     if (*appData.searchTime != NULLCHAR) {
665         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
666         if (matched == 1) {
667             searchTime = min * 60;
668         } else if (matched == 2) {
669             searchTime = min * 60 + sec;
670         } else {
671             char buf[MSG_SIZ];
672             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
673             DisplayFatalError(buf, 0, 2);
674         }
675     }
676
677     /* [AS] Adjudication threshold */
678     adjudicateLossThreshold = appData.adjudicateLossThreshold;
679     
680     first.which = "first";
681     second.which = "second";
682     first.maybeThinking = second.maybeThinking = FALSE;
683     first.pr = second.pr = NoProc;
684     first.isr = second.isr = NULL;
685     first.sendTime = second.sendTime = 2;
686     first.sendDrawOffers = 1;
687     if (appData.firstPlaysBlack) {
688         first.twoMachinesColor = "black\n";
689         second.twoMachinesColor = "white\n";
690     } else {
691         first.twoMachinesColor = "white\n";
692         second.twoMachinesColor = "black\n";
693     }
694     first.program = appData.firstChessProgram;
695     second.program = appData.secondChessProgram;
696     first.host = appData.firstHost;
697     second.host = appData.secondHost;
698     first.dir = appData.firstDirectory;
699     second.dir = appData.secondDirectory;
700     first.other = &second;
701     second.other = &first;
702     first.initString = appData.initString;
703     second.initString = appData.secondInitString;
704     first.computerString = appData.firstComputerString;
705     second.computerString = appData.secondComputerString;
706     first.useSigint = second.useSigint = TRUE;
707     first.useSigterm = second.useSigterm = TRUE;
708     first.reuse = appData.reuseFirst;
709     second.reuse = appData.reuseSecond;
710     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
711     second.nps = appData.secondNPS;
712     first.useSetboard = second.useSetboard = FALSE;
713     first.useSAN = second.useSAN = FALSE;
714     first.usePing = second.usePing = FALSE;
715     first.lastPing = second.lastPing = 0;
716     first.lastPong = second.lastPong = 0;
717     first.usePlayother = second.usePlayother = FALSE;
718     first.useColors = second.useColors = TRUE;
719     first.useUsermove = second.useUsermove = FALSE;
720     first.sendICS = second.sendICS = FALSE;
721     first.sendName = second.sendName = appData.icsActive;
722     first.sdKludge = second.sdKludge = FALSE;
723     first.stKludge = second.stKludge = FALSE;
724     TidyProgramName(first.program, first.host, first.tidy);
725     TidyProgramName(second.program, second.host, second.tidy);
726     first.matchWins = second.matchWins = 0;
727     strcpy(first.variants, appData.variant);
728     strcpy(second.variants, appData.variant);
729     first.analysisSupport = second.analysisSupport = 2; /* detect */
730     first.analyzing = second.analyzing = FALSE;
731     first.initDone = second.initDone = FALSE;
732
733     /* New features added by Tord: */
734     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
735     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
736     /* End of new features added by Tord. */
737     first.fenOverride  = appData.fenOverride1;
738     second.fenOverride = appData.fenOverride2;
739
740     /* [HGM] time odds: set factor for each machine */
741     first.timeOdds  = appData.firstTimeOdds;
742     second.timeOdds = appData.secondTimeOdds;
743     { int norm = 1;
744         if(appData.timeOddsMode) {
745             norm = first.timeOdds;
746             if(norm > second.timeOdds) norm = second.timeOdds;
747         }
748         first.timeOdds /= norm;
749         second.timeOdds /= norm;
750     }
751
752     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
753     first.accumulateTC = appData.firstAccumulateTC;
754     second.accumulateTC = appData.secondAccumulateTC;
755     first.maxNrOfSessions = second.maxNrOfSessions = 1;
756
757     /* [HGM] debug */
758     first.debug = second.debug = FALSE;
759     first.supportsNPS = second.supportsNPS = UNKNOWN;
760
761     /* [HGM] options */
762     first.optionSettings  = appData.firstOptions;
763     second.optionSettings = appData.secondOptions;
764
765     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
766     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
767     first.isUCI = appData.firstIsUCI; /* [AS] */
768     second.isUCI = appData.secondIsUCI; /* [AS] */
769     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
770     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
771
772     if (appData.firstProtocolVersion > PROTOVER ||
773         appData.firstProtocolVersion < 1) {
774       char buf[MSG_SIZ];
775       sprintf(buf, _("protocol version %d not supported"),
776               appData.firstProtocolVersion);
777       DisplayFatalError(buf, 0, 2);
778     } else {
779       first.protocolVersion = appData.firstProtocolVersion;
780     }
781
782     if (appData.secondProtocolVersion > PROTOVER ||
783         appData.secondProtocolVersion < 1) {
784       char buf[MSG_SIZ];
785       sprintf(buf, _("protocol version %d not supported"),
786               appData.secondProtocolVersion);
787       DisplayFatalError(buf, 0, 2);
788     } else {
789       second.protocolVersion = appData.secondProtocolVersion;
790     }
791
792     if (appData.icsActive) {
793         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
794 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
795     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
796         appData.clockMode = FALSE;
797         first.sendTime = second.sendTime = 0;
798     }
799     
800 #if ZIPPY
801     /* Override some settings from environment variables, for backward
802        compatibility.  Unfortunately it's not feasible to have the env
803        vars just set defaults, at least in xboard.  Ugh.
804     */
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
806       ZippyInit();
807     }
808 #endif
809     
810     if (appData.noChessProgram) {
811         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812         sprintf(programVersion, "%s", PACKAGE_STRING);
813     } else {
814       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
817     }
818
819     if (!appData.icsActive) {
820       char buf[MSG_SIZ];
821       /* Check for variants that are supported only in ICS mode,
822          or not at all.  Some that are accepted here nevertheless
823          have bugs; see comments below.
824       */
825       VariantClass variant = StringToVariant(appData.variant);
826       switch (variant) {
827       case VariantBughouse:     /* need four players and two boards */
828       case VariantKriegspiel:   /* need to hide pieces and move details */
829       /* case VariantFischeRandom: (Fabien: moved below) */
830         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831         DisplayFatalError(buf, 0, 2);
832         return;
833
834       case VariantUnknown:
835       case VariantLoadable:
836       case Variant29:
837       case Variant30:
838       case Variant31:
839       case Variant32:
840       case Variant33:
841       case Variant34:
842       case Variant35:
843       case Variant36:
844       default:
845         sprintf(buf, _("Unknown variant name %s"), appData.variant);
846         DisplayFatalError(buf, 0, 2);
847         return;
848
849       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
850       case VariantFairy:      /* [HGM] TestLegality definitely off! */
851       case VariantGothic:     /* [HGM] should work */
852       case VariantCapablanca: /* [HGM] should work */
853       case VariantCourier:    /* [HGM] initial forced moves not implemented */
854       case VariantShogi:      /* [HGM] drops not tested for legality */
855       case VariantKnightmate: /* [HGM] should work */
856       case VariantCylinder:   /* [HGM] untested */
857       case VariantFalcon:     /* [HGM] untested */
858       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859                                  offboard interposition not understood */
860       case VariantNormal:     /* definitely works! */
861       case VariantWildCastle: /* pieces not automatically shuffled */
862       case VariantNoCastle:   /* pieces not automatically shuffled */
863       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864       case VariantLosers:     /* should work except for win condition,
865                                  and doesn't know captures are mandatory */
866       case VariantSuicide:    /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantGiveaway:   /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantTwoKings:   /* should work */
871       case VariantAtomic:     /* should work except for win condition */
872       case Variant3Check:     /* should work except for win condition */
873       case VariantShatranj:   /* should work except for all win conditions */
874       case VariantBerolina:   /* might work if TestLegality is off */
875       case VariantCapaRandom: /* should work */
876       case VariantJanus:      /* should work */
877       case VariantSuper:      /* experimental */
878       case VariantGreat:      /* experimental, requires legality testing to be off */
879         break;
880       }
881     }
882
883     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
884     InitEngineUCI( installDir, &second );
885 }
886
887 int NextIntegerFromString( char ** str, long * value )
888 {
889     int result = -1;
890     char * s = *str;
891
892     while( *s == ' ' || *s == '\t' ) {
893         s++;
894     }
895
896     *value = 0;
897
898     if( *s >= '0' && *s <= '9' ) {
899         while( *s >= '0' && *s <= '9' ) {
900             *value = *value * 10 + (*s - '0');
901             s++;
902         }
903
904         result = 0;
905     }
906
907     *str = s;
908
909     return result;
910 }
911
912 int NextTimeControlFromString( char ** str, long * value )
913 {
914     long temp;
915     int result = NextIntegerFromString( str, &temp );
916
917     if( result == 0 ) {
918         *value = temp * 60; /* Minutes */
919         if( **str == ':' ) {
920             (*str)++;
921             result = NextIntegerFromString( str, &temp );
922             *value += temp; /* Seconds */
923         }
924     }
925
926     return result;
927 }
928
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
931     int result = -1; long temp, temp2;
932
933     if(**str != '+') return -1; // old params remain in force!
934     (*str)++;
935     if( NextTimeControlFromString( str, &temp ) ) return -1;
936
937     if(**str != '/') {
938         /* time only: incremental or sudden-death time control */
939         if(**str == '+') { /* increment follows; read it */
940             (*str)++;
941             if(result = NextIntegerFromString( str, &temp2)) return -1;
942             *inc = temp2 * 1000;
943         } else *inc = 0;
944         *moves = 0; *tc = temp * 1000; 
945         return 0;
946     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
947
948     (*str)++; /* classical time control */
949     result = NextTimeControlFromString( str, &temp2);
950     if(result == 0) {
951         *moves = temp/60;
952         *tc    = temp2 * 1000;
953         *inc   = 0;
954     }
955     return result;
956 }
957
958 int GetTimeQuota(int movenr)
959 {   /* [HGM] get time to add from the multi-session time-control string */
960     int moves=1; /* kludge to force reading of first session */
961     long time, increment;
962     char *s = fullTimeControlString;
963
964     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
965     do {
966         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968         if(movenr == -1) return time;    /* last move before new session     */
969         if(!moves) return increment;     /* current session is incremental   */
970         if(movenr >= 0) movenr -= moves; /* we already finished this session */
971     } while(movenr >= -1);               /* try again for next session       */
972
973     return 0; // no new time quota on this move
974 }
975
976 int
977 ParseTimeControl(tc, ti, mps)
978      char *tc;
979      int ti;
980      int mps;
981 {
982   long tc1;
983   long tc2;
984   char buf[MSG_SIZ];
985   
986   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
987   if(ti > 0) {
988     if(mps)
989       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990     else sprintf(buf, "+%s+%d", tc, ti);
991   } else {
992     if(mps)
993              sprintf(buf, "+%d/%s", mps, tc);
994     else sprintf(buf, "+%s", tc);
995   }
996   fullTimeControlString = StrSave(buf);
997   
998   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
999     return FALSE;
1000   }
1001   
1002   if( *tc == '/' ) {
1003     /* Parse second time control */
1004     tc++;
1005     
1006     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1007       return FALSE;
1008     }
1009     
1010     if( tc2 == 0 ) {
1011       return FALSE;
1012     }
1013     
1014     timeControl_2 = tc2 * 1000;
1015   }
1016   else {
1017     timeControl_2 = 0;
1018   }
1019   
1020   if( tc1 == 0 ) {
1021     return FALSE;
1022   }
1023   
1024   timeControl = tc1 * 1000;
1025   
1026   if (ti >= 0) {
1027     timeIncrement = ti * 1000;  /* convert to ms */
1028     movesPerSession = 0;
1029   } else {
1030     timeIncrement = 0;
1031     movesPerSession = mps;
1032   }
1033   return TRUE;
1034 }
1035
1036 void
1037 InitBackEnd2()
1038 {
1039     if (appData.debugMode) {
1040         fprintf(debugFP, "%s\n", programVersion);
1041     }
1042
1043     set_cont_sequence(appData.wrapContSeq);
1044     if (appData.matchGames > 0) {
1045         appData.matchMode = TRUE;
1046     } else if (appData.matchMode) {
1047         appData.matchGames = 1;
1048     }
1049     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1050         appData.matchGames = appData.sameColorGames;
1051     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1052         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1053         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1054     }
1055     Reset(TRUE, FALSE);
1056     if (appData.noChessProgram || first.protocolVersion == 1) {
1057       InitBackEnd3();
1058     } else {
1059       /* kludge: allow timeout for initial "feature" commands */
1060       FreezeUI();
1061       DisplayMessage("", _("Starting chess program"));
1062       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1063     }
1064 }
1065
1066 void
1067 InitBackEnd3 P((void))
1068 {
1069     GameMode initialMode;
1070     char buf[MSG_SIZ];
1071     int err;
1072
1073     InitChessProgram(&first, startedFromSetupPosition);
1074
1075
1076     if (appData.icsActive) {
1077 #ifdef WIN32
1078         /* [DM] Make a console window if needed [HGM] merged ifs */
1079         ConsoleCreate(); 
1080 #endif
1081         err = establish();
1082         if (err != 0) {
1083             if (*appData.icsCommPort != NULLCHAR) {
1084                 sprintf(buf, _("Could not open comm port %s"),  
1085                         appData.icsCommPort);
1086             } else {
1087                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1088                         appData.icsHost, appData.icsPort);
1089             }
1090             DisplayFatalError(buf, err, 1);
1091             return;
1092         }
1093         SetICSMode();
1094         telnetISR =
1095           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1096         fromUserISR =
1097           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1098     } else if (appData.noChessProgram) {
1099         SetNCPMode();
1100     } else {
1101         SetGNUMode();
1102     }
1103
1104     if (*appData.cmailGameName != NULLCHAR) {
1105         SetCmailMode();
1106         OpenLoopback(&cmailPR);
1107         cmailISR =
1108           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1109     }
1110     
1111     ThawUI();
1112     DisplayMessage("", "");
1113     if (StrCaseCmp(appData.initialMode, "") == 0) {
1114       initialMode = BeginningOfGame;
1115     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1116       initialMode = TwoMachinesPlay;
1117     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1118       initialMode = AnalyzeFile; 
1119     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1120       initialMode = AnalyzeMode;
1121     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1122       initialMode = MachinePlaysWhite;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1124       initialMode = MachinePlaysBlack;
1125     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1126       initialMode = EditGame;
1127     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1128       initialMode = EditPosition;
1129     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1130       initialMode = Training;
1131     } else {
1132       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1133       DisplayFatalError(buf, 0, 2);
1134       return;
1135     }
1136
1137     if (appData.matchMode) {
1138         /* Set up machine vs. machine match */
1139         if (appData.noChessProgram) {
1140             DisplayFatalError(_("Can't have a match with no chess programs"),
1141                               0, 2);
1142             return;
1143         }
1144         matchMode = TRUE;
1145         matchGame = 1;
1146         if (*appData.loadGameFile != NULLCHAR) {
1147             int index = appData.loadGameIndex; // [HGM] autoinc
1148             if(index<0) lastIndex = index = 1;
1149             if (!LoadGameFromFile(appData.loadGameFile,
1150                                   index,
1151                                   appData.loadGameFile, FALSE)) {
1152                 DisplayFatalError(_("Bad game file"), 0, 1);
1153                 return;
1154             }
1155         } else if (*appData.loadPositionFile != NULLCHAR) {
1156             int index = appData.loadPositionIndex; // [HGM] autoinc
1157             if(index<0) lastIndex = index = 1;
1158             if (!LoadPositionFromFile(appData.loadPositionFile,
1159                                       index,
1160                                       appData.loadPositionFile)) {
1161                 DisplayFatalError(_("Bad position file"), 0, 1);
1162                 return;
1163             }
1164         }
1165         TwoMachinesEvent();
1166     } else if (*appData.cmailGameName != NULLCHAR) {
1167         /* Set up cmail mode */
1168         ReloadCmailMsgEvent(TRUE);
1169     } else {
1170         /* Set up other modes */
1171         if (initialMode == AnalyzeFile) {
1172           if (*appData.loadGameFile == NULLCHAR) {
1173             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1174             return;
1175           }
1176         }
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             (void) LoadGameFromFile(appData.loadGameFile,
1179                                     appData.loadGameIndex,
1180                                     appData.loadGameFile, TRUE);
1181         } else if (*appData.loadPositionFile != NULLCHAR) {
1182             (void) LoadPositionFromFile(appData.loadPositionFile,
1183                                         appData.loadPositionIndex,
1184                                         appData.loadPositionFile);
1185             /* [HGM] try to make self-starting even after FEN load */
1186             /* to allow automatic setup of fairy variants with wtm */
1187             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1188                 gameMode = BeginningOfGame;
1189                 setboardSpoiledMachineBlack = 1;
1190             }
1191             /* [HGM] loadPos: make that every new game uses the setup */
1192             /* from file as long as we do not switch variant          */
1193             if(!blackPlaysFirst) {
1194                 startedFromPositionFile = TRUE;
1195                 CopyBoard(filePosition, boards[0]);
1196             }
1197         }
1198         if (initialMode == AnalyzeMode) {
1199           if (appData.noChessProgram) {
1200             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1201             return;
1202           }
1203           if (appData.icsActive) {
1204             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1205             return;
1206           }
1207           AnalyzeModeEvent();
1208         } else if (initialMode == AnalyzeFile) {
1209           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210           ShowThinkingEvent();
1211           AnalyzeFileEvent();
1212           AnalysisPeriodicEvent(1);
1213         } else if (initialMode == MachinePlaysWhite) {
1214           if (appData.noChessProgram) {
1215             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1216                               0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1221                               0, 2);
1222             return;
1223           }
1224           MachineWhiteEvent();
1225         } else if (initialMode == MachinePlaysBlack) {
1226           if (appData.noChessProgram) {
1227             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1228                               0, 2);
1229             return;
1230           }
1231           if (appData.icsActive) {
1232             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1233                               0, 2);
1234             return;
1235           }
1236           MachineBlackEvent();
1237         } else if (initialMode == TwoMachinesPlay) {
1238           if (appData.noChessProgram) {
1239             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1240                               0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1245                               0, 2);
1246             return;
1247           }
1248           TwoMachinesEvent();
1249         } else if (initialMode == EditGame) {
1250           EditGameEvent();
1251         } else if (initialMode == EditPosition) {
1252           EditPositionEvent();
1253         } else if (initialMode == Training) {
1254           if (*appData.loadGameFile == NULLCHAR) {
1255             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1256             return;
1257           }
1258           TrainingEvent();
1259         }
1260     }
1261 }
1262
1263 /*
1264  * Establish will establish a contact to a remote host.port.
1265  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266  *  used to talk to the host.
1267  * Returns 0 if okay, error code if not.
1268  */
1269 int
1270 establish()
1271 {
1272     char buf[MSG_SIZ];
1273
1274     if (*appData.icsCommPort != NULLCHAR) {
1275         /* Talk to the host through a serial comm port */
1276         return OpenCommPort(appData.icsCommPort, &icsPR);
1277
1278     } else if (*appData.gateway != NULLCHAR) {
1279         if (*appData.remoteShell == NULLCHAR) {
1280             /* Use the rcmd protocol to run telnet program on a gateway host */
1281             snprintf(buf, sizeof(buf), "%s %s %s",
1282                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1283             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1284
1285         } else {
1286             /* Use the rsh program to run telnet program on a gateway host */
1287             if (*appData.remoteUser == NULLCHAR) {
1288                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289                         appData.gateway, appData.telnetProgram,
1290                         appData.icsHost, appData.icsPort);
1291             } else {
1292                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293                         appData.remoteShell, appData.gateway, 
1294                         appData.remoteUser, appData.telnetProgram,
1295                         appData.icsHost, appData.icsPort);
1296             }
1297             return StartChildProcess(buf, "", &icsPR);
1298
1299         }
1300     } else if (appData.useTelnet) {
1301         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1302
1303     } else {
1304         /* TCP socket interface differs somewhat between
1305            Unix and NT; handle details in the front end.
1306            */
1307         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1308     }
1309 }
1310
1311 void
1312 show_bytes(fp, buf, count)
1313      FILE *fp;
1314      char *buf;
1315      int count;
1316 {
1317     while (count--) {
1318         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319             fprintf(fp, "\\%03o", *buf & 0xff);
1320         } else {
1321             putc(*buf, fp);
1322         }
1323         buf++;
1324     }
1325     fflush(fp);
1326 }
1327
1328 /* Returns an errno value */
1329 int
1330 OutputMaybeTelnet(pr, message, count, outError)
1331      ProcRef pr;
1332      char *message;
1333      int count;
1334      int *outError;
1335 {
1336     char buf[8192], *p, *q, *buflim;
1337     int left, newcount, outcount;
1338
1339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340         *appData.gateway != NULLCHAR) {
1341         if (appData.debugMode) {
1342             fprintf(debugFP, ">ICS: ");
1343             show_bytes(debugFP, message, count);
1344             fprintf(debugFP, "\n");
1345         }
1346         return OutputToProcess(pr, message, count, outError);
1347     }
1348
1349     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1350     p = message;
1351     q = buf;
1352     left = count;
1353     newcount = 0;
1354     while (left) {
1355         if (q >= buflim) {
1356             if (appData.debugMode) {
1357                 fprintf(debugFP, ">ICS: ");
1358                 show_bytes(debugFP, buf, newcount);
1359                 fprintf(debugFP, "\n");
1360             }
1361             outcount = OutputToProcess(pr, buf, newcount, outError);
1362             if (outcount < newcount) return -1; /* to be sure */
1363             q = buf;
1364             newcount = 0;
1365         }
1366         if (*p == '\n') {
1367             *q++ = '\r';
1368             newcount++;
1369         } else if (((unsigned char) *p) == TN_IAC) {
1370             *q++ = (char) TN_IAC;
1371             newcount ++;
1372         }
1373         *q++ = *p++;
1374         newcount++;
1375         left--;
1376     }
1377     if (appData.debugMode) {
1378         fprintf(debugFP, ">ICS: ");
1379         show_bytes(debugFP, buf, newcount);
1380         fprintf(debugFP, "\n");
1381     }
1382     outcount = OutputToProcess(pr, buf, newcount, outError);
1383     if (outcount < newcount) return -1; /* to be sure */
1384     return count;
1385 }
1386
1387 void
1388 read_from_player(isr, closure, message, count, error)
1389      InputSourceRef isr;
1390      VOIDSTAR closure;
1391      char *message;
1392      int count;
1393      int error;
1394 {
1395     int outError, outCount;
1396     static int gotEof = 0;
1397
1398     /* Pass data read from player on to ICS */
1399     if (count > 0) {
1400         gotEof = 0;
1401         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402         if (outCount < count) {
1403             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1404         }
1405     } else if (count < 0) {
1406         RemoveInputSource(isr);
1407         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408     } else if (gotEof++ > 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1411     }
1412 }
1413
1414 void
1415 KeepAlive()
1416 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417     SendToICS("date\n");
1418     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1419 }
1420
1421 /* added routine for printf style output to ics */
1422 void ics_printf(char *format, ...)
1423 {
1424     char buffer[MSG_SIZ];
1425     va_list args;
1426
1427     va_start(args, format);
1428     vsnprintf(buffer, sizeof(buffer), format, args);
1429     buffer[sizeof(buffer)-1] = '\0';
1430     SendToICS(buffer);
1431     va_end(args);
1432 }
1433
1434 void
1435 SendToICS(s)
1436      char *s;
1437 {
1438     int count, outCount, outError;
1439
1440     if (icsPR == NULL) return;
1441
1442     count = strlen(s);
1443     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444     if (outCount < count) {
1445         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446     }
1447 }
1448
1449 /* This is used for sending logon scripts to the ICS. Sending
1450    without a delay causes problems when using timestamp on ICC
1451    (at least on my machine). */
1452 void
1453 SendToICSDelayed(s,msdelay)
1454      char *s;
1455      long msdelay;
1456 {
1457     int count, outCount, outError;
1458
1459     if (icsPR == NULL) return;
1460
1461     count = strlen(s);
1462     if (appData.debugMode) {
1463         fprintf(debugFP, ">ICS: ");
1464         show_bytes(debugFP, s, count);
1465         fprintf(debugFP, "\n");
1466     }
1467     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1468                                       msdelay);
1469     if (outCount < count) {
1470         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1471     }
1472 }
1473
1474
1475 /* Remove all highlighting escape sequences in s
1476    Also deletes any suffix starting with '(' 
1477    */
1478 char *
1479 StripHighlightAndTitle(s)
1480      char *s;
1481 {
1482     static char retbuf[MSG_SIZ];
1483     char *p = retbuf;
1484
1485     while (*s != NULLCHAR) {
1486         while (*s == '\033') {
1487             while (*s != NULLCHAR && !isalpha(*s)) s++;
1488             if (*s != NULLCHAR) s++;
1489         }
1490         while (*s != NULLCHAR && *s != '\033') {
1491             if (*s == '(' || *s == '[') {
1492                 *p = NULLCHAR;
1493                 return retbuf;
1494             }
1495             *p++ = *s++;
1496         }
1497     }
1498     *p = NULLCHAR;
1499     return retbuf;
1500 }
1501
1502 /* Remove all highlighting escape sequences in s */
1503 char *
1504 StripHighlight(s)
1505      char *s;
1506 {
1507     static char retbuf[MSG_SIZ];
1508     char *p = retbuf;
1509
1510     while (*s != NULLCHAR) {
1511         while (*s == '\033') {
1512             while (*s != NULLCHAR && !isalpha(*s)) s++;
1513             if (*s != NULLCHAR) s++;
1514         }
1515         while (*s != NULLCHAR && *s != '\033') {
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 char *variantNames[] = VARIANT_NAMES;
1524 char *
1525 VariantName(v)
1526      VariantClass v;
1527 {
1528     return variantNames[v];
1529 }
1530
1531
1532 /* Identify a variant from the strings the chess servers use or the
1533    PGN Variant tag names we use. */
1534 VariantClass
1535 StringToVariant(e)
1536      char *e;
1537 {
1538     char *p;
1539     int wnum = -1;
1540     VariantClass v = VariantNormal;
1541     int i, found = FALSE;
1542     char buf[MSG_SIZ];
1543
1544     if (!e) return v;
1545
1546     /* [HGM] skip over optional board-size prefixes */
1547     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549         while( *e++ != '_');
1550     }
1551
1552     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1553         v = VariantNormal;
1554         found = TRUE;
1555     } else
1556     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1557       if (StrCaseStr(e, variantNames[i])) {
1558         v = (VariantClass) i;
1559         found = TRUE;
1560         break;
1561       }
1562     }
1563
1564     if (!found) {
1565       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1566           || StrCaseStr(e, "wild/fr") 
1567           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1568         v = VariantFischeRandom;
1569       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1570                  (i = 1, p = StrCaseStr(e, "w"))) {
1571         p += i;
1572         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1573         if (isdigit(*p)) {
1574           wnum = atoi(p);
1575         } else {
1576           wnum = -1;
1577         }
1578         switch (wnum) {
1579         case 0: /* FICS only, actually */
1580         case 1:
1581           /* Castling legal even if K starts on d-file */
1582           v = VariantWildCastle;
1583           break;
1584         case 2:
1585         case 3:
1586         case 4:
1587           /* Castling illegal even if K & R happen to start in
1588              normal positions. */
1589           v = VariantNoCastle;
1590           break;
1591         case 5:
1592         case 7:
1593         case 8:
1594         case 10:
1595         case 11:
1596         case 12:
1597         case 13:
1598         case 14:
1599         case 15:
1600         case 18:
1601         case 19:
1602           /* Castling legal iff K & R start in normal positions */
1603           v = VariantNormal;
1604           break;
1605         case 6:
1606         case 20:
1607         case 21:
1608           /* Special wilds for position setup; unclear what to do here */
1609           v = VariantLoadable;
1610           break;
1611         case 9:
1612           /* Bizarre ICC game */
1613           v = VariantTwoKings;
1614           break;
1615         case 16:
1616           v = VariantKriegspiel;
1617           break;
1618         case 17:
1619           v = VariantLosers;
1620           break;
1621         case 22:
1622           v = VariantFischeRandom;
1623           break;
1624         case 23:
1625           v = VariantCrazyhouse;
1626           break;
1627         case 24:
1628           v = VariantBughouse;
1629           break;
1630         case 25:
1631           v = Variant3Check;
1632           break;
1633         case 26:
1634           /* Not quite the same as FICS suicide! */
1635           v = VariantGiveaway;
1636           break;
1637         case 27:
1638           v = VariantAtomic;
1639           break;
1640         case 28:
1641           v = VariantShatranj;
1642           break;
1643
1644         /* Temporary names for future ICC types.  The name *will* change in 
1645            the next xboard/WinBoard release after ICC defines it. */
1646         case 29:
1647           v = Variant29;
1648           break;
1649         case 30:
1650           v = Variant30;
1651           break;
1652         case 31:
1653           v = Variant31;
1654           break;
1655         case 32:
1656           v = Variant32;
1657           break;
1658         case 33:
1659           v = Variant33;
1660           break;
1661         case 34:
1662           v = Variant34;
1663           break;
1664         case 35:
1665           v = Variant35;
1666           break;
1667         case 36:
1668           v = Variant36;
1669           break;
1670         case 37:
1671           v = VariantShogi;
1672           break;
1673         case 38:
1674           v = VariantXiangqi;
1675           break;
1676         case 39:
1677           v = VariantCourier;
1678           break;
1679         case 40:
1680           v = VariantGothic;
1681           break;
1682         case 41:
1683           v = VariantCapablanca;
1684           break;
1685         case 42:
1686           v = VariantKnightmate;
1687           break;
1688         case 43:
1689           v = VariantFairy;
1690           break;
1691         case 44:
1692           v = VariantCylinder;
1693           break;
1694         case 45:
1695           v = VariantFalcon;
1696           break;
1697         case 46:
1698           v = VariantCapaRandom;
1699           break;
1700         case 47:
1701           v = VariantBerolina;
1702           break;
1703         case 48:
1704           v = VariantJanus;
1705           break;
1706         case 49:
1707           v = VariantSuper;
1708           break;
1709         case 50:
1710           v = VariantGreat;
1711           break;
1712         case -1:
1713           /* Found "wild" or "w" in the string but no number;
1714              must assume it's normal chess. */
1715           v = VariantNormal;
1716           break;
1717         default:
1718           sprintf(buf, _("Unknown wild type %d"), wnum);
1719           DisplayError(buf, 0);
1720           v = VariantUnknown;
1721           break;
1722         }
1723       }
1724     }
1725     if (appData.debugMode) {
1726       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1727               e, wnum, VariantName(v));
1728     }
1729     return v;
1730 }
1731
1732 static int leftover_start = 0, leftover_len = 0;
1733 char star_match[STAR_MATCH_N][MSG_SIZ];
1734
1735 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1736    advance *index beyond it, and set leftover_start to the new value of
1737    *index; else return FALSE.  If pattern contains the character '*', it
1738    matches any sequence of characters not containing '\r', '\n', or the
1739    character following the '*' (if any), and the matched sequence(s) are
1740    copied into star_match.
1741    */
1742 int
1743 looking_at(buf, index, pattern)
1744      char *buf;
1745      int *index;
1746      char *pattern;
1747 {
1748     char *bufp = &buf[*index], *patternp = pattern;
1749     int star_count = 0;
1750     char *matchp = star_match[0];
1751     
1752     for (;;) {
1753         if (*patternp == NULLCHAR) {
1754             *index = leftover_start = bufp - buf;
1755             *matchp = NULLCHAR;
1756             return TRUE;
1757         }
1758         if (*bufp == NULLCHAR) return FALSE;
1759         if (*patternp == '*') {
1760             if (*bufp == *(patternp + 1)) {
1761                 *matchp = NULLCHAR;
1762                 matchp = star_match[++star_count];
1763                 patternp += 2;
1764                 bufp++;
1765                 continue;
1766             } else if (*bufp == '\n' || *bufp == '\r') {
1767                 patternp++;
1768                 if (*patternp == NULLCHAR)
1769                   continue;
1770                 else
1771                   return FALSE;
1772             } else {
1773                 *matchp++ = *bufp++;
1774                 continue;
1775             }
1776         }
1777         if (*patternp != *bufp) return FALSE;
1778         patternp++;
1779         bufp++;
1780     }
1781 }
1782
1783 void
1784 SendToPlayer(data, length)
1785      char *data;
1786      int length;
1787 {
1788     int error, outCount;
1789     outCount = OutputToProcess(NoProc, data, length, &error);
1790     if (outCount < length) {
1791         DisplayFatalError(_("Error writing to display"), error, 1);
1792     }
1793 }
1794
1795 void
1796 PackHolding(packed, holding)
1797      char packed[];
1798      char *holding;
1799 {
1800     char *p = holding;
1801     char *q = packed;
1802     int runlength = 0;
1803     int curr = 9999;
1804     do {
1805         if (*p == curr) {
1806             runlength++;
1807         } else {
1808             switch (runlength) {
1809               case 0:
1810                 break;
1811               case 1:
1812                 *q++ = curr;
1813                 break;
1814               case 2:
1815                 *q++ = curr;
1816                 *q++ = curr;
1817                 break;
1818               default:
1819                 sprintf(q, "%d", runlength);
1820                 while (*q) q++;
1821                 *q++ = curr;
1822                 break;
1823             }
1824             runlength = 1;
1825             curr = *p;
1826         }
1827     } while (*p++);
1828     *q = NULLCHAR;
1829 }
1830
1831 /* Telnet protocol requests from the front end */
1832 void
1833 TelnetRequest(ddww, option)
1834      unsigned char ddww, option;
1835 {
1836     unsigned char msg[3];
1837     int outCount, outError;
1838
1839     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1840
1841     if (appData.debugMode) {
1842         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1843         switch (ddww) {
1844           case TN_DO:
1845             ddwwStr = "DO";
1846             break;
1847           case TN_DONT:
1848             ddwwStr = "DONT";
1849             break;
1850           case TN_WILL:
1851             ddwwStr = "WILL";
1852             break;
1853           case TN_WONT:
1854             ddwwStr = "WONT";
1855             break;
1856           default:
1857             ddwwStr = buf1;
1858             sprintf(buf1, "%d", ddww);
1859             break;
1860         }
1861         switch (option) {
1862           case TN_ECHO:
1863             optionStr = "ECHO";
1864             break;
1865           default:
1866             optionStr = buf2;
1867             sprintf(buf2, "%d", option);
1868             break;
1869         }
1870         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871     }
1872     msg[0] = TN_IAC;
1873     msg[1] = ddww;
1874     msg[2] = option;
1875     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1876     if (outCount < 3) {
1877         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1878     }
1879 }
1880
1881 void
1882 DoEcho()
1883 {
1884     if (!appData.icsActive) return;
1885     TelnetRequest(TN_DO, TN_ECHO);
1886 }
1887
1888 void
1889 DontEcho()
1890 {
1891     if (!appData.icsActive) return;
1892     TelnetRequest(TN_DONT, TN_ECHO);
1893 }
1894
1895 void
1896 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1897 {
1898     /* put the holdings sent to us by the server on the board holdings area */
1899     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1900     char p;
1901     ChessSquare piece;
1902
1903     if(gameInfo.holdingsWidth < 2)  return;
1904     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1905         return; // prevent overwriting by pre-board holdings
1906
1907     if( (int)lowestPiece >= BlackPawn ) {
1908         holdingsColumn = 0;
1909         countsColumn = 1;
1910         holdingsStartRow = BOARD_HEIGHT-1;
1911         direction = -1;
1912     } else {
1913         holdingsColumn = BOARD_WIDTH-1;
1914         countsColumn = BOARD_WIDTH-2;
1915         holdingsStartRow = 0;
1916         direction = 1;
1917     }
1918
1919     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920         board[i][holdingsColumn] = EmptySquare;
1921         board[i][countsColumn]   = (ChessSquare) 0;
1922     }
1923     while( (p=*holdings++) != NULLCHAR ) {
1924         piece = CharToPiece( ToUpper(p) );
1925         if(piece == EmptySquare) continue;
1926         /*j = (int) piece - (int) WhitePawn;*/
1927         j = PieceToNumber(piece);
1928         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929         if(j < 0) continue;               /* should not happen */
1930         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932         board[holdingsStartRow+j*direction][countsColumn]++;
1933     }
1934 }
1935
1936
1937 void
1938 VariantSwitch(Board board, VariantClass newVariant)
1939 {
1940    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1941    Board oldBoard;
1942
1943    startedFromPositionFile = FALSE;
1944    if(gameInfo.variant == newVariant) return;
1945
1946    /* [HGM] This routine is called each time an assignment is made to
1947     * gameInfo.variant during a game, to make sure the board sizes
1948     * are set to match the new variant. If that means adding or deleting
1949     * holdings, we shift the playing board accordingly
1950     * This kludge is needed because in ICS observe mode, we get boards
1951     * of an ongoing game without knowing the variant, and learn about the
1952     * latter only later. This can be because of the move list we requested,
1953     * in which case the game history is refilled from the beginning anyway,
1954     * but also when receiving holdings of a crazyhouse game. In the latter
1955     * case we want to add those holdings to the already received position.
1956     */
1957
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        break;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013    } else gameInfo.variant = newVariant;
2014    CopyBoard(oldBoard, board);   // remember correctly formatted board
2015      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2016    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2082                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(TRUE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s\n", appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s\n", appData.premoveBlackText);
3245                         if (appData.debugMode)
3246                           fprintf(debugFP, "Sending premove:\n");
3247                         SendToICS(str);
3248                       } else if (gotPremove) {
3249                         gotPremove = 0;
3250                         ClearPremoveHighlights();
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                           UserMoveEvent(premoveFromX, premoveFromY, 
3254                                         premoveToX, premoveToY, 
3255                                         premovePromoChar);
3256                       }
3257                     }
3258
3259                     /* Usually suppress following prompt */
3260                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3262                         if (looking_at(buf, &i, "*% ")) {
3263                             savingComment = FALSE;
3264                         }
3265                     }
3266                     next_out = i;
3267                 } else if (started == STARTED_HOLDINGS) {
3268                     int gamenum;
3269                     char new_piece[MSG_SIZ];
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     if (appData.debugMode)
3273                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274                                                         parse, currentMove);
3275                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276                         gamenum == ics_gamenum) {
3277                         if (gameInfo.variant == VariantNormal) {
3278                           /* [HGM] We seem to switch variant during a game!
3279                            * Presumably no holdings were displayed, so we have
3280                            * to move the position two files to the right to
3281                            * create room for them!
3282                            */
3283                           VariantClass newVariant;
3284                           switch(gameInfo.boardWidth) { // base guess on board width
3285                                 case 9:  newVariant = VariantShogi; break;
3286                                 case 10: newVariant = VariantGreat; break;
3287                                 default: newVariant = VariantCrazyhouse; break;
3288                           }
3289                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3290                           /* Get a move list just to see the header, which
3291                              will tell us whether this is really bug or zh */
3292                           if (ics_getting_history == H_FALSE) {
3293                             ics_getting_history = H_REQUESTED;
3294                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3295                             SendToICS(str);
3296                           }
3297                         }
3298                         new_piece[0] = NULLCHAR;
3299                         sscanf(parse, "game %d white [%s black [%s <- %s",
3300                                &gamenum, white_holding, black_holding,
3301                                new_piece);
3302                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3303                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3304                         /* [HGM] copy holdings to board holdings area */
3305                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3306                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3307                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3308 #if ZIPPY
3309                         if (appData.zippyPlay && first.initDone) {
3310                             ZippyHoldings(white_holding, black_holding,
3311                                           new_piece);
3312                         }
3313 #endif /*ZIPPY*/
3314                         if (tinyLayout || smallLayout) {
3315                             char wh[16], bh[16];
3316                             PackHolding(wh, white_holding);
3317                             PackHolding(bh, black_holding);
3318                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319                                     gameInfo.white, gameInfo.black);
3320                         } else {
3321                             sprintf(str, "%s [%s] vs. %s [%s]",
3322                                     gameInfo.white, white_holding,
3323                                     gameInfo.black, black_holding);
3324                         }
3325
3326                         DrawPosition(FALSE, boards[currentMove]);
3327                         DisplayTitle(str);
3328                     }
3329                     /* Suppress following prompt */
3330                     if (looking_at(buf, &i, "*% ")) {
3331                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3332                         savingComment = FALSE;
3333                     }
3334                     next_out = i;
3335                 }
3336                 continue;
3337             }
3338
3339             i++;                /* skip unparsed character and loop back */
3340         }
3341         
3342         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3343             started != STARTED_HOLDINGS && i > next_out) {
3344             SendToPlayer(&buf[next_out], i - next_out);
3345             next_out = i;
3346         }
3347         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3348         
3349         leftover_len = buf_len - leftover_start;
3350         /* if buffer ends with something we couldn't parse,
3351            reparse it after appending the next read */
3352         
3353     } else if (count == 0) {
3354         RemoveInputSource(isr);
3355         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3356     } else {
3357         DisplayFatalError(_("Error reading from ICS"), error, 1);
3358     }
3359 }
3360
3361
3362 /* Board style 12 looks like this:
3363    
3364    <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
3365    
3366  * The "<12> " is stripped before it gets to this routine.  The two
3367  * trailing 0's (flip state and clock ticking) are later addition, and
3368  * some chess servers may not have them, or may have only the first.
3369  * Additional trailing fields may be added in the future.  
3370  */
3371
3372 #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"
3373
3374 #define RELATION_OBSERVING_PLAYED    0
3375 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3376 #define RELATION_PLAYING_MYMOVE      1
3377 #define RELATION_PLAYING_NOTMYMOVE  -1
3378 #define RELATION_EXAMINING           2
3379 #define RELATION_ISOLATED_BOARD     -3
3380 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3381
3382 void
3383 ParseBoard12(string)
3384      char *string;
3385
3386     GameMode newGameMode;
3387     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3388     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3389     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3390     char to_play, board_chars[200];
3391     char move_str[500], str[500], elapsed_time[500];
3392     char black[32], white[32];
3393     Board board;
3394     int prevMove = currentMove;
3395     int ticking = 2;
3396     ChessMove moveType;
3397     int fromX, fromY, toX, toY;
3398     char promoChar;
3399     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3400     char *bookHit = NULL; // [HGM] book
3401     Boolean weird = FALSE, reqFlag = FALSE;
3402
3403     fromX = fromY = toX = toY = -1;
3404     
3405     newGame = FALSE;
3406
3407     if (appData.debugMode)
3408       fprintf(debugFP, _("Parsing board: %s\n"), string);
3409
3410     move_str[0] = NULLCHAR;
3411     elapsed_time[0] = NULLCHAR;
3412     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3413         int  i = 0, j;
3414         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3415             if(string[i] == ' ') { ranks++; files = 0; }
3416             else files++;
3417             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3418             i++;
3419         }
3420         for(j = 0; j <i; j++) board_chars[j] = string[j];
3421         board_chars[i] = '\0';
3422         string += i + 1;
3423     }
3424     n = sscanf(string, PATTERN, &to_play, &double_push,
3425                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3426                &gamenum, white, black, &relation, &basetime, &increment,
3427                &white_stren, &black_stren, &white_time, &black_time,
3428                &moveNum, str, elapsed_time, move_str, &ics_flip,
3429                &ticking);
3430
3431     if (n < 21) {
3432         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3433         DisplayError(str, 0);
3434         return;
3435     }
3436
3437     /* Convert the move number to internal form */
3438     moveNum = (moveNum - 1) * 2;
3439     if (to_play == 'B') moveNum++;
3440     if (moveNum >= MAX_MOVES) {
3441       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3442                         0, 1);
3443       return;
3444     }
3445     
3446     switch (relation) {
3447       case RELATION_OBSERVING_PLAYED:
3448       case RELATION_OBSERVING_STATIC:
3449         if (gamenum == -1) {
3450             /* Old ICC buglet */
3451             relation = RELATION_OBSERVING_STATIC;
3452         }
3453         newGameMode = IcsObserving;
3454         break;
3455       case RELATION_PLAYING_MYMOVE:
3456       case RELATION_PLAYING_NOTMYMOVE:
3457         newGameMode =
3458           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3459             IcsPlayingWhite : IcsPlayingBlack;
3460         break;
3461       case RELATION_EXAMINING:
3462         newGameMode = IcsExamining;
3463         break;
3464       case RELATION_ISOLATED_BOARD:
3465       default:
3466         /* Just display this board.  If user was doing something else,
3467            we will forget about it until the next board comes. */ 
3468         newGameMode = IcsIdle;
3469         break;
3470       case RELATION_STARTING_POSITION:
3471         newGameMode = gameMode;
3472         break;
3473     }
3474     
3475     /* Modify behavior for initial board display on move listing
3476        of wild games.
3477        */
3478     switch (ics_getting_history) {
3479       case H_FALSE:
3480       case H_REQUESTED:
3481         break;
3482       case H_GOT_REQ_HEADER:
3483       case H_GOT_UNREQ_HEADER:
3484         /* This is the initial position of the current game */
3485         gamenum = ics_gamenum;
3486         moveNum = 0;            /* old ICS bug workaround */
3487         if (to_play == 'B') {
3488           startedFromSetupPosition = TRUE;
3489           blackPlaysFirst = TRUE;
3490           moveNum = 1;
3491           if (forwardMostMove == 0) forwardMostMove = 1;
3492           if (backwardMostMove == 0) backwardMostMove = 1;
3493           if (currentMove == 0) currentMove = 1;
3494         }
3495         newGameMode = gameMode;
3496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3497         break;
3498       case H_GOT_UNWANTED_HEADER:
3499         /* This is an initial board that we don't want */
3500         return;
3501       case H_GETTING_MOVES:
3502         /* Should not happen */
3503         DisplayError(_("Error gathering move list: extra board"), 0);
3504         ics_getting_history = H_FALSE;
3505         return;
3506     }
3507
3508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3509                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3510      /* [HGM] We seem to have switched variant unexpectedly
3511       * Try to guess new variant from board size
3512       */
3513           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3514           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3515           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3516           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3517           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3518           if(!weird) newVariant = VariantNormal;
3519           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3520           /* Get a move list just to see the header, which
3521              will tell us whether this is really bug or zh */
3522           if (ics_getting_history == H_FALSE) {
3523             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3524             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3525             SendToICS(str);
3526           }
3527     }
3528     
3529     /* Take action if this is the first board of a new game, or of a
3530        different game than is currently being displayed.  */
3531     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3532         relation == RELATION_ISOLATED_BOARD) {
3533         
3534         /* Forget the old game and get the history (if any) of the new one */
3535         if (gameMode != BeginningOfGame) {
3536           Reset(TRUE, TRUE);
3537         }
3538         newGame = TRUE;
3539         if (appData.autoRaiseBoard) BoardToTop();
3540         prevMove = -3;
3541         if (gamenum == -1) {
3542             newGameMode = IcsIdle;
3543         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3544                    appData.getMoveList && !reqFlag) {
3545             /* Need to get game history */
3546             ics_getting_history = H_REQUESTED;
3547             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3548             SendToICS(str);
3549         }
3550         
3551         /* Initially flip the board to have black on the bottom if playing
3552            black or if the ICS flip flag is set, but let the user change
3553            it with the Flip View button. */
3554         flipView = appData.autoFlipView ? 
3555           (newGameMode == IcsPlayingBlack) || ics_flip :
3556           appData.flipView;
3557         
3558         /* Done with values from previous mode; copy in new ones */
3559         gameMode = newGameMode;
3560         ModeHighlight();
3561         ics_gamenum = gamenum;
3562         if (gamenum == gs_gamenum) {
3563             int klen = strlen(gs_kind);
3564             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3565             sprintf(str, "ICS %s", gs_kind);
3566             gameInfo.event = StrSave(str);
3567         } else {
3568             gameInfo.event = StrSave("ICS game");
3569         }
3570         gameInfo.site = StrSave(appData.icsHost);
3571         gameInfo.date = PGNDate();
3572         gameInfo.round = StrSave("-");
3573         gameInfo.white = StrSave(white);
3574         gameInfo.black = StrSave(black);
3575         timeControl = basetime * 60 * 1000;
3576         timeControl_2 = 0;
3577         timeIncrement = increment * 1000;
3578         movesPerSession = 0;
3579         gameInfo.timeControl = TimeControlTagValue();
3580         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3581   if (appData.debugMode) {
3582     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3583     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3584     setbuf(debugFP, NULL);
3585   }
3586
3587         gameInfo.outOfBook = NULL;
3588         
3589         /* Do we have the ratings? */
3590         if (strcmp(player1Name, white) == 0 &&
3591             strcmp(player2Name, black) == 0) {
3592             if (appData.debugMode)
3593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3594                       player1Rating, player2Rating);
3595             gameInfo.whiteRating = player1Rating;
3596             gameInfo.blackRating = player2Rating;
3597         } else if (strcmp(player2Name, white) == 0 &&
3598                    strcmp(player1Name, black) == 0) {
3599             if (appData.debugMode)
3600               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3601                       player2Rating, player1Rating);
3602             gameInfo.whiteRating = player2Rating;
3603             gameInfo.blackRating = player1Rating;
3604         }
3605         player1Name[0] = player2Name[0] = NULLCHAR;
3606
3607         /* Silence shouts if requested */
3608         if (appData.quietPlay &&
3609             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3610             SendToICS(ics_prefix);
3611             SendToICS("set shout 0\n");
3612         }
3613     }
3614     
3615     /* Deal with midgame name changes */
3616     if (!newGame) {
3617         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3618             if (gameInfo.white) free(gameInfo.white);
3619             gameInfo.white = StrSave(white);
3620         }
3621         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3622             if (gameInfo.black) free(gameInfo.black);
3623             gameInfo.black = StrSave(black);
3624         }
3625     }
3626     
3627     /* Throw away game result if anything actually changes in examine mode */
3628     if (gameMode == IcsExamining && !newGame) {
3629         gameInfo.result = GameUnfinished;
3630         if (gameInfo.resultDetails != NULL) {
3631             free(gameInfo.resultDetails);
3632             gameInfo.resultDetails = NULL;
3633         }
3634     }
3635     
3636     /* In pausing && IcsExamining mode, we ignore boards coming
3637        in if they are in a different variation than we are. */
3638     if (pauseExamInvalid) return;
3639     if (pausing && gameMode == IcsExamining) {
3640         if (moveNum <= pauseExamForwardMostMove) {
3641             pauseExamInvalid = TRUE;
3642             forwardMostMove = pauseExamForwardMostMove;
3643             return;
3644         }
3645     }
3646     
3647   if (appData.debugMode) {
3648     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3649   }
3650     /* Parse the board */
3651     for (k = 0; k < ranks; k++) {
3652       for (j = 0; j < files; j++)
3653         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3654       if(gameInfo.holdingsWidth > 1) {
3655            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3656            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3657       }
3658     }
3659     CopyBoard(boards[moveNum], board);
3660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3661     if (moveNum == 0) {
3662         startedFromSetupPosition =
3663           !CompareBoards(board, initialPosition);
3664         if(startedFromSetupPosition)
3665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3666     }
3667
3668     /* [HGM] Set castling rights. Take the outermost Rooks,
3669        to make it also work for FRC opening positions. Note that board12
3670        is really defective for later FRC positions, as it has no way to
3671        indicate which Rook can castle if they are on the same side of King.
3672        For the initial position we grant rights to the outermost Rooks,
3673        and remember thos rights, and we then copy them on positions
3674        later in an FRC game. This means WB might not recognize castlings with
3675        Rooks that have moved back to their original position as illegal,
3676        but in ICS mode that is not its job anyway.
3677     */
3678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3680
3681         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3682             if(board[0][i] == WhiteRook) j = i;
3683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3684         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3685             if(board[0][i] == WhiteRook) j = i;
3686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3687         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3690         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3693
3694         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698             if(board[BOARD_HEIGHT-1][k] == bKing)
3699                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3700     } else { int r;
3701         r = boards[moveNum][CASTLING][0] = initialRights[0];
3702         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3703         r = boards[moveNum][CASTLING][1] = initialRights[1];
3704         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3705         r = boards[moveNum][CASTLING][3] = initialRights[3];
3706         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3707         r = boards[moveNum][CASTLING][4] = initialRights[4];
3708         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3709         /* wildcastle kludge: always assume King has rights */
3710         r = boards[moveNum][CASTLING][2] = initialRights[2];
3711         r = boards[moveNum][CASTLING][5] = initialRights[5];
3712     }
3713     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3714     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3715
3716     
3717     if (ics_getting_history == H_GOT_REQ_HEADER ||
3718         ics_getting_history == H_GOT_UNREQ_HEADER) {
3719         /* This was an initial position from a move list, not
3720            the current position */
3721         return;
3722     }
3723     
3724     /* Update currentMove and known move number limits */
3725     newMove = newGame || moveNum > forwardMostMove;
3726
3727     if (newGame) {
3728         forwardMostMove = backwardMostMove = currentMove = moveNum;
3729         if (gameMode == IcsExamining && moveNum == 0) {
3730           /* Workaround for ICS limitation: we are not told the wild
3731              type when starting to examine a game.  But if we ask for
3732              the move list, the move list header will tell us */
3733             ics_getting_history = H_REQUESTED;
3734             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3735             SendToICS(str);
3736         }
3737     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3738                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3739 #if ZIPPY
3740         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3741         /* [HGM] applied this also to an engine that is silently watching        */
3742         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3743             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3744             gameInfo.variant == currentlyInitializedVariant) {
3745           takeback = forwardMostMove - moveNum;
3746           for (i = 0; i < takeback; i++) {
3747             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3748             SendToProgram("undo\n", &first);
3749           }
3750         }
3751 #endif
3752
3753         forwardMostMove = moveNum;
3754         if (!pausing || currentMove > forwardMostMove)
3755           currentMove = forwardMostMove;
3756     } else {
3757         /* New part of history that is not contiguous with old part */ 
3758         if (pausing && gameMode == IcsExamining) {
3759             pauseExamInvalid = TRUE;
3760             forwardMostMove = pauseExamForwardMostMove;
3761             return;
3762         }
3763         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3764 #if ZIPPY
3765             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3766                 // [HGM] when we will receive the move list we now request, it will be
3767                 // fed to the engine from the first move on. So if the engine is not
3768                 // in the initial position now, bring it there.
3769                 InitChessProgram(&first, 0);
3770             }
3771 #endif
3772             ics_getting_history = H_REQUESTED;
3773             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3774             SendToICS(str);
3775         }
3776         forwardMostMove = backwardMostMove = currentMove = moveNum;
3777     }
3778     
3779     /* Update the clocks */
3780     if (strchr(elapsed_time, '.')) {
3781       /* Time is in ms */
3782       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3783       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3784     } else {
3785       /* Time is in seconds */
3786       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3787       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3788     }
3789       
3790
3791 #if ZIPPY
3792     if (appData.zippyPlay && newGame &&
3793         gameMode != IcsObserving && gameMode != IcsIdle &&
3794         gameMode != IcsExamining)
3795       ZippyFirstBoard(moveNum, basetime, increment);
3796 #endif
3797     
3798     /* Put the move on the move list, first converting
3799        to canonical algebraic form. */
3800     if (moveNum > 0) {
3801   if (appData.debugMode) {
3802     if (appData.debugMode) { int f = forwardMostMove;
3803         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3804                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3805                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3806     }
3807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808     fprintf(debugFP, "moveNum = %d\n", moveNum);
3809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810     setbuf(debugFP, NULL);
3811   }
3812         if (moveNum <= backwardMostMove) {
3813             /* We don't know what the board looked like before
3814                this move.  Punt. */
3815             strcpy(parseList[moveNum - 1], move_str);
3816             strcat(parseList[moveNum - 1], " ");
3817             strcat(parseList[moveNum - 1], elapsed_time);
3818             moveList[moveNum - 1][0] = NULLCHAR;
3819         } else if (strcmp(move_str, "none") == 0) {
3820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821             /* Again, we don't know what the board looked like;
3822                this is really the start of the game. */
3823             parseList[moveNum - 1][0] = NULLCHAR;
3824             moveList[moveNum - 1][0] = NULLCHAR;
3825             backwardMostMove = moveNum;
3826             startedFromSetupPosition = TRUE;
3827             fromX = fromY = toX = toY = -1;
3828         } else {
3829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3830           //                 So we parse the long-algebraic move string in stead of the SAN move
3831           int valid; char buf[MSG_SIZ], *prom;
3832
3833           // str looks something like "Q/a1-a2"; kill the slash
3834           if(str[1] == '/') 
3835                 sprintf(buf, "%c%s", str[0], str+2);
3836           else  strcpy(buf, str); // might be castling
3837           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3838                 strcat(buf, prom); // long move lacks promo specification!
3839           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840                 if(appData.debugMode) 
3841                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842                 strcpy(move_str, buf);
3843           }
3844           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845                                 &fromX, &fromY, &toX, &toY, &promoChar)
3846                || ParseOneMove(buf, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar);
3848           // end of long SAN patch
3849           if (valid) {
3850             (void) CoordsToAlgebraic(boards[moveNum - 1],
3851                                      PosFlags(moveNum - 1),
3852                                      fromY, fromX, toY, toX, promoChar,
3853                                      parseList[moveNum-1]);
3854             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3855               case MT_NONE:
3856               case MT_STALEMATE:
3857               default:
3858                 break;
3859               case MT_CHECK:
3860                 if(gameInfo.variant != VariantShogi)
3861                     strcat(parseList[moveNum - 1], "+");
3862                 break;
3863               case MT_CHECKMATE:
3864               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3865                 strcat(parseList[moveNum - 1], "#");
3866                 break;
3867             }
3868             strcat(parseList[moveNum - 1], " ");
3869             strcat(parseList[moveNum - 1], elapsed_time);
3870             /* currentMoveString is set as a side-effect of ParseOneMove */
3871             strcpy(moveList[moveNum - 1], currentMoveString);
3872             strcat(moveList[moveNum - 1], "\n");
3873           } else {
3874             /* Move from ICS was illegal!?  Punt. */
3875   if (appData.debugMode) {
3876     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3877     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3878   }
3879             strcpy(parseList[moveNum - 1], move_str);
3880             strcat(parseList[moveNum - 1], " ");
3881             strcat(parseList[moveNum - 1], elapsed_time);
3882             moveList[moveNum - 1][0] = NULLCHAR;
3883             fromX = fromY = toX = toY = -1;
3884           }
3885         }
3886   if (appData.debugMode) {
3887     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3888     setbuf(debugFP, NULL);
3889   }
3890
3891 #if ZIPPY
3892         /* Send move to chess program (BEFORE animating it). */
3893         if (appData.zippyPlay && !newGame && newMove && 
3894            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3895
3896             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3897                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3898                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3899                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3900                             move_str);
3901                     DisplayError(str, 0);
3902                 } else {
3903                     if (first.sendTime) {
3904                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3905                     }
3906                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3907                     if (firstMove && !bookHit) {
3908                         firstMove = FALSE;
3909                         if (first.useColors) {
3910                           SendToProgram(gameMode == IcsPlayingWhite ?
3911                                         "white\ngo\n" :
3912                                         "black\ngo\n", &first);
3913                         } else {
3914                           SendToProgram("go\n", &first);
3915                         }
3916                         first.maybeThinking = TRUE;
3917                     }
3918                 }
3919             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3920               if (moveList[moveNum - 1][0] == NULLCHAR) {
3921                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3922                 DisplayError(str, 0);
3923               } else {
3924                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3925                 SendMoveToProgram(moveNum - 1, &first);
3926               }
3927             }
3928         }
3929 #endif
3930     }
3931
3932     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3933         /* If move comes from a remote source, animate it.  If it
3934            isn't remote, it will have already been animated. */
3935         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3936             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3937         }
3938         if (!pausing && appData.highlightLastMove) {
3939             SetHighlights(fromX, fromY, toX, toY);
3940         }
3941     }
3942     
3943     /* Start the clocks */
3944     whiteFlag = blackFlag = FALSE;
3945     appData.clockMode = !(basetime == 0 && increment == 0);
3946     if (ticking == 0) {
3947       ics_clock_paused = TRUE;
3948       StopClocks();
3949     } else if (ticking == 1) {
3950       ics_clock_paused = FALSE;
3951     }
3952     if (gameMode == IcsIdle ||
3953         relation == RELATION_OBSERVING_STATIC ||
3954         relation == RELATION_EXAMINING ||
3955         ics_clock_paused)
3956       DisplayBothClocks();
3957     else
3958       StartClocks();
3959     
3960     /* Display opponents and material strengths */
3961     if (gameInfo.variant != VariantBughouse &&
3962         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3963         if (tinyLayout || smallLayout) {
3964             if(gameInfo.variant == VariantNormal)
3965                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3966                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3967                     basetime, increment);
3968             else
3969                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3970                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3971                     basetime, increment, (int) gameInfo.variant);
3972         } else {
3973             if(gameInfo.variant == VariantNormal)
3974                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3975                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3976                     basetime, increment);
3977             else
3978                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3980                     basetime, increment, VariantName(gameInfo.variant));
3981         }
3982         DisplayTitle(str);
3983   if (appData.debugMode) {
3984     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3985   }
3986     }
3987
3988    
3989     /* Display the board */
3990     if (!pausing && !appData.noGUI) {
3991       
3992       if (appData.premove)
3993           if (!gotPremove || 
3994              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3995              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3996               ClearPremoveHighlights();
3997
3998       DrawPosition(FALSE, boards[currentMove]);
3999       DisplayMove(moveNum - 1);
4000       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4001             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4002               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4003         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4004       }
4005     }
4006
4007     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4008 #if ZIPPY
4009     if(bookHit) { // [HGM] book: simulate book reply
4010         static char bookMove[MSG_SIZ]; // a bit generous?
4011
4012         programStats.nodes = programStats.depth = programStats.time = 
4013         programStats.score = programStats.got_only_move = 0;
4014         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4015
4016         strcpy(bookMove, "move ");
4017         strcat(bookMove, bookHit);
4018         HandleMachineMove(bookMove, &first);
4019     }
4020 #endif
4021 }
4022
4023 void
4024 GetMoveListEvent()
4025 {
4026     char buf[MSG_SIZ];
4027     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4028         ics_getting_history = H_REQUESTED;
4029         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4030         SendToICS(buf);
4031     }
4032 }
4033
4034 void
4035 AnalysisPeriodicEvent(force)
4036      int force;
4037 {
4038     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4039          && !force) || !appData.periodicUpdates)
4040       return;
4041
4042     /* Send . command to Crafty to collect stats */
4043     SendToProgram(".\n", &first);
4044
4045     /* Don't send another until we get a response (this makes
4046        us stop sending to old Crafty's which don't understand
4047        the "." command (sending illegal cmds resets node count & time,
4048        which looks bad)) */
4049     programStats.ok_to_send = 0;
4050 }
4051
4052 void ics_update_width(new_width)
4053         int new_width;
4054 {
4055         ics_printf("set width %d\n", new_width);
4056 }
4057
4058 void
4059 SendMoveToProgram(moveNum, cps)
4060      int moveNum;
4061      ChessProgramState *cps;
4062 {
4063     char buf[MSG_SIZ];
4064
4065     if (cps->useUsermove) {
4066       SendToProgram("usermove ", cps);
4067     }
4068     if (cps->useSAN) {
4069       char *space;
4070       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4071         int len = space - parseList[moveNum];
4072         memcpy(buf, parseList[moveNum], len);
4073         buf[len++] = '\n';
4074         buf[len] = NULLCHAR;
4075       } else {
4076         sprintf(buf, "%s\n", parseList[moveNum]);
4077       }
4078       SendToProgram(buf, cps);
4079     } else {
4080       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4081         AlphaRank(moveList[moveNum], 4);
4082         SendToProgram(moveList[moveNum], cps);
4083         AlphaRank(moveList[moveNum], 4); // and back
4084       } else
4085       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4086        * the engine. It would be nice to have a better way to identify castle 
4087        * moves here. */
4088       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4089                                                                          && cps->useOOCastle) {
4090         int fromX = moveList[moveNum][0] - AAA; 
4091         int fromY = moveList[moveNum][1] - ONE;
4092         int toX = moveList[moveNum][2] - AAA; 
4093         int toY = moveList[moveNum][3] - ONE;
4094         if((boards[moveNum][fromY][fromX] == WhiteKing 
4095             && boards[moveNum][toY][toX] == WhiteRook)
4096            || (boards[moveNum][fromY][fromX] == BlackKing 
4097                && boards[moveNum][toY][toX] == BlackRook)) {
4098           if(toX > fromX) SendToProgram("O-O\n", cps);
4099           else SendToProgram("O-O-O\n", cps);
4100         }
4101         else SendToProgram(moveList[moveNum], cps);
4102       }
4103       else SendToProgram(moveList[moveNum], cps);
4104       /* End of additions by Tord */
4105     }
4106
4107     /* [HGM] setting up the opening has brought engine in force mode! */
4108     /*       Send 'go' if we are in a mode where machine should play. */
4109     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4110         (gameMode == TwoMachinesPlay   ||
4111 #ifdef ZIPPY
4112          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4113 #endif
4114          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4115         SendToProgram("go\n", cps);
4116   if (appData.debugMode) {
4117     fprintf(debugFP, "(extra)\n");
4118   }
4119     }
4120     setboardSpoiledMachineBlack = 0;
4121 }
4122
4123 void
4124 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4125      ChessMove moveType;
4126      int fromX, fromY, toX, toY;
4127 {
4128     char user_move[MSG_SIZ];
4129
4130     switch (moveType) {
4131       default:
4132         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4133                 (int)moveType, fromX, fromY, toX, toY);
4134         DisplayError(user_move + strlen("say "), 0);
4135         break;
4136       case WhiteKingSideCastle:
4137       case BlackKingSideCastle:
4138       case WhiteQueenSideCastleWild:
4139       case BlackQueenSideCastleWild:
4140       /* PUSH Fabien */
4141       case WhiteHSideCastleFR:
4142       case BlackHSideCastleFR:
4143       /* POP Fabien */
4144         sprintf(user_move, "o-o\n");
4145         break;
4146       case WhiteQueenSideCastle:
4147       case BlackQueenSideCastle:
4148       case WhiteKingSideCastleWild:
4149       case BlackKingSideCastleWild:
4150       /* PUSH Fabien */
4151       case WhiteASideCastleFR:
4152       case BlackASideCastleFR:
4153       /* POP Fabien */
4154         sprintf(user_move, "o-o-o\n");
4155         break;
4156       case WhitePromotionQueen:
4157       case BlackPromotionQueen:
4158       case WhitePromotionRook:
4159       case BlackPromotionRook:
4160       case WhitePromotionBishop:
4161       case BlackPromotionBishop:
4162       case WhitePromotionKnight:
4163       case BlackPromotionKnight:
4164       case WhitePromotionKing:
4165       case BlackPromotionKing:
4166       case WhitePromotionChancellor:
4167       case BlackPromotionChancellor:
4168       case WhitePromotionArchbishop:
4169       case BlackPromotionArchbishop:
4170         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4171             sprintf(user_move, "%c%c%c%c=%c\n",
4172                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173                 PieceToChar(WhiteFerz));
4174         else if(gameInfo.variant == VariantGreat)
4175             sprintf(user_move, "%c%c%c%c=%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177                 PieceToChar(WhiteMan));
4178         else
4179             sprintf(user_move, "%c%c%c%c=%c\n",
4180                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181                 PieceToChar(PromoPiece(moveType)));
4182         break;
4183       case WhiteDrop:
4184       case BlackDrop:
4185         sprintf(user_move, "%c@%c%c\n",
4186                 ToUpper(PieceToChar((ChessSquare) fromX)),
4187                 AAA + toX, ONE + toY);
4188         break;
4189       case NormalMove:
4190       case WhiteCapturesEnPassant:
4191       case BlackCapturesEnPassant:
4192       case IllegalMove:  /* could be a variant we don't quite understand */
4193         sprintf(user_move, "%c%c%c%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4195         break;
4196     }
4197     SendToICS(user_move);
4198     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4199         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4200 }
4201
4202 void
4203 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4204      int rf, ff, rt, ft;
4205      char promoChar;
4206      char move[7];
4207 {
4208     if (rf == DROP_RANK) {
4209         sprintf(move, "%c@%c%c\n",
4210                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4211     } else {
4212         if (promoChar == 'x' || promoChar == NULLCHAR) {
4213             sprintf(move, "%c%c%c%c\n",
4214                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4215         } else {
4216             sprintf(move, "%c%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4218         }
4219     }
4220 }
4221
4222 void
4223 ProcessICSInitScript(f)
4224      FILE *f;
4225 {
4226     char buf[MSG_SIZ];
4227
4228     while (fgets(buf, MSG_SIZ, f)) {
4229         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4230     }
4231
4232     fclose(f);
4233 }
4234
4235
4236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4237 void
4238 AlphaRank(char *move, int n)
4239 {
4240 //    char *p = move, c; int x, y;
4241
4242     if (appData.debugMode) {
4243         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4244     }
4245
4246     if(move[1]=='*' && 
4247        move[2]>='0' && move[2]<='9' &&
4248        move[3]>='a' && move[3]<='x'    ) {
4249         move[1] = '@';
4250         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4251         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4252     } else
4253     if(move[0]>='0' && move[0]<='9' &&
4254        move[1]>='a' && move[1]<='x' &&
4255        move[2]>='0' && move[2]<='9' &&
4256        move[3]>='a' && move[3]<='x'    ) {
4257         /* input move, Shogi -> normal */
4258         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4259         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4260         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4261         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4262     } else
4263     if(move[1]=='@' &&
4264        move[3]>='0' && move[3]<='9' &&
4265        move[2]>='a' && move[2]<='x'    ) {
4266         move[1] = '*';
4267         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4268         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4269     } else
4270     if(
4271        move[0]>='a' && move[0]<='x' &&
4272        move[3]>='0' && move[3]<='9' &&
4273        move[2]>='a' && move[2]<='x'    ) {
4274          /* output move, normal -> Shogi */
4275         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4276         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4277         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4278         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4279         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4280     }
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "   out = '%s'\n", move);
4283     }
4284 }
4285
4286 /* Parser for moves from gnuchess, ICS, or user typein box */
4287 Boolean
4288 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4289      char *move;
4290      int moveNum;
4291      ChessMove *moveType;
4292      int *fromX, *fromY, *toX, *toY;
4293      char *promoChar;
4294 {       
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "move to parse: %s\n", move);
4297     }
4298     *moveType = yylexstr(moveNum, move);
4299
4300     switch (*moveType) {
4301       case WhitePromotionChancellor:
4302       case BlackPromotionChancellor:
4303       case WhitePromotionArchbishop:
4304       case BlackPromotionArchbishop:
4305       case WhitePromotionQueen:
4306       case BlackPromotionQueen:
4307       case WhitePromotionRook:
4308       case BlackPromotionRook:
4309       case WhitePromotionBishop:
4310       case BlackPromotionBishop:
4311       case WhitePromotionKnight:
4312       case BlackPromotionKnight:
4313       case WhitePromotionKing:
4314       case BlackPromotionKing:
4315       case NormalMove:
4316       case WhiteCapturesEnPassant:
4317       case BlackCapturesEnPassant:
4318       case WhiteKingSideCastle:
4319       case WhiteQueenSideCastle:
4320       case BlackKingSideCastle:
4321       case BlackQueenSideCastle:
4322       case WhiteKingSideCastleWild:
4323       case WhiteQueenSideCastleWild:
4324       case BlackKingSideCastleWild:
4325       case BlackQueenSideCastleWild:
4326       /* Code added by Tord: */
4327       case WhiteHSideCastleFR:
4328       case WhiteASideCastleFR:
4329       case BlackHSideCastleFR:
4330       case BlackASideCastleFR:
4331       /* End of code added by Tord */
4332       case IllegalMove:         /* bug or odd chess variant */
4333         *fromX = currentMoveString[0] - AAA;
4334         *fromY = currentMoveString[1] - ONE;
4335         *toX = currentMoveString[2] - AAA;
4336         *toY = currentMoveString[3] - ONE;
4337         *promoChar = currentMoveString[4];
4338         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4339             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4340     if (appData.debugMode) {
4341         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4342     }
4343             *fromX = *fromY = *toX = *toY = 0;
4344             return FALSE;
4345         }
4346         if (appData.testLegality) {
4347           return (*moveType != IllegalMove);
4348         } else {
4349           return !(fromX == fromY && toX == toY);
4350         }
4351
4352       case WhiteDrop:
4353       case BlackDrop:
4354         *fromX = *moveType == WhiteDrop ?
4355           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4356           (int) CharToPiece(ToLower(currentMoveString[0]));
4357         *fromY = DROP_RANK;
4358         *toX = currentMoveString[2] - AAA;
4359         *toY = currentMoveString[3] - ONE;
4360         *promoChar = NULLCHAR;
4361         return TRUE;
4362
4363       case AmbiguousMove:
4364       case ImpossibleMove:
4365       case (ChessMove) 0:       /* end of file */
4366       case ElapsedTime:
4367       case Comment:
4368       case PGNTag:
4369       case NAG:
4370       case WhiteWins:
4371       case BlackWins:
4372       case GameIsDrawn:
4373       default:
4374     if (appData.debugMode) {
4375         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4376     }
4377         /* bug? */
4378         *fromX = *fromY = *toX = *toY = 0;
4379         *promoChar = NULLCHAR;
4380         return FALSE;
4381     }
4382 }
4383
4384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4385 // All positions will have equal probability, but the current method will not provide a unique
4386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4387 #define DARK 1
4388 #define LITE 2
4389 #define ANY 3
4390
4391 int squaresLeft[4];
4392 int piecesLeft[(int)BlackPawn];
4393 int seed, nrOfShuffles;
4394
4395 void GetPositionNumber()
4396 {       // sets global variable seed
4397         int i;
4398
4399         seed = appData.defaultFrcPosition;
4400         if(seed < 0) { // randomize based on time for negative FRC position numbers
4401                 for(i=0; i<50; i++) seed += random();
4402                 seed = random() ^ random() >> 8 ^ random() << 8;
4403                 if(seed<0) seed = -seed;
4404         }
4405 }
4406
4407 int put(Board board, int pieceType, int rank, int n, int shade)
4408 // put the piece on the (n-1)-th empty squares of the given shade
4409 {
4410         int i;
4411
4412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4413                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4414                         board[rank][i] = (ChessSquare) pieceType;
4415                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4416                         squaresLeft[ANY]--;
4417                         piecesLeft[pieceType]--; 
4418                         return i;
4419                 }
4420         }
4421         return -1;
4422 }
4423
4424
4425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4426 // calculate where the next piece goes, (any empty square), and put it there
4427 {
4428         int i;
4429
4430         i = seed % squaresLeft[shade];
4431         nrOfShuffles *= squaresLeft[shade];
4432         seed /= squaresLeft[shade];
4433         put(board, pieceType, rank, i, shade);
4434 }
4435
4436 void AddTwoPieces(Board board, int pieceType, int rank)
4437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4438 {
4439         int i, n=squaresLeft[ANY], j=n-1, k;
4440
4441         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4442         i = seed % k;  // pick one
4443         nrOfShuffles *= k;
4444         seed /= k;
4445         while(i >= j) i -= j--;
4446         j = n - 1 - j; i += j;
4447         put(board, pieceType, rank, j, ANY);
4448         put(board, pieceType, rank, i, ANY);
4449 }
4450
4451 void SetUpShuffle(Board board, int number)
4452 {
4453         int i, p, first=1;
4454
4455         GetPositionNumber(); nrOfShuffles = 1;
4456
4457         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4458         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4459         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4460
4461         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4462
4463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4464             p = (int) board[0][i];
4465             if(p < (int) BlackPawn) piecesLeft[p] ++;
4466             board[0][i] = EmptySquare;
4467         }
4468
4469         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4470             // shuffles restricted to allow normal castling put KRR first
4471             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4472                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4473             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4474                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4475             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4476                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4477             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4478                 put(board, WhiteRook, 0, 0, ANY);
4479             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4480         }
4481
4482         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4483             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4484             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4485                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4486                 while(piecesLeft[p] >= 2) {
4487                     AddOnePiece(board, p, 0, LITE);
4488                     AddOnePiece(board, p, 0, DARK);
4489                 }
4490                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4491             }
4492
4493         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4494             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4495             // but we leave King and Rooks for last, to possibly obey FRC restriction
4496             if(p == (int)WhiteRook) continue;
4497             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4498             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4499         }
4500
4501         // now everything is placed, except perhaps King (Unicorn) and Rooks
4502
4503         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4504             // Last King gets castling rights
4505             while(piecesLeft[(int)WhiteUnicorn]) {
4506                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4507                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4508             }
4509
4510             while(piecesLeft[(int)WhiteKing]) {
4511                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4512                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4513             }
4514
4515
4516         } else {
4517             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4518             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4519         }
4520
4521         // Only Rooks can be left; simply place them all
4522         while(piecesLeft[(int)WhiteRook]) {
4523                 i = put(board, WhiteRook, 0, 0, ANY);
4524                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4525                         if(first) {
4526                                 first=0;
4527                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4528                         }
4529                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4530                 }
4531         }
4532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4533             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4534         }
4535
4536         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4537 }
4538
4539 int SetCharTable( char *table, const char * map )
4540 /* [HGM] moved here from winboard.c because of its general usefulness */
4541 /*       Basically a safe strcpy that uses the last character as King */
4542 {
4543     int result = FALSE; int NrPieces;
4544
4545     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4546                     && NrPieces >= 12 && !(NrPieces&1)) {
4547         int i; /* [HGM] Accept even length from 12 to 34 */
4548
4549         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4550         for( i=0; i<NrPieces/2-1; i++ ) {
4551             table[i] = map[i];
4552             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4553         }
4554         table[(int) WhiteKing]  = map[NrPieces/2-1];
4555         table[(int) BlackKing]  = map[NrPieces-1];
4556
4557         result = TRUE;
4558     }
4559
4560     return result;
4561 }
4562
4563 void Prelude(Board board)
4564 {       // [HGM] superchess: random selection of exo-pieces
4565         int i, j, k; ChessSquare p; 
4566         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4567
4568         GetPositionNumber(); // use FRC position number
4569
4570         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4571             SetCharTable(pieceToChar, appData.pieceToCharTable);
4572             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4573                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4574         }
4575
4576         j = seed%4;                 seed /= 4; 
4577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4580         j = seed%3 + (seed%3 >= j); seed /= 3; 
4581         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4584         j = seed%3;                 seed /= 3; 
4585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%2 + (seed%2 >= j); seed /= 2; 
4589         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4593         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4594         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4595         put(board, exoPieces[0],    0, 0, ANY);
4596         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4597 }
4598
4599 void
4600 InitPosition(redraw)
4601      int redraw;
4602 {
4603     ChessSquare (* pieces)[BOARD_FILES];
4604     int i, j, pawnRow, overrule,
4605     oldx = gameInfo.boardWidth,
4606     oldy = gameInfo.boardHeight,
4607     oldh = gameInfo.holdingsWidth,
4608     oldv = gameInfo.variant;
4609
4610     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4611
4612     /* [AS] Initialize pv info list [HGM] and game status */
4613     {
4614         for( i=0; i<MAX_MOVES; i++ ) {
4615             pvInfoList[i].depth = 0;
4616             boards[i][EP_STATUS] = EP_NONE;
4617             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4618         }
4619
4620         initialRulePlies = 0; /* 50-move counter start */
4621
4622         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4623         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4624     }
4625
4626     
4627     /* [HGM] logic here is completely changed. In stead of full positions */
4628     /* the initialized data only consist of the two backranks. The switch */
4629     /* selects which one we will use, which is than copied to the Board   */
4630     /* initialPosition, which for the rest is initialized by Pawns and    */
4631     /* empty squares. This initial position is then copied to boards[0],  */
4632     /* possibly after shuffling, so that it remains available.            */
4633
4634     gameInfo.holdingsWidth = 0; /* default board sizes */
4635     gameInfo.boardWidth    = 8;
4636     gameInfo.boardHeight   = 8;
4637     gameInfo.holdingsSize  = 0;
4638     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4639     for(i=0; i<BOARD_FILES-2; i++)
4640       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4641     initialPosition[EP_STATUS] = EP_NONE;
4642     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4643
4644     switch (gameInfo.variant) {
4645     case VariantFischeRandom:
4646       shuffleOpenings = TRUE;
4647     default:
4648       pieces = FIDEArray;
4649       break;
4650     case VariantShatranj:
4651       pieces = ShatranjArray;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4654       break;
4655     case VariantTwoKings:
4656       pieces = twoKingsArray;
4657       break;
4658     case VariantCapaRandom:
4659       shuffleOpenings = TRUE;
4660     case VariantCapablanca:
4661       pieces = CapablancaArray;
4662       gameInfo.boardWidth = 10;
4663       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4664       break;
4665     case VariantGothic:
4666       pieces = GothicArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4669       break;
4670     case VariantJanus:
4671       pieces = JanusArray;
4672       gameInfo.boardWidth = 10;
4673       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4674       nrCastlingRights = 6;
4675         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4676         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4677         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4678         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4679         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4680         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4681       break;
4682     case VariantFalcon:
4683       pieces = FalconArray;
4684       gameInfo.boardWidth = 10;
4685       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4686       break;
4687     case VariantXiangqi:
4688       pieces = XiangqiArray;
4689       gameInfo.boardWidth  = 9;
4690       gameInfo.boardHeight = 10;
4691       nrCastlingRights = 0;
4692       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4693       break;
4694     case VariantShogi:
4695       pieces = ShogiArray;
4696       gameInfo.boardWidth  = 9;
4697       gameInfo.boardHeight = 9;
4698       gameInfo.holdingsSize = 7;
4699       nrCastlingRights = 0;
4700       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4701       break;
4702     case VariantCourier:
4703       pieces = CourierArray;
4704       gameInfo.boardWidth  = 12;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4707       break;
4708     case VariantKnightmate:
4709       pieces = KnightmateArray;
4710       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4711       break;
4712     case VariantFairy:
4713       pieces = fairyArray;
4714       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4715       break;
4716     case VariantGreat:
4717       pieces = GreatArray;
4718       gameInfo.boardWidth = 10;
4719       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4720       gameInfo.holdingsSize = 8;
4721       break;
4722     case VariantSuper:
4723       pieces = FIDEArray;
4724       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4725       gameInfo.holdingsSize = 8;
4726       startedFromSetupPosition = TRUE;
4727       break;
4728     case VariantCrazyhouse:
4729     case VariantBughouse:
4730       pieces = FIDEArray;
4731       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4732       gameInfo.holdingsSize = 5;
4733       break;
4734     case VariantWildCastle:
4735       pieces = FIDEArray;
4736       /* !!?shuffle with kings guaranteed to be on d or e file */
4737       shuffleOpenings = 1;
4738       break;
4739     case VariantNoCastle:
4740       pieces = FIDEArray;
4741       nrCastlingRights = 0;
4742       /* !!?unconstrained back-rank shuffle */
4743       shuffleOpenings = 1;
4744       break;
4745     }
4746
4747     overrule = 0;
4748     if(appData.NrFiles >= 0) {
4749         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4750         gameInfo.boardWidth = appData.NrFiles;
4751     }
4752     if(appData.NrRanks >= 0) {
4753         gameInfo.boardHeight = appData.NrRanks;
4754     }
4755     if(appData.holdingsSize >= 0) {
4756         i = appData.holdingsSize;
4757         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4758         gameInfo.holdingsSize = i;
4759     }
4760     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4761     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4762         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4763
4764     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4765     if(pawnRow < 1) pawnRow = 1;
4766
4767     /* User pieceToChar list overrules defaults */
4768     if(appData.pieceToCharTable != NULL)
4769         SetCharTable(pieceToChar, appData.pieceToCharTable);
4770
4771     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4772
4773         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4774             s = (ChessSquare) 0; /* account holding counts in guard band */
4775         for( i=0; i<BOARD_HEIGHT; i++ )
4776             initialPosition[i][j] = s;
4777
4778         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4779         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4780         initialPosition[pawnRow][j] = WhitePawn;
4781         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4782         if(gameInfo.variant == VariantXiangqi) {
4783             if(j&1) {
4784                 initialPosition[pawnRow][j] = 
4785                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4786                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4787                    initialPosition[2][j] = WhiteCannon;
4788                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4789                 }
4790             }
4791         }
4792         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4793     }
4794     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4795
4796             j=BOARD_LEFT+1;
4797             initialPosition[1][j] = WhiteBishop;
4798             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4799             j=BOARD_RGHT-2;
4800             initialPosition[1][j] = WhiteRook;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4802     }
4803
4804     if( nrCastlingRights == -1) {
4805         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4806         /*       This sets default castling rights from none to normal corners   */
4807         /* Variants with other castling rights must set them themselves above    */
4808         nrCastlingRights = 6;
4809        
4810         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4811         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4812         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4813         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4814         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4815         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4816      }
4817
4818      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4819      if(gameInfo.variant == VariantGreat) { // promotion commoners
4820         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4821         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4822         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4823         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4824      }
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4827   }
4828     if(shuffleOpenings) {
4829         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4830         startedFromSetupPosition = TRUE;
4831     }
4832     if(startedFromPositionFile) {
4833       /* [HGM] loadPos: use PositionFile for every new game */
4834       CopyBoard(initialPosition, filePosition);
4835       for(i=0; i<nrCastlingRights; i++)
4836           initialRights[i] = filePosition[CASTLING][i];
4837       startedFromSetupPosition = TRUE;
4838     }
4839
4840     CopyBoard(boards[0], initialPosition);
4841
4842     if(oldx != gameInfo.boardWidth ||
4843        oldy != gameInfo.boardHeight ||
4844        oldh != gameInfo.holdingsWidth
4845 #ifdef GOTHIC
4846        || oldv == VariantGothic ||        // For licensing popups
4847        gameInfo.variant == VariantGothic
4848 #endif
4849 #ifdef FALCON
4850        || oldv == VariantFalcon ||
4851        gameInfo.variant == VariantFalcon
4852 #endif
4853                                          )
4854             InitDrawingSizes(-2 ,0);
4855
4856     if (redraw)
4857       DrawPosition(TRUE, boards[currentMove]);
4858 }
4859
4860 void
4861 SendBoard(cps, moveNum)
4862      ChessProgramState *cps;
4863      int moveNum;
4864 {
4865     char message[MSG_SIZ];
4866     
4867     if (cps->useSetboard) {
4868       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4869       sprintf(message, "setboard %s\n", fen);
4870       SendToProgram(message, cps);
4871       free(fen);
4872
4873     } else {
4874       ChessSquare *bp;
4875       int i, j;
4876       /* Kludge to set black to move, avoiding the troublesome and now
4877        * deprecated "black" command.
4878        */
4879       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4880
4881       SendToProgram("edit\n", cps);
4882       SendToProgram("#\n", cps);
4883       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4884         bp = &boards[moveNum][i][BOARD_LEFT];
4885         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4886           if ((int) *bp < (int) BlackPawn) {
4887             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4888                     AAA + j, ONE + i);
4889             if(message[0] == '+' || message[0] == '~') {
4890                 sprintf(message, "%c%c%c+\n",
4891                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4892                         AAA + j, ONE + i);
4893             }
4894             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4895                 message[1] = BOARD_RGHT   - 1 - j + '1';
4896                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4897             }
4898             SendToProgram(message, cps);
4899           }
4900         }
4901       }
4902     
4903       SendToProgram("c\n", cps);
4904       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4905         bp = &boards[moveNum][i][BOARD_LEFT];
4906         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4907           if (((int) *bp != (int) EmptySquare)
4908               && ((int) *bp >= (int) BlackPawn)) {
4909             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4910                     AAA + j, ONE + i);
4911             if(message[0] == '+' || message[0] == '~') {
4912                 sprintf(message, "%c%c%c+\n",
4913                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4914                         AAA + j, ONE + i);
4915             }
4916             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4917                 message[1] = BOARD_RGHT   - 1 - j + '1';
4918                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4919             }
4920             SendToProgram(message, cps);
4921           }
4922         }
4923       }
4924     
4925       SendToProgram(".\n", cps);
4926     }
4927     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4928 }
4929
4930 int
4931 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4932 {
4933     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4934     /* [HGM] add Shogi promotions */
4935     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4936     ChessSquare piece;
4937     ChessMove moveType;
4938     Boolean premove;
4939
4940     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4941     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4942
4943     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4944       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4945         return FALSE;
4946
4947     piece = boards[currentMove][fromY][fromX];
4948     if(gameInfo.variant == VariantShogi) {
4949         promotionZoneSize = 3;
4950         highestPromotingPiece = (int)WhiteFerz;
4951     }
4952
4953     // next weed out all moves that do not touch the promotion zone at all
4954     if((int)piece >= BlackPawn) {
4955         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4956              return FALSE;
4957         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4958     } else {
4959         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4960            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4961     }
4962
4963     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4964
4965     // weed out mandatory Shogi promotions
4966     if(gameInfo.variant == VariantShogi) {
4967         if(piece >= BlackPawn) {
4968             if(toY == 0 && piece == BlackPawn ||
4969                toY == 0 && piece == BlackQueen ||
4970                toY <= 1 && piece == BlackKnight) {
4971                 *promoChoice = '+';
4972                 return FALSE;
4973             }
4974         } else {
4975             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4976                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4977                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4978                 *promoChoice = '+';
4979                 return FALSE;
4980             }
4981         }
4982     }
4983
4984     // weed out obviously illegal Pawn moves
4985     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4986         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4987         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4988         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4989         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4990         // note we are not allowed to test for valid (non-)capture, due to premove
4991     }
4992
4993     // we either have a choice what to promote to, or (in Shogi) whether to promote
4994     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4995         *promoChoice = PieceToChar(BlackFerz);  // no choice
4996         return FALSE;
4997     }
4998     if(appData.alwaysPromoteToQueen) { // predetermined
4999         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5000              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5001         else *promoChoice = PieceToChar(BlackQueen);
5002         return FALSE;
5003     }
5004
5005     // suppress promotion popup on illegal moves that are not premoves
5006     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5007               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5008     if(appData.testLegality && !premove) {
5009         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5010                         fromY, fromX, toY, toX, NULLCHAR);
5011         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5012            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5013             return FALSE;
5014     }
5015
5016     return TRUE;
5017 }
5018
5019 int
5020 InPalace(row, column)
5021      int row, column;
5022 {   /* [HGM] for Xiangqi */
5023     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5024          column < (BOARD_WIDTH + 4)/2 &&
5025          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5026     return FALSE;
5027 }
5028
5029 int
5030 PieceForSquare (x, y)
5031      int x;
5032      int y;
5033 {
5034   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5035      return -1;
5036   else
5037      return boards[currentMove][y][x];
5038 }
5039
5040 int
5041 OKToStartUserMove(x, y)
5042      int x, y;
5043 {
5044     ChessSquare from_piece;
5045     int white_piece;
5046
5047     if (matchMode) return FALSE;
5048     if (gameMode == EditPosition) return TRUE;
5049
5050     if (x >= 0 && y >= 0)
5051       from_piece = boards[currentMove][y][x];
5052     else
5053       from_piece = EmptySquare;
5054
5055     if (from_piece == EmptySquare) return FALSE;
5056
5057     white_piece = (int)from_piece >= (int)WhitePawn &&
5058       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5059
5060     switch (gameMode) {
5061       case PlayFromGameFile:
5062       case AnalyzeFile:
5063       case TwoMachinesPlay:
5064       case EndOfGame:
5065         return FALSE;
5066
5067       case IcsObserving:
5068       case IcsIdle:
5069         return FALSE;
5070
5071       case MachinePlaysWhite:
5072       case IcsPlayingBlack:
5073         if (appData.zippyPlay) return FALSE;
5074         if (white_piece) {
5075             DisplayMoveError(_("You are playing Black"));
5076             return FALSE;
5077         }
5078         break;
5079
5080       case MachinePlaysBlack:
5081       case IcsPlayingWhite:
5082         if (appData.zippyPlay) return FALSE;
5083         if (!white_piece) {
5084             DisplayMoveError(_("You are playing White"));
5085             return FALSE;
5086         }
5087         break;
5088
5089       case EditGame:
5090         if (!white_piece && WhiteOnMove(currentMove)) {
5091             DisplayMoveError(_("It is White's turn"));
5092             return FALSE;
5093         }           
5094         if (white_piece && !WhiteOnMove(currentMove)) {
5095             DisplayMoveError(_("It is Black's turn"));
5096             return FALSE;
5097         }           
5098         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5099             /* Editing correspondence game history */
5100             /* Could disallow this or prompt for confirmation */
5101             cmailOldMove = -1;
5102         }
5103         if (currentMove < forwardMostMove) {
5104             /* Discarding moves */
5105             /* Could prompt for confirmation here,
5106                but I don't think that's such a good idea */
5107             forwardMostMove = currentMove;
5108         }
5109         break;
5110
5111       case BeginningOfGame:
5112         if (appData.icsActive) return FALSE;
5113         if (!appData.noChessProgram) {
5114             if (!white_piece) {
5115                 DisplayMoveError(_("You are playing White"));
5116                 return FALSE;
5117             }
5118         }
5119         break;
5120         
5121       case Training:
5122         if (!white_piece && WhiteOnMove(currentMove)) {
5123             DisplayMoveError(_("It is White's turn"));
5124             return FALSE;
5125         }           
5126         if (white_piece && !WhiteOnMove(currentMove)) {
5127             DisplayMoveError(_("It is Black's turn"));
5128             return FALSE;
5129         }           
5130         break;
5131
5132       default:
5133       case IcsExamining:
5134         break;
5135     }
5136     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5137         && gameMode != AnalyzeFile && gameMode != Training) {
5138         DisplayMoveError(_("Displayed position is not current"));
5139         return FALSE;
5140     }
5141     return TRUE;
5142 }
5143
5144 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5145 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5146 int lastLoadGameUseList = FALSE;
5147 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5148 ChessMove lastLoadGameStart = (ChessMove) 0;
5149
5150 ChessMove
5151 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5152      int fromX, fromY, toX, toY;
5153      int promoChar;
5154      Boolean captureOwn;
5155 {
5156     ChessMove moveType;
5157     ChessSquare pdown, pup;
5158
5159     /* Check if the user is playing in turn.  This is complicated because we
5160        let the user "pick up" a piece before it is his turn.  So the piece he
5161        tried to pick up may have been captured by the time he puts it down!
5162        Therefore we use the color the user is supposed to be playing in this
5163        test, not the color of the piece that is currently on the starting
5164        square---except in EditGame mode, where the user is playing both
5165        sides; fortunately there the capture race can't happen.  (It can
5166        now happen in IcsExamining mode, but that's just too bad.  The user
5167        will get a somewhat confusing message in that case.)
5168        */
5169
5170     switch (gameMode) {
5171       case PlayFromGameFile:
5172       case AnalyzeFile:
5173       case TwoMachinesPlay:
5174       case EndOfGame:
5175       case IcsObserving:
5176       case IcsIdle:
5177         /* We switched into a game mode where moves are not accepted,
5178            perhaps while the mouse button was down. */
5179         return ImpossibleMove;
5180
5181       case MachinePlaysWhite:
5182         /* User is moving for Black */
5183         if (WhiteOnMove(currentMove)) {
5184             DisplayMoveError(_("It is White's turn"));
5185             return ImpossibleMove;
5186         }
5187         break;
5188
5189       case MachinePlaysBlack:
5190         /* User is moving for White */
5191         if (!WhiteOnMove(currentMove)) {
5192             DisplayMoveError(_("It is Black's turn"));
5193             return ImpossibleMove;
5194         }
5195         break;
5196
5197       case EditGame:
5198       case IcsExamining:
5199       case BeginningOfGame:
5200       case AnalyzeMode:
5201       case Training:
5202         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5203             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5204             /* User is moving for Black */
5205             if (WhiteOnMove(currentMove)) {
5206                 DisplayMoveError(_("It is White's turn"));
5207                 return ImpossibleMove;
5208             }
5209         } else {
5210             /* User is moving for White */
5211             if (!WhiteOnMove(currentMove)) {
5212                 DisplayMoveError(_("It is Black's turn"));
5213                 return ImpossibleMove;
5214             }
5215         }
5216         break;
5217
5218       case IcsPlayingBlack:
5219         /* User is moving for Black */
5220         if (WhiteOnMove(currentMove)) {
5221             if (!appData.premove) {
5222                 DisplayMoveError(_("It is White's turn"));
5223             } else if (toX >= 0 && toY >= 0) {
5224                 premoveToX = toX;
5225                 premoveToY = toY;
5226                 premoveFromX = fromX;
5227                 premoveFromY = fromY;
5228                 premovePromoChar = promoChar;
5229                 gotPremove = 1;
5230                 if (appData.debugMode) 
5231                     fprintf(debugFP, "Got premove: fromX %d,"
5232                             "fromY %d, toX %d, toY %d\n",
5233                             fromX, fromY, toX, toY);
5234             }
5235             return ImpossibleMove;
5236         }
5237         break;
5238
5239       case IcsPlayingWhite:
5240         /* User is moving for White */
5241         if (!WhiteOnMove(currentMove)) {
5242             if (!appData.premove) {
5243                 DisplayMoveError(_("It is Black's turn"));
5244             } else if (toX >= 0 && toY >= 0) {
5245                 premoveToX = toX;
5246                 premoveToY = toY;
5247                 premoveFromX = fromX;
5248                 premoveFromY = fromY;
5249                 premovePromoChar = promoChar;
5250                 gotPremove = 1;
5251                 if (appData.debugMode) 
5252                     fprintf(debugFP, "Got premove: fromX %d,"
5253                             "fromY %d, toX %d, toY %d\n",
5254                             fromX, fromY, toX, toY);
5255             }
5256             return ImpossibleMove;
5257         }
5258         break;
5259
5260       default:
5261         break;
5262
5263       case EditPosition:
5264         /* EditPosition, empty square, or different color piece;
5265            click-click move is possible */
5266         if (toX == -2 || toY == -2) {
5267             boards[0][fromY][fromX] = EmptySquare;
5268             return AmbiguousMove;
5269         } else if (toX >= 0 && toY >= 0) {
5270             boards[0][toY][toX] = boards[0][fromY][fromX];
5271             boards[0][fromY][fromX] = EmptySquare;
5272             return AmbiguousMove;
5273         }
5274         return ImpossibleMove;
5275     }
5276
5277     if(toX < 0 || toY < 0) return ImpossibleMove;
5278     pdown = boards[currentMove][fromY][fromX];
5279     pup = boards[currentMove][toY][toX];
5280
5281     /* [HGM] If move started in holdings, it means a drop */
5282     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5283          if( pup != EmptySquare ) return ImpossibleMove;
5284          if(appData.testLegality) {
5285              /* it would be more logical if LegalityTest() also figured out
5286               * which drops are legal. For now we forbid pawns on back rank.
5287               * Shogi is on its own here...
5288               */
5289              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5290                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5291                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5292          }
5293          return WhiteDrop; /* Not needed to specify white or black yet */
5294     }
5295
5296     userOfferedDraw = FALSE;
5297         
5298     /* [HGM] always test for legality, to get promotion info */
5299     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5300                                          fromY, fromX, toY, toX, promoChar);
5301     /* [HGM] but possibly ignore an IllegalMove result */
5302     if (appData.testLegality) {
5303         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304             DisplayMoveError(_("Illegal move"));
5305             return ImpossibleMove;
5306         }
5307     }
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5309     return moveType;
5310     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311        function is made into one that returns an OK move type if FinishMove
5312        should be called. This to give the calling driver routine the
5313        opportunity to finish the userMove input with a promotion popup,
5314        without bothering the user with this for invalid or illegal moves */
5315
5316 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5317 }
5318
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5320 int
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5322      ChessMove moveType;
5323      int fromX, fromY, toX, toY;
5324      /*char*/int promoChar;
5325 {
5326     char *bookHit = 0;
5327 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5328     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5329         // [HGM] superchess: suppress promotions to non-available piece
5330         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5331         if(WhiteOnMove(currentMove)) {
5332             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5333         } else {
5334             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5335         }
5336     }
5337
5338     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5339        move type in caller when we know the move is a legal promotion */
5340     if(moveType == NormalMove && promoChar)
5341         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5343     /* [HGM] convert drag-and-drop piece drops to standard form */
5344     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5346            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5347                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5348 //         fromX = boards[currentMove][fromY][fromX];
5349            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5350            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5352            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5353          fromY = DROP_RANK;
5354     }
5355
5356     /* [HGM] <popupFix> The following if has been moved here from
5357        UserMoveEvent(). Because it seemed to belon here (why not allow
5358        piece drops in training games?), and because it can only be
5359        performed after it is known to what we promote. */
5360     if (gameMode == Training) {
5361       /* compare the move played on the board to the next move in the
5362        * game. If they match, display the move and the opponent's response. 
5363        * If they don't match, display an error message.
5364        */
5365       int saveAnimate;
5366       Board testBoard;
5367       CopyBoard(testBoard, boards[currentMove]);
5368       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5369
5370       if (CompareBoards(testBoard, boards[currentMove+1])) {
5371         ForwardInner(currentMove+1);
5372
5373         /* Autoplay the opponent's response.
5374          * if appData.animate was TRUE when Training mode was entered,
5375          * the response will be animated.
5376          */
5377         saveAnimate = appData.animate;
5378         appData.animate = animateTraining;
5379         ForwardInner(currentMove+1);
5380         appData.animate = saveAnimate;
5381
5382         /* check for the end of the game */
5383         if (currentMove >= forwardMostMove) {
5384           gameMode = PlayFromGameFile;
5385           ModeHighlight();
5386           SetTrainingModeOff();
5387           DisplayInformation(_("End of game"));
5388         }
5389       } else {
5390         DisplayError(_("Incorrect move"), 0);
5391       }
5392       return 1;
5393     }
5394
5395   /* Ok, now we know that the move is good, so we can kill
5396      the previous line in Analysis Mode */
5397   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5398     forwardMostMove = currentMove;
5399   }
5400
5401   /* If we need the chess program but it's dead, restart it */
5402   ResurrectChessProgram();
5403
5404   /* A user move restarts a paused game*/
5405   if (pausing)
5406     PauseEvent();
5407
5408   thinkOutput[0] = NULLCHAR;
5409
5410   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5411
5412   if (gameMode == BeginningOfGame) {
5413     if (appData.noChessProgram) {
5414       gameMode = EditGame;
5415       SetGameInfo();
5416     } else {
5417       char buf[MSG_SIZ];
5418       gameMode = MachinePlaysBlack;
5419       StartClocks();
5420       SetGameInfo();
5421       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5422       DisplayTitle(buf);
5423       if (first.sendName) {
5424         sprintf(buf, "name %s\n", gameInfo.white);
5425         SendToProgram(buf, &first);
5426       }
5427       StartClocks();
5428     }
5429     ModeHighlight();
5430   }
5431 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5432   /* Relay move to ICS or chess engine */
5433   if (appData.icsActive) {
5434     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5435         gameMode == IcsExamining) {
5436       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5437       ics_user_moved = 1;
5438     }
5439   } else {
5440     if (first.sendTime && (gameMode == BeginningOfGame ||
5441                            gameMode == MachinePlaysWhite ||
5442                            gameMode == MachinePlaysBlack)) {
5443       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5444     }
5445     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5446          // [HGM] book: if program might be playing, let it use book
5447         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5448         first.maybeThinking = TRUE;
5449     } else SendMoveToProgram(forwardMostMove-1, &first);
5450     if (currentMove == cmailOldMove + 1) {
5451       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5452     }
5453   }
5454
5455   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5456
5457   switch (gameMode) {
5458   case EditGame:
5459     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5460     case MT_NONE:
5461     case MT_CHECK:
5462       break;
5463     case MT_CHECKMATE:
5464     case MT_STAINMATE:
5465       if (WhiteOnMove(currentMove)) {
5466         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5467       } else {
5468         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5469       }
5470       break;
5471     case MT_STALEMATE:
5472       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5473       break;
5474     }
5475     break;
5476     
5477   case MachinePlaysBlack:
5478   case MachinePlaysWhite:
5479     /* disable certain menu options while machine is thinking */
5480     SetMachineThinkingEnables();
5481     break;
5482
5483   default:
5484     break;
5485   }
5486
5487   if(bookHit) { // [HGM] book: simulate book reply
5488         static char bookMove[MSG_SIZ]; // a bit generous?
5489
5490         programStats.nodes = programStats.depth = programStats.time = 
5491         programStats.score = programStats.got_only_move = 0;
5492         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5493
5494         strcpy(bookMove, "move ");
5495         strcat(bookMove, bookHit);
5496         HandleMachineMove(bookMove, &first);
5497   }
5498   return 1;
5499 }
5500
5501 void
5502 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5503      int fromX, fromY, toX, toY;
5504      int promoChar;
5505 {
5506     /* [HGM] This routine was added to allow calling of its two logical
5507        parts from other modules in the old way. Before, UserMoveEvent()
5508        automatically called FinishMove() if the move was OK, and returned
5509        otherwise. I separated the two, in order to make it possible to
5510        slip a promotion popup in between. But that it always needs two
5511        calls, to the first part, (now called UserMoveTest() ), and to
5512        FinishMove if the first part succeeded. Calls that do not need
5513        to do anything in between, can call this routine the old way. 
5514     */
5515     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5516 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5517     if(moveType == AmbiguousMove)
5518         DrawPosition(FALSE, boards[currentMove]);
5519     else if(moveType != ImpossibleMove && moveType != Comment)
5520         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5521 }
5522
5523 void LeftClick(ClickType clickType, int xPix, int yPix)
5524 {
5525     int x, y;
5526     Boolean saveAnimate;
5527     static int second = 0, promotionChoice = 0;
5528     char promoChoice = NULLCHAR;
5529
5530     if (clickType == Press) ErrorPopDown();
5531
5532     x = EventToSquare(xPix, BOARD_WIDTH);
5533     y = EventToSquare(yPix, BOARD_HEIGHT);
5534     if (!flipView && y >= 0) {
5535         y = BOARD_HEIGHT - 1 - y;
5536     }
5537     if (flipView && x >= 0) {
5538         x = BOARD_WIDTH - 1 - x;
5539     }
5540
5541     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5542         if(clickType == Release) return; // ignore upclick of click-click destination
5543         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5544         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5545         if(gameInfo.holdingsWidth && 
5546                 (WhiteOnMove(currentMove) 
5547                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5548                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5549             // click in right holdings, for determining promotion piece
5550             ChessSquare p = boards[currentMove][y][x];
5551             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5552             if(p != EmptySquare) {
5553                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5554                 fromX = fromY = -1;
5555                 return;
5556             }
5557         }
5558         DrawPosition(FALSE, boards[currentMove]);
5559         return;
5560     }
5561
5562     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5563     if(clickType == Press
5564             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5565               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5566               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5567         return;
5568
5569     if (fromX == -1) {
5570         if (clickType == Press) {
5571             /* First square */
5572             if (OKToStartUserMove(x, y)) {
5573                 fromX = x;
5574                 fromY = y;
5575                 second = 0;
5576                 DragPieceBegin(xPix, yPix);
5577                 if (appData.highlightDragging) {
5578                     SetHighlights(x, y, -1, -1);
5579                 }
5580             }
5581         }
5582         return;
5583     }
5584
5585     /* fromX != -1 */
5586     if (clickType == Press && gameMode != EditPosition) {
5587         ChessSquare fromP;
5588         ChessSquare toP;
5589         int frc;
5590
5591         // ignore off-board to clicks
5592         if(y < 0 || x < 0) return;
5593
5594         /* Check if clicking again on the same color piece */
5595         fromP = boards[currentMove][fromY][fromX];
5596         toP = boards[currentMove][y][x];
5597         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5598         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5599              WhitePawn <= toP && toP <= WhiteKing &&
5600              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5601              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5602             (BlackPawn <= fromP && fromP <= BlackKing && 
5603              BlackPawn <= toP && toP <= BlackKing &&
5604              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5605              !(fromP == BlackKing && toP == BlackRook && frc))) {
5606             /* Clicked again on same color piece -- changed his mind */
5607             second = (x == fromX && y == fromY);
5608             if (appData.highlightDragging) {
5609                 SetHighlights(x, y, -1, -1);
5610             } else {
5611                 ClearHighlights();
5612             }
5613             if (OKToStartUserMove(x, y)) {
5614                 fromX = x;
5615                 fromY = y;
5616                 DragPieceBegin(xPix, yPix);
5617             }
5618             return;
5619         }
5620         // ignore clicks on holdings
5621         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5622     }
5623
5624     if (clickType == Release && x == fromX && y == fromY) {
5625         DragPieceEnd(xPix, yPix);
5626         if (appData.animateDragging) {
5627             /* Undo animation damage if any */
5628             DrawPosition(FALSE, NULL);
5629         }
5630         if (second) {
5631             /* Second up/down in same square; just abort move */
5632             second = 0;
5633             fromX = fromY = -1;
5634             ClearHighlights();
5635             gotPremove = 0;
5636             ClearPremoveHighlights();
5637         } else {
5638             /* First upclick in same square; start click-click mode */
5639             SetHighlights(x, y, -1, -1);
5640         }
5641         return;
5642     }
5643
5644     /* we now have a different from- and (possibly off-board) to-square */
5645     /* Completed move */
5646     toX = x;
5647     toY = y;
5648     saveAnimate = appData.animate;
5649     if (clickType == Press) {
5650         /* Finish clickclick move */
5651         if (appData.animate || appData.highlightLastMove) {
5652             SetHighlights(fromX, fromY, toX, toY);
5653         } else {
5654             ClearHighlights();
5655         }
5656     } else {
5657         /* Finish drag move */
5658         if (appData.highlightLastMove) {
5659             SetHighlights(fromX, fromY, toX, toY);
5660         } else {
5661             ClearHighlights();
5662         }
5663         DragPieceEnd(xPix, yPix);
5664         /* Don't animate move and drag both */
5665         appData.animate = FALSE;
5666     }
5667
5668     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5669     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5670         ClearHighlights();
5671         fromX = fromY = -1;
5672         DrawPosition(TRUE, NULL);
5673         return;
5674     }
5675
5676     // off-board moves should not be highlighted
5677     if(x < 0 || x < 0) ClearHighlights();
5678
5679     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5680         SetHighlights(fromX, fromY, toX, toY);
5681         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5682             // [HGM] super: promotion to captured piece selected from holdings
5683             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5684             promotionChoice = TRUE;
5685             // kludge follows to temporarily execute move on display, without promoting yet
5686             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5687             boards[currentMove][toY][toX] = p;
5688             DrawPosition(FALSE, boards[currentMove]);
5689             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5690             boards[currentMove][toY][toX] = q;
5691             DisplayMessage("Click in holdings to choose piece", "");
5692             return;
5693         }
5694         PromotionPopUp();
5695     } else {
5696         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5697         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5698         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5699         fromX = fromY = -1;
5700     }
5701     appData.animate = saveAnimate;
5702     if (appData.animate || appData.animateDragging) {
5703         /* Undo animation damage if needed */
5704         DrawPosition(FALSE, NULL);
5705     }
5706 }
5707
5708 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5709 {
5710 //    char * hint = lastHint;
5711     FrontEndProgramStats stats;
5712
5713     stats.which = cps == &first ? 0 : 1;
5714     stats.depth = cpstats->depth;
5715     stats.nodes = cpstats->nodes;
5716     stats.score = cpstats->score;
5717     stats.time = cpstats->time;
5718     stats.pv = cpstats->movelist;
5719     stats.hint = lastHint;
5720     stats.an_move_index = 0;
5721     stats.an_move_count = 0;
5722
5723     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5724         stats.hint = cpstats->move_name;
5725         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5726         stats.an_move_count = cpstats->nr_moves;
5727     }
5728
5729     SetProgramStats( &stats );
5730 }
5731
5732 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5733 {   // [HGM] book: this routine intercepts moves to simulate book replies
5734     char *bookHit = NULL;
5735
5736     //first determine if the incoming move brings opponent into his book
5737     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5738         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5739     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5740     if(bookHit != NULL && !cps->bookSuspend) {
5741         // make sure opponent is not going to reply after receiving move to book position
5742         SendToProgram("force\n", cps);
5743         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5744     }
5745     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5746     // now arrange restart after book miss
5747     if(bookHit) {
5748         // after a book hit we never send 'go', and the code after the call to this routine
5749         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5750         char buf[MSG_SIZ];
5751         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5752         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5753         SendToProgram(buf, cps);
5754         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5755     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5756         SendToProgram("go\n", cps);
5757         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5758     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5759         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5760             SendToProgram("go\n", cps); 
5761         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5762     }
5763     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5764 }
5765
5766 char *savedMessage;
5767 ChessProgramState *savedState;
5768 void DeferredBookMove(void)
5769 {
5770         if(savedState->lastPing != savedState->lastPong)
5771                     ScheduleDelayedEvent(DeferredBookMove, 10);
5772         else
5773         HandleMachineMove(savedMessage, savedState);
5774 }
5775
5776 void
5777 HandleMachineMove(message, cps)
5778      char *message;
5779      ChessProgramState *cps;
5780 {
5781     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5782     char realname[MSG_SIZ];
5783     int fromX, fromY, toX, toY;
5784     ChessMove moveType;
5785     char promoChar;
5786     char *p;
5787     int machineWhite;
5788     char *bookHit;
5789
5790 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5791     /*
5792      * Kludge to ignore BEL characters
5793      */
5794     while (*message == '\007') message++;
5795
5796     /*
5797      * [HGM] engine debug message: ignore lines starting with '#' character
5798      */
5799     if(cps->debug && *message == '#') return;
5800
5801     /*
5802      * Look for book output
5803      */
5804     if (cps == &first && bookRequested) {
5805         if (message[0] == '\t' || message[0] == ' ') {
5806             /* Part of the book output is here; append it */
5807             strcat(bookOutput, message);
5808             strcat(bookOutput, "  \n");
5809             return;
5810         } else if (bookOutput[0] != NULLCHAR) {
5811             /* All of book output has arrived; display it */
5812             char *p = bookOutput;
5813             while (*p != NULLCHAR) {
5814                 if (*p == '\t') *p = ' ';
5815                 p++;
5816             }
5817             DisplayInformation(bookOutput);
5818             bookRequested = FALSE;
5819             /* Fall through to parse the current output */
5820         }
5821     }
5822
5823     /*
5824      * Look for machine move.
5825      */
5826     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5827         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5828     {
5829         /* This method is only useful on engines that support ping */
5830         if (cps->lastPing != cps->lastPong) {
5831           if (gameMode == BeginningOfGame) {
5832             /* Extra move from before last new; ignore */
5833             if (appData.debugMode) {
5834                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5835             }
5836           } else {
5837             if (appData.debugMode) {
5838                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5839                         cps->which, gameMode);
5840             }
5841
5842             SendToProgram("undo\n", cps);
5843           }
5844           return;
5845         }
5846
5847         switch (gameMode) {
5848           case BeginningOfGame:
5849             /* Extra move from before last reset; ignore */
5850             if (appData.debugMode) {
5851                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5852             }
5853             return;
5854
5855           case EndOfGame:
5856           case IcsIdle:
5857           default:
5858             /* Extra move after we tried to stop.  The mode test is
5859                not a reliable way of detecting this problem, but it's
5860                the best we can do on engines that don't support ping.
5861             */
5862             if (appData.debugMode) {
5863                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5864                         cps->which, gameMode);
5865             }
5866             SendToProgram("undo\n", cps);
5867             return;
5868
5869           case MachinePlaysWhite:
5870           case IcsPlayingWhite:
5871             machineWhite = TRUE;
5872             break;
5873
5874           case MachinePlaysBlack:
5875           case IcsPlayingBlack:
5876             machineWhite = FALSE;
5877             break;
5878
5879           case TwoMachinesPlay:
5880             machineWhite = (cps->twoMachinesColor[0] == 'w');
5881             break;
5882         }
5883         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5884             if (appData.debugMode) {
5885                 fprintf(debugFP,
5886                         "Ignoring move out of turn by %s, gameMode %d"
5887                         ", forwardMost %d\n",
5888                         cps->which, gameMode, forwardMostMove);
5889             }
5890             return;
5891         }
5892
5893     if (appData.debugMode) { int f = forwardMostMove;
5894         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5895                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5896                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5897     }
5898         if(cps->alphaRank) AlphaRank(machineMove, 4);
5899         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5900                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5901             /* Machine move could not be parsed; ignore it. */
5902             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5903                     machineMove, cps->which);
5904             DisplayError(buf1, 0);
5905             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5906                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5907             if (gameMode == TwoMachinesPlay) {
5908               GameEnds(machineWhite ? BlackWins : WhiteWins,
5909                        buf1, GE_XBOARD);
5910             }
5911             return;
5912         }
5913
5914         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5915         /* So we have to redo legality test with true e.p. status here,  */
5916         /* to make sure an illegal e.p. capture does not slip through,   */
5917         /* to cause a forfeit on a justified illegal-move complaint      */
5918         /* of the opponent.                                              */
5919         if( gameMode==TwoMachinesPlay && appData.testLegality
5920             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5921                                                               ) {
5922            ChessMove moveType;
5923            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5924                              fromY, fromX, toY, toX, promoChar);
5925             if (appData.debugMode) {
5926                 int i;
5927                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5928                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5929                 fprintf(debugFP, "castling rights\n");
5930             }
5931             if(moveType == IllegalMove) {
5932                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5933                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5934                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5935                            buf1, GE_XBOARD);
5936                 return;
5937            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5938            /* [HGM] Kludge to handle engines that send FRC-style castling
5939               when they shouldn't (like TSCP-Gothic) */
5940            switch(moveType) {
5941              case WhiteASideCastleFR:
5942              case BlackASideCastleFR:
5943                toX+=2;
5944                currentMoveString[2]++;
5945                break;
5946              case WhiteHSideCastleFR:
5947              case BlackHSideCastleFR:
5948                toX--;
5949                currentMoveString[2]--;
5950                break;
5951              default: ; // nothing to do, but suppresses warning of pedantic compilers
5952            }
5953         }
5954         hintRequested = FALSE;
5955         lastHint[0] = NULLCHAR;
5956         bookRequested = FALSE;
5957         /* Program may be pondering now */
5958         cps->maybeThinking = TRUE;
5959         if (cps->sendTime == 2) cps->sendTime = 1;
5960         if (cps->offeredDraw) cps->offeredDraw--;
5961
5962 #if ZIPPY
5963         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5964             first.initDone) {
5965           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5966           ics_user_moved = 1;
5967           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5968                 char buf[3*MSG_SIZ];
5969
5970                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5971                         programStats.score / 100.,
5972                         programStats.depth,
5973                         programStats.time / 100.,
5974                         (unsigned int)programStats.nodes,
5975                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5976                         programStats.movelist);
5977                 SendToICS(buf);
5978 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5979           }
5980         }
5981 #endif
5982         /* currentMoveString is set as a side-effect of ParseOneMove */
5983         strcpy(machineMove, currentMoveString);
5984         strcat(machineMove, "\n");
5985         strcpy(moveList[forwardMostMove], machineMove);
5986
5987         /* [AS] Save move info and clear stats for next move */
5988         pvInfoList[ forwardMostMove ].score = programStats.score;
5989         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5990         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5991         ClearProgramStats();
5992         thinkOutput[0] = NULLCHAR;
5993         hiddenThinkOutputState = 0;
5994
5995         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5996
5997         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5998         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5999             int count = 0;
6000
6001             while( count < adjudicateLossPlies ) {
6002                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6003
6004                 if( count & 1 ) {
6005                     score = -score; /* Flip score for winning side */
6006                 }
6007
6008                 if( score > adjudicateLossThreshold ) {
6009                     break;
6010                 }
6011
6012                 count++;
6013             }
6014
6015             if( count >= adjudicateLossPlies ) {
6016                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6017
6018                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6019                     "Xboard adjudication", 
6020                     GE_XBOARD );
6021
6022                 return;
6023             }
6024         }
6025
6026         if( gameMode == TwoMachinesPlay ) {
6027           // [HGM] some adjudications useful with buggy engines
6028             int k, count = 0; static int bare = 1;
6029           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6030
6031
6032             if( appData.testLegality )
6033             {   /* [HGM] Some more adjudications for obstinate engines */
6034                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6035                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6036                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6037                 static int moveCount = 6;
6038                 ChessMove result;
6039                 char *reason = NULL;
6040
6041                 /* Count what is on board. */
6042                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6043                 {   ChessSquare p = boards[forwardMostMove][i][j];
6044                     int m=i;
6045
6046                     switch((int) p)
6047                     {   /* count B,N,R and other of each side */
6048                         case WhiteKing:
6049                         case BlackKing:
6050                              NrK++; break; // [HGM] atomic: count Kings
6051                         case WhiteKnight:
6052                              NrWN++; break;
6053                         case WhiteBishop:
6054                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6055                              bishopsColor |= 1 << ((i^j)&1);
6056                              NrWB++; break;
6057                         case BlackKnight:
6058                              NrBN++; break;
6059                         case BlackBishop:
6060                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6061                              bishopsColor |= 1 << ((i^j)&1);
6062                              NrBB++; break;
6063                         case WhiteRook:
6064                              NrWR++; break;
6065                         case BlackRook:
6066                              NrBR++; break;
6067                         case WhiteQueen:
6068                              NrWQ++; break;
6069                         case BlackQueen:
6070                              NrBQ++; break;
6071                         case EmptySquare: 
6072                              break;
6073                         case BlackPawn:
6074                              m = 7-i;
6075                         case WhitePawn:
6076                              PawnAdvance += m; NrPawns++;
6077                     }
6078                     NrPieces += (p != EmptySquare);
6079                     NrW += ((int)p < (int)BlackPawn);
6080                     if(gameInfo.variant == VariantXiangqi && 
6081                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6082                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6083                         NrW -= ((int)p < (int)BlackPawn);
6084                     }
6085                 }
6086
6087                 /* Some material-based adjudications that have to be made before stalemate test */
6088                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6089                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6090                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6091                      if(appData.checkMates) {
6092                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6093                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6095                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6096                          return;
6097                      }
6098                 }
6099
6100                 /* Bare King in Shatranj (loses) or Losers (wins) */
6101                 if( NrW == 1 || NrPieces - NrW == 1) {
6102                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6103                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6104                      if(appData.checkMates) {
6105                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6106                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6108                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6109                          return;
6110                      }
6111                   } else
6112                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6113                   {    /* bare King */
6114                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6115                         if(appData.checkMates) {
6116                             /* but only adjudicate if adjudication enabled */
6117                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6118                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6120                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6121                             return;
6122                         }
6123                   }
6124                 } else bare = 1;
6125
6126
6127             // don't wait for engine to announce game end if we can judge ourselves
6128             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6129               case MT_CHECK:
6130                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6131                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6132                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6133                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6134                             checkCnt++;
6135                         if(checkCnt >= 2) {
6136                             reason = "Xboard adjudication: 3rd check";
6137                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6138                             break;
6139                         }
6140                     }
6141                 }
6142               case MT_NONE:
6143               default:
6144                 break;
6145               case MT_STALEMATE:
6146               case MT_STAINMATE:
6147                 reason = "Xboard adjudication: Stalemate";
6148                 if((int)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6149                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6150                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6151                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6152                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6153                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6154                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6155                                                                         EP_CHECKMATE : EP_WINS);
6156                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6157                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6158                 }
6159                 break;
6160               case MT_CHECKMATE:
6161                 reason = "Xboard adjudication: Checkmate";
6162                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6163                 break;
6164             }
6165
6166                 switch(i = (int)boards[forwardMostMove][EP_STATUS]) {
6167                     case EP_STALEMATE:
6168                         result = GameIsDrawn; break;
6169                     case EP_CHECKMATE:
6170                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6171                     case EP_WINS:
6172                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6173                     default:
6174                         result = (ChessMove) 0;
6175                 }
6176                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6177                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6178                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6179                     GameEnds( result, reason, GE_XBOARD );
6180                     return;
6181                 }
6182
6183                 /* Next absolutely insufficient mating material. */
6184                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6185                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6186                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6187                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6188                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6189
6190                      /* always flag draws, for judging claims */
6191                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6192
6193                      if(appData.materialDraws) {
6194                          /* but only adjudicate them if adjudication enabled */
6195                          SendToProgram("force\n", cps->other); // suppress reply
6196                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6197                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6198                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6199                          return;
6200                      }
6201                 }
6202
6203                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6204                 if(NrPieces == 4 && 
6205                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6206                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6207                    || NrWN==2 || NrBN==2     /* KNNK */
6208                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6209                   ) ) {
6210                      if(--moveCount < 0 && appData.trivialDraws)
6211                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6212                           SendToProgram("force\n", cps->other); // suppress reply
6213                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6214                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6215                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6216                           return;
6217                      }
6218                 } else moveCount = 6;
6219             }
6220           }
6221           
6222           if (appData.debugMode) { int i;
6223             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6224                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6225                     appData.drawRepeats);
6226             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6227               fprintf(debugFP, "%d ep=%d\n", i, (int)boards[i][EP_STATUS]);
6228             
6229           }
6230
6231                 /* Check for rep-draws */
6232                 count = 0;
6233                 for(k = forwardMostMove-2;
6234                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6235                         (int)boards[k][EP_STATUS] < EP_UNKNOWN &&
6236                         (int)boards[k+2][EP_STATUS] <= EP_NONE && (int)boards[k+1][EP_STATUS] <= EP_NONE;
6237                     k-=2)
6238                 {   int rights=0;
6239                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6240                         /* compare castling rights */
6241                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6242                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6243                                 rights++; /* King lost rights, while rook still had them */
6244                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6245                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6246                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6247                                    rights++; /* but at least one rook lost them */
6248                         }
6249                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6250                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6251                                 rights++; 
6252                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6253                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6254                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6255                                    rights++;
6256                         }
6257                         if( rights == 0 && ++count > appData.drawRepeats-2
6258                             && appData.drawRepeats > 1) {
6259                              /* adjudicate after user-specified nr of repeats */
6260                              SendToProgram("force\n", cps->other); // suppress reply
6261                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6262                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6263                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6264                                 // [HGM] xiangqi: check for forbidden perpetuals
6265                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6266                                 for(m=forwardMostMove; m>k; m-=2) {
6267                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6268                                         ourPerpetual = 0; // the current mover did not always check
6269                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6270                                         hisPerpetual = 0; // the opponent did not always check
6271                                 }
6272                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6273                                                                         ourPerpetual, hisPerpetual);
6274                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6275                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6276                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6277                                     return;
6278                                 }
6279                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6280                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6281                                 // Now check for perpetual chases
6282                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6283                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6284                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6285                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6286                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6287                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6288                                         return;
6289                                     }
6290                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6291                                         break; // Abort repetition-checking loop.
6292                                 }
6293                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6294                              }
6295                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6296                              return;
6297                         }
6298                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6299                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6300                     }
6301                 }
6302
6303                 /* Now we test for 50-move draws. Determine ply count */
6304                 count = forwardMostMove;
6305                 /* look for last irreversble move */
6306                 while( (int)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6307                     count--;
6308                 /* if we hit starting position, add initial plies */
6309                 if( count == backwardMostMove )
6310                     count -= initialRulePlies;
6311                 count = forwardMostMove - count; 
6312                 if( count >= 100)
6313                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6314                          /* this is used to judge if draw claims are legal */
6315                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6316                          SendToProgram("force\n", cps->other); // suppress reply
6317                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6318                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6319                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6320                          return;
6321                 }
6322
6323                 /* if draw offer is pending, treat it as a draw claim
6324                  * when draw condition present, to allow engines a way to
6325                  * claim draws before making their move to avoid a race
6326                  * condition occurring after their move
6327                  */
6328                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6329                          char *p = NULL;
6330                          if((int)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6331                              p = "Draw claim: 50-move rule";
6332                          if((int)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6333                              p = "Draw claim: 3-fold repetition";
6334                          if((int)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6335                              p = "Draw claim: insufficient mating material";
6336                          if( p != NULL ) {
6337                              SendToProgram("force\n", cps->other); // suppress reply
6338                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6339                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6340                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341                              return;
6342                          }
6343                 }
6344
6345
6346                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6347                     SendToProgram("force\n", cps->other); // suppress reply
6348                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350
6351                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6352
6353                     return;
6354                 }
6355         }
6356
6357         bookHit = NULL;
6358         if (gameMode == TwoMachinesPlay) {
6359             /* [HGM] relaying draw offers moved to after reception of move */
6360             /* and interpreting offer as claim if it brings draw condition */
6361             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6362                 SendToProgram("draw\n", cps->other);
6363             }
6364             if (cps->other->sendTime) {
6365                 SendTimeRemaining(cps->other,
6366                                   cps->other->twoMachinesColor[0] == 'w');
6367             }
6368             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6369             if (firstMove && !bookHit) {
6370                 firstMove = FALSE;
6371                 if (cps->other->useColors) {
6372                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6373                 }
6374                 SendToProgram("go\n", cps->other);
6375             }
6376             cps->other->maybeThinking = TRUE;
6377         }
6378
6379         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6380         
6381         if (!pausing && appData.ringBellAfterMoves) {
6382             RingBell();
6383         }
6384
6385         /* 
6386          * Reenable menu items that were disabled while
6387          * machine was thinking
6388          */
6389         if (gameMode != TwoMachinesPlay)
6390             SetUserThinkingEnables();
6391
6392         // [HGM] book: after book hit opponent has received move and is now in force mode
6393         // force the book reply into it, and then fake that it outputted this move by jumping
6394         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6395         if(bookHit) {
6396                 static char bookMove[MSG_SIZ]; // a bit generous?
6397
6398                 strcpy(bookMove, "move ");
6399                 strcat(bookMove, bookHit);
6400                 message = bookMove;
6401                 cps = cps->other;
6402                 programStats.nodes = programStats.depth = programStats.time = 
6403                 programStats.score = programStats.got_only_move = 0;
6404                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6405
6406                 if(cps->lastPing != cps->lastPong) {
6407                     savedMessage = message; // args for deferred call
6408                     savedState = cps;
6409                     ScheduleDelayedEvent(DeferredBookMove, 10);
6410                     return;
6411                 }
6412                 goto FakeBookMove;
6413         }
6414
6415         return;
6416     }
6417
6418     /* Set special modes for chess engines.  Later something general
6419      *  could be added here; for now there is just one kludge feature,
6420      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6421      *  when "xboard" is given as an interactive command.
6422      */
6423     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6424         cps->useSigint = FALSE;
6425         cps->useSigterm = FALSE;
6426     }
6427     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6428       ParseFeatures(message+8, cps);
6429       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6430     }
6431
6432     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6433      * want this, I was asked to put it in, and obliged.
6434      */
6435     if (!strncmp(message, "setboard ", 9)) {
6436         Board initial_position;
6437
6438         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6439
6440         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6441             DisplayError(_("Bad FEN received from engine"), 0);
6442             return ;
6443         } else {
6444            Reset(TRUE, FALSE);
6445            CopyBoard(boards[0], initial_position);
6446            initialRulePlies = FENrulePlies;
6447            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6448            else gameMode = MachinePlaysBlack;                 
6449            DrawPosition(FALSE, boards[currentMove]);
6450         }
6451         return;
6452     }
6453
6454     /*
6455      * Look for communication commands
6456      */
6457     if (!strncmp(message, "telluser ", 9)) {
6458         DisplayNote(message + 9);
6459         return;
6460     }
6461     if (!strncmp(message, "tellusererror ", 14)) {
6462         DisplayError(message + 14, 0);
6463         return;
6464     }
6465     if (!strncmp(message, "tellopponent ", 13)) {
6466       if (appData.icsActive) {
6467         if (loggedOn) {
6468           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6469           SendToICS(buf1);
6470         }
6471       } else {
6472         DisplayNote(message + 13);
6473       }
6474       return;
6475     }
6476     if (!strncmp(message, "tellothers ", 11)) {
6477       if (appData.icsActive) {
6478         if (loggedOn) {
6479           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6480           SendToICS(buf1);
6481         }
6482       }
6483       return;
6484     }
6485     if (!strncmp(message, "tellall ", 8)) {
6486       if (appData.icsActive) {
6487         if (loggedOn) {
6488           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6489           SendToICS(buf1);
6490         }
6491       } else {
6492         DisplayNote(message + 8);
6493       }
6494       return;
6495     }
6496     if (strncmp(message, "warning", 7) == 0) {
6497         /* Undocumented feature, use tellusererror in new code */
6498         DisplayError(message, 0);
6499         return;
6500     }
6501     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6502         strcpy(realname, cps->tidy);
6503         strcat(realname, " query");
6504         AskQuestion(realname, buf2, buf1, cps->pr);
6505         return;
6506     }
6507     /* Commands from the engine directly to ICS.  We don't allow these to be 
6508      *  sent until we are logged on. Crafty kibitzes have been known to 
6509      *  interfere with the login process.
6510      */
6511     if (loggedOn) {
6512         if (!strncmp(message, "tellics ", 8)) {
6513             SendToICS(message + 8);
6514             SendToICS("\n");
6515             return;
6516         }
6517         if (!strncmp(message, "tellicsnoalias ", 15)) {
6518             SendToICS(ics_prefix);
6519             SendToICS(message + 15);
6520             SendToICS("\n");
6521             return;
6522         }
6523         /* The following are for backward compatibility only */
6524         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6525             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6526             SendToICS(ics_prefix);
6527             SendToICS(message);
6528             SendToICS("\n");
6529             return;
6530         }
6531     }
6532     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6533         return;
6534     }
6535     /*
6536      * If the move is illegal, cancel it and redraw the board.
6537      * Also deal with other error cases.  Matching is rather loose
6538      * here to accommodate engines written before the spec.
6539      */
6540     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6541         strncmp(message, "Error", 5) == 0) {
6542         if (StrStr(message, "name") || 
6543             StrStr(message, "rating") || StrStr(message, "?") ||
6544             StrStr(message, "result") || StrStr(message, "board") ||
6545             StrStr(message, "bk") || StrStr(message, "computer") ||
6546             StrStr(message, "variant") || StrStr(message, "hint") ||
6547             StrStr(message, "random") || StrStr(message, "depth") ||
6548             StrStr(message, "accepted")) {
6549             return;
6550         }
6551         if (StrStr(message, "protover")) {
6552           /* Program is responding to input, so it's apparently done
6553              initializing, and this error message indicates it is
6554              protocol version 1.  So we don't need to wait any longer
6555              for it to initialize and send feature commands. */
6556           FeatureDone(cps, 1);
6557           cps->protocolVersion = 1;
6558           return;
6559         }
6560         cps->maybeThinking = FALSE;
6561
6562         if (StrStr(message, "draw")) {
6563             /* Program doesn't have "draw" command */
6564             cps->sendDrawOffers = 0;
6565             return;
6566         }
6567         if (cps->sendTime != 1 &&
6568             (StrStr(message, "time") || StrStr(message, "otim"))) {
6569           /* Program apparently doesn't have "time" or "otim" command */
6570           cps->sendTime = 0;
6571           return;
6572         }
6573         if (StrStr(message, "analyze")) {
6574             cps->analysisSupport = FALSE;
6575             cps->analyzing = FALSE;
6576             Reset(FALSE, TRUE);
6577             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6578             DisplayError(buf2, 0);
6579             return;
6580         }
6581         if (StrStr(message, "(no matching move)st")) {
6582           /* Special kludge for GNU Chess 4 only */
6583           cps->stKludge = TRUE;
6584           SendTimeControl(cps, movesPerSession, timeControl,
6585                           timeIncrement, appData.searchDepth,
6586                           searchTime);
6587           return;
6588         }
6589         if (StrStr(message, "(no matching move)sd")) {
6590           /* Special kludge for GNU Chess 4 only */
6591           cps->sdKludge = TRUE;
6592           SendTimeControl(cps, movesPerSession, timeControl,
6593                           timeIncrement, appData.searchDepth,
6594                           searchTime);
6595           return;
6596         }
6597         if (!StrStr(message, "llegal")) {
6598             return;
6599         }
6600         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6601             gameMode == IcsIdle) return;
6602         if (forwardMostMove <= backwardMostMove) return;
6603         if (pausing) PauseEvent();
6604       if(appData.forceIllegal) {
6605             // [HGM] illegal: machine refused move; force position after move into it
6606           SendToProgram("force\n", cps);
6607           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6608                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6609                 // when black is to move, while there might be nothing on a2 or black
6610                 // might already have the move. So send the board as if white has the move.
6611                 // But first we must change the stm of the engine, as it refused the last move
6612                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6613                 if(WhiteOnMove(forwardMostMove)) {
6614                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6615                     SendBoard(cps, forwardMostMove); // kludgeless board
6616                 } else {
6617                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6618                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6619                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6620                 }
6621           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6622             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6623                  gameMode == TwoMachinesPlay)
6624               SendToProgram("go\n", cps);
6625             return;
6626       } else
6627         if (gameMode == PlayFromGameFile) {
6628             /* Stop reading this game file */
6629             gameMode = EditGame;
6630             ModeHighlight();
6631         }
6632         currentMove = --forwardMostMove;
6633         DisplayMove(currentMove-1); /* before DisplayMoveError */
6634         SwitchClocks();
6635         DisplayBothClocks();
6636         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6637                 parseList[currentMove], cps->which);
6638         DisplayMoveError(buf1);
6639         DrawPosition(FALSE, boards[currentMove]);
6640
6641         /* [HGM] illegal-move claim should forfeit game when Xboard */
6642         /* only passes fully legal moves                            */
6643         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6644             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6645                                 "False illegal-move claim", GE_XBOARD );
6646         }
6647         return;
6648     }
6649     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6650         /* Program has a broken "time" command that
6651            outputs a string not ending in newline.
6652            Don't use it. */
6653         cps->sendTime = 0;
6654     }
6655     
6656     /*
6657      * If chess program startup fails, exit with an error message.
6658      * Attempts to recover here are futile.
6659      */
6660     if ((StrStr(message, "unknown host") != NULL)
6661         || (StrStr(message, "No remote directory") != NULL)
6662         || (StrStr(message, "not found") != NULL)
6663         || (StrStr(message, "No such file") != NULL)
6664         || (StrStr(message, "can't alloc") != NULL)
6665         || (StrStr(message, "Permission denied") != NULL)) {
6666
6667         cps->maybeThinking = FALSE;
6668         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6669                 cps->which, cps->program, cps->host, message);
6670         RemoveInputSource(cps->isr);
6671         DisplayFatalError(buf1, 0, 1);
6672         return;
6673     }
6674     
6675     /* 
6676      * Look for hint output
6677      */
6678     if (sscanf(message, "Hint: %s", buf1) == 1) {
6679         if (cps == &first && hintRequested) {
6680             hintRequested = FALSE;
6681             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6682                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6683                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6684                                     PosFlags(forwardMostMove),
6685                                     fromY, fromX, toY, toX, promoChar, buf1);
6686                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6687                 DisplayInformation(buf2);
6688             } else {
6689                 /* Hint move could not be parsed!? */
6690               snprintf(buf2, sizeof(buf2),
6691                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6692                         buf1, cps->which);
6693                 DisplayError(buf2, 0);
6694             }
6695         } else {
6696             strcpy(lastHint, buf1);
6697         }
6698         return;
6699     }
6700
6701     /*
6702      * Ignore other messages if game is not in progress
6703      */
6704     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6705         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6706
6707     /*
6708      * look for win, lose, draw, or draw offer
6709      */
6710     if (strncmp(message, "1-0", 3) == 0) {
6711         char *p, *q, *r = "";
6712         p = strchr(message, '{');
6713         if (p) {
6714             q = strchr(p, '}');
6715             if (q) {
6716                 *q = NULLCHAR;
6717                 r = p + 1;
6718             }
6719         }
6720         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6721         return;
6722     } else if (strncmp(message, "0-1", 3) == 0) {
6723         char *p, *q, *r = "";
6724         p = strchr(message, '{');
6725         if (p) {
6726             q = strchr(p, '}');
6727             if (q) {
6728                 *q = NULLCHAR;
6729                 r = p + 1;
6730             }
6731         }
6732         /* Kludge for Arasan 4.1 bug */
6733         if (strcmp(r, "Black resigns") == 0) {
6734             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6735             return;
6736         }
6737         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6738         return;
6739     } else if (strncmp(message, "1/2", 3) == 0) {
6740         char *p, *q, *r = "";
6741         p = strchr(message, '{');
6742         if (p) {
6743             q = strchr(p, '}');
6744             if (q) {
6745                 *q = NULLCHAR;
6746                 r = p + 1;
6747             }
6748         }
6749             
6750         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6751         return;
6752
6753     } else if (strncmp(message, "White resign", 12) == 0) {
6754         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6755         return;
6756     } else if (strncmp(message, "Black resign", 12) == 0) {
6757         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6758         return;
6759     } else if (strncmp(message, "White matches", 13) == 0 ||
6760                strncmp(message, "Black matches", 13) == 0   ) {
6761         /* [HGM] ignore GNUShogi noises */
6762         return;
6763     } else if (strncmp(message, "White", 5) == 0 &&
6764                message[5] != '(' &&
6765                StrStr(message, "Black") == NULL) {
6766         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "Black", 5) == 0 &&
6769                message[5] != '(') {
6770         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6771         return;
6772     } else if (strcmp(message, "resign") == 0 ||
6773                strcmp(message, "computer resigns") == 0) {
6774         switch (gameMode) {
6775           case MachinePlaysBlack:
6776           case IcsPlayingBlack:
6777             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6778             break;
6779           case MachinePlaysWhite:
6780           case IcsPlayingWhite:
6781             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6782             break;
6783           case TwoMachinesPlay:
6784             if (cps->twoMachinesColor[0] == 'w')
6785               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6786             else
6787               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6788             break;
6789           default:
6790             /* can't happen */
6791             break;
6792         }
6793         return;
6794     } else if (strncmp(message, "opponent mates", 14) == 0) {
6795         switch (gameMode) {
6796           case MachinePlaysBlack:
6797           case IcsPlayingBlack:
6798             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6799             break;
6800           case MachinePlaysWhite:
6801           case IcsPlayingWhite:
6802             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6803             break;
6804           case TwoMachinesPlay:
6805             if (cps->twoMachinesColor[0] == 'w')
6806               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6807             else
6808               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6809             break;
6810           default:
6811             /* can't happen */
6812             break;
6813         }
6814         return;
6815     } else if (strncmp(message, "computer mates", 14) == 0) {
6816         switch (gameMode) {
6817           case MachinePlaysBlack:
6818           case IcsPlayingBlack:
6819             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6820             break;
6821           case MachinePlaysWhite:
6822           case IcsPlayingWhite:
6823             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6824             break;
6825           case TwoMachinesPlay:
6826             if (cps->twoMachinesColor[0] == 'w')
6827               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6828             else
6829               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6830             break;
6831           default:
6832             /* can't happen */
6833             break;
6834         }
6835         return;
6836     } else if (strncmp(message, "checkmate", 9) == 0) {
6837         if (WhiteOnMove(forwardMostMove)) {
6838             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839         } else {
6840             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6841         }
6842         return;
6843     } else if (strstr(message, "Draw") != NULL ||
6844                strstr(message, "game is a draw") != NULL) {
6845         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6846         return;
6847     } else if (strstr(message, "offer") != NULL &&
6848                strstr(message, "draw") != NULL) {
6849 #if ZIPPY
6850         if (appData.zippyPlay && first.initDone) {
6851             /* Relay offer to ICS */
6852             SendToICS(ics_prefix);
6853             SendToICS("draw\n");
6854         }
6855 #endif
6856         cps->offeredDraw = 2; /* valid until this engine moves twice */
6857         if (gameMode == TwoMachinesPlay) {
6858             if (cps->other->offeredDraw) {
6859                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6860             /* [HGM] in two-machine mode we delay relaying draw offer      */
6861             /* until after we also have move, to see if it is really claim */
6862             }
6863         } else if (gameMode == MachinePlaysWhite ||
6864                    gameMode == MachinePlaysBlack) {
6865           if (userOfferedDraw) {
6866             DisplayInformation(_("Machine accepts your draw offer"));
6867             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6868           } else {
6869             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6870           }
6871         }
6872     }
6873
6874     
6875     /*
6876      * Look for thinking output
6877      */
6878     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6879           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6880                                 ) {
6881         int plylev, mvleft, mvtot, curscore, time;
6882         char mvname[MOVE_LEN];
6883         u64 nodes; // [DM]
6884         char plyext;
6885         int ignore = FALSE;
6886         int prefixHint = FALSE;
6887         mvname[0] = NULLCHAR;
6888
6889         switch (gameMode) {
6890           case MachinePlaysBlack:
6891           case IcsPlayingBlack:
6892             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6893             break;
6894           case MachinePlaysWhite:
6895           case IcsPlayingWhite:
6896             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6897             break;
6898           case AnalyzeMode:
6899           case AnalyzeFile:
6900             break;
6901           case IcsObserving: /* [DM] icsEngineAnalyze */
6902             if (!appData.icsEngineAnalyze) ignore = TRUE;
6903             break;
6904           case TwoMachinesPlay:
6905             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6906                 ignore = TRUE;
6907             }
6908             break;
6909           default:
6910             ignore = TRUE;
6911             break;
6912         }
6913
6914         if (!ignore) {
6915             buf1[0] = NULLCHAR;
6916             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6917                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6918
6919                 if (plyext != ' ' && plyext != '\t') {
6920                     time *= 100;
6921                 }
6922
6923                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6924                 if( cps->scoreIsAbsolute && 
6925                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6926                 {
6927                     curscore = -curscore;
6928                 }
6929
6930
6931                 programStats.depth = plylev;
6932                 programStats.nodes = nodes;
6933                 programStats.time = time;
6934                 programStats.score = curscore;
6935                 programStats.got_only_move = 0;
6936
6937                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6938                         int ticklen;
6939
6940                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6941                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6942                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6943                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6944                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6945                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6946                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6947                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6948                 }
6949
6950                 /* Buffer overflow protection */
6951                 if (buf1[0] != NULLCHAR) {
6952                     if (strlen(buf1) >= sizeof(programStats.movelist)
6953                         && appData.debugMode) {
6954                         fprintf(debugFP,
6955                                 "PV is too long; using the first %u bytes.\n",
6956                                 (unsigned) sizeof(programStats.movelist) - 1);
6957                     }
6958
6959                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6960                 } else {
6961                     sprintf(programStats.movelist, " no PV\n");
6962                 }
6963
6964                 if (programStats.seen_stat) {
6965                     programStats.ok_to_send = 1;
6966                 }
6967
6968                 if (strchr(programStats.movelist, '(') != NULL) {
6969                     programStats.line_is_book = 1;
6970                     programStats.nr_moves = 0;
6971                     programStats.moves_left = 0;
6972                 } else {
6973                     programStats.line_is_book = 0;
6974                 }
6975
6976                 SendProgramStatsToFrontend( cps, &programStats );
6977
6978                 /* 
6979                     [AS] Protect the thinkOutput buffer from overflow... this
6980                     is only useful if buf1 hasn't overflowed first!
6981                 */
6982                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6983                         plylev, 
6984                         (gameMode == TwoMachinesPlay ?
6985                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6986                         ((double) curscore) / 100.0,
6987                         prefixHint ? lastHint : "",
6988                         prefixHint ? " " : "" );
6989
6990                 if( buf1[0] != NULLCHAR ) {
6991                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6992
6993                     if( strlen(buf1) > max_len ) {
6994                         if( appData.debugMode) {
6995                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6996                         }
6997                         buf1[max_len+1] = '\0';
6998                     }
6999
7000                     strcat( thinkOutput, buf1 );
7001                 }
7002
7003                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7004                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7005                     DisplayMove(currentMove - 1);
7006                 }
7007                 return;
7008
7009             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7010                 /* crafty (9.25+) says "(only move) <move>"
7011                  * if there is only 1 legal move
7012                  */
7013                 sscanf(p, "(only move) %s", buf1);
7014                 sprintf(thinkOutput, "%s (only move)", buf1);
7015                 sprintf(programStats.movelist, "%s (only move)", buf1);
7016                 programStats.depth = 1;
7017                 programStats.nr_moves = 1;
7018                 programStats.moves_left = 1;
7019                 programStats.nodes = 1;
7020                 programStats.time = 1;
7021                 programStats.got_only_move = 1;
7022
7023                 /* Not really, but we also use this member to
7024                    mean "line isn't going to change" (Crafty
7025                    isn't searching, so stats won't change) */
7026                 programStats.line_is_book = 1;
7027
7028                 SendProgramStatsToFrontend( cps, &programStats );
7029                 
7030                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7031                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7032                     DisplayMove(currentMove - 1);
7033                 }
7034                 return;
7035             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7036                               &time, &nodes, &plylev, &mvleft,
7037                               &mvtot, mvname) >= 5) {
7038                 /* The stat01: line is from Crafty (9.29+) in response
7039                    to the "." command */
7040                 programStats.seen_stat = 1;
7041                 cps->maybeThinking = TRUE;
7042
7043                 if (programStats.got_only_move || !appData.periodicUpdates)
7044                   return;
7045
7046                 programStats.depth = plylev;
7047                 programStats.time = time;
7048                 programStats.nodes = nodes;
7049                 programStats.moves_left = mvleft;
7050                 programStats.nr_moves = mvtot;
7051                 strcpy(programStats.move_name, mvname);
7052                 programStats.ok_to_send = 1;
7053                 programStats.movelist[0] = '\0';
7054
7055                 SendProgramStatsToFrontend( cps, &programStats );
7056
7057                 return;
7058
7059             } else if (strncmp(message,"++",2) == 0) {
7060                 /* Crafty 9.29+ outputs this */
7061                 programStats.got_fail = 2;
7062                 return;
7063
7064             } else if (strncmp(message,"--",2) == 0) {
7065                 /* Crafty 9.29+ outputs this */
7066                 programStats.got_fail = 1;
7067                 return;
7068
7069             } else if (thinkOutput[0] != NULLCHAR &&
7070                        strncmp(message, "    ", 4) == 0) {
7071                 unsigned message_len;
7072
7073                 p = message;
7074                 while (*p && *p == ' ') p++;
7075
7076                 message_len = strlen( p );
7077
7078                 /* [AS] Avoid buffer overflow */
7079                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7080                     strcat(thinkOutput, " ");
7081                     strcat(thinkOutput, p);
7082                 }
7083
7084                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7085                     strcat(programStats.movelist, " ");
7086                     strcat(programStats.movelist, p);
7087                 }
7088
7089                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7090                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7091                     DisplayMove(currentMove - 1);
7092                 }
7093                 return;
7094             }
7095         }
7096         else {
7097             buf1[0] = NULLCHAR;
7098
7099             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7100                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7101             {
7102                 ChessProgramStats cpstats;
7103
7104                 if (plyext != ' ' && plyext != '\t') {
7105                     time *= 100;
7106                 }
7107
7108                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7109                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7110                     curscore = -curscore;
7111                 }
7112
7113                 cpstats.depth = plylev;
7114                 cpstats.nodes = nodes;
7115                 cpstats.time = time;
7116                 cpstats.score = curscore;
7117                 cpstats.got_only_move = 0;
7118                 cpstats.movelist[0] = '\0';
7119
7120                 if (buf1[0] != NULLCHAR) {
7121                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7122                 }
7123
7124                 cpstats.ok_to_send = 0;
7125                 cpstats.line_is_book = 0;
7126                 cpstats.nr_moves = 0;
7127                 cpstats.moves_left = 0;
7128
7129                 SendProgramStatsToFrontend( cps, &cpstats );
7130             }
7131         }
7132     }
7133 }
7134
7135
7136 /* Parse a game score from the character string "game", and
7137    record it as the history of the current game.  The game
7138    score is NOT assumed to start from the standard position. 
7139    The display is not updated in any way.
7140    */
7141 void
7142 ParseGameHistory(game)
7143      char *game;
7144 {
7145     ChessMove moveType;
7146     int fromX, fromY, toX, toY, boardIndex;
7147     char promoChar;
7148     char *p, *q;
7149     char buf[MSG_SIZ];
7150
7151     if (appData.debugMode)
7152       fprintf(debugFP, "Parsing game history: %s\n", game);
7153
7154     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7155     gameInfo.site = StrSave(appData.icsHost);
7156     gameInfo.date = PGNDate();
7157     gameInfo.round = StrSave("-");
7158
7159     /* Parse out names of players */
7160     while (*game == ' ') game++;
7161     p = buf;
7162     while (*game != ' ') *p++ = *game++;
7163     *p = NULLCHAR;
7164     gameInfo.white = StrSave(buf);
7165     while (*game == ' ') game++;
7166     p = buf;
7167     while (*game != ' ' && *game != '\n') *p++ = *game++;
7168     *p = NULLCHAR;
7169     gameInfo.black = StrSave(buf);
7170
7171     /* Parse moves */
7172     boardIndex = blackPlaysFirst ? 1 : 0;
7173     yynewstr(game);
7174     for (;;) {
7175         yyboardindex = boardIndex;
7176         moveType = (ChessMove) yylex();
7177         switch (moveType) {
7178           case IllegalMove:             /* maybe suicide chess, etc. */
7179   if (appData.debugMode) {
7180     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7181     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7182     setbuf(debugFP, NULL);
7183   }
7184           case WhitePromotionChancellor:
7185           case BlackPromotionChancellor:
7186           case WhitePromotionArchbishop:
7187           case BlackPromotionArchbishop:
7188           case WhitePromotionQueen:
7189           case BlackPromotionQueen:
7190           case WhitePromotionRook:
7191           case BlackPromotionRook:
7192           case WhitePromotionBishop:
7193           case BlackPromotionBishop:
7194           case WhitePromotionKnight:
7195           case BlackPromotionKnight:
7196           case WhitePromotionKing:
7197           case BlackPromotionKing:
7198           case NormalMove:
7199           case WhiteCapturesEnPassant:
7200           case BlackCapturesEnPassant:
7201           case WhiteKingSideCastle:
7202           case WhiteQueenSideCastle:
7203           case BlackKingSideCastle:
7204           case BlackQueenSideCastle:
7205           case WhiteKingSideCastleWild:
7206           case WhiteQueenSideCastleWild:
7207           case BlackKingSideCastleWild:
7208           case BlackQueenSideCastleWild:
7209           /* PUSH Fabien */
7210           case WhiteHSideCastleFR:
7211           case WhiteASideCastleFR:
7212           case BlackHSideCastleFR:
7213           case BlackASideCastleFR:
7214           /* POP Fabien */
7215             fromX = currentMoveString[0] - AAA;
7216             fromY = currentMoveString[1] - ONE;
7217             toX = currentMoveString[2] - AAA;
7218             toY = currentMoveString[3] - ONE;
7219             promoChar = currentMoveString[4];
7220             break;
7221           case WhiteDrop:
7222           case BlackDrop:
7223             fromX = moveType == WhiteDrop ?
7224               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7225             (int) CharToPiece(ToLower(currentMoveString[0]));
7226             fromY = DROP_RANK;
7227             toX = currentMoveString[2] - AAA;
7228             toY = currentMoveString[3] - ONE;
7229             promoChar = NULLCHAR;
7230             break;
7231           case AmbiguousMove:
7232             /* bug? */
7233             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7234   if (appData.debugMode) {
7235     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7236     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7237     setbuf(debugFP, NULL);
7238   }
7239             DisplayError(buf, 0);
7240             return;
7241           case ImpossibleMove:
7242             /* bug? */
7243             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7244   if (appData.debugMode) {
7245     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247     setbuf(debugFP, NULL);
7248   }
7249             DisplayError(buf, 0);
7250             return;
7251           case (ChessMove) 0:   /* end of file */
7252             if (boardIndex < backwardMostMove) {
7253                 /* Oops, gap.  How did that happen? */
7254                 DisplayError(_("Gap in move list"), 0);
7255                 return;
7256             }
7257             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7258             if (boardIndex > forwardMostMove) {
7259                 forwardMostMove = boardIndex;
7260             }
7261             return;
7262           case ElapsedTime:
7263             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7264                 strcat(parseList[boardIndex-1], " ");
7265                 strcat(parseList[boardIndex-1], yy_text);
7266             }
7267             continue;
7268           case Comment:
7269           case PGNTag:
7270           case NAG:
7271           default:
7272             /* ignore */
7273             continue;
7274           case WhiteWins:
7275           case BlackWins:
7276           case GameIsDrawn:
7277           case GameUnfinished:
7278             if (gameMode == IcsExamining) {
7279                 if (boardIndex < backwardMostMove) {
7280                     /* Oops, gap.  How did that happen? */
7281                     return;
7282                 }
7283                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7284                 return;
7285             }
7286             gameInfo.result = moveType;
7287             p = strchr(yy_text, '{');
7288             if (p == NULL) p = strchr(yy_text, '(');
7289             if (p == NULL) {
7290                 p = yy_text;
7291                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7292             } else {
7293                 q = strchr(p, *p == '{' ? '}' : ')');
7294                 if (q != NULL) *q = NULLCHAR;
7295                 p++;
7296             }
7297             gameInfo.resultDetails = StrSave(p);
7298             continue;
7299         }
7300         if (boardIndex >= forwardMostMove &&
7301             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7302             backwardMostMove = blackPlaysFirst ? 1 : 0;
7303             return;
7304         }
7305         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7306                                  fromY, fromX, toY, toX, promoChar,
7307                                  parseList[boardIndex]);
7308         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7309         /* currentMoveString is set as a side-effect of yylex */
7310         strcpy(moveList[boardIndex], currentMoveString);
7311         strcat(moveList[boardIndex], "\n");
7312         boardIndex++;
7313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7314         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7315           case MT_NONE:
7316           case MT_STALEMATE:
7317           default:
7318             break;
7319           case MT_CHECK:
7320             if(gameInfo.variant != VariantShogi)
7321                 strcat(parseList[boardIndex - 1], "+");
7322             break;
7323           case MT_CHECKMATE:
7324           case MT_STAINMATE:
7325             strcat(parseList[boardIndex - 1], "#");
7326             break;
7327         }
7328     }
7329 }
7330
7331
7332 /* Apply a move to the given board  */
7333 void
7334 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7335      int fromX, fromY, toX, toY;
7336      int promoChar;
7337      Board board;
7338 {
7339   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7340
7341     /* [HGM] compute & store e.p. status and castling rights for new position */
7342     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7343     { int i;
7344
7345       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7346       oldEP = board[EP_STATUS];
7347       board[EP_STATUS] = EP_NONE;
7348
7349       if( board[toY][toX] != EmptySquare ) 
7350            board[EP_STATUS] = EP_CAPTURE;  
7351
7352       if( board[fromY][fromX] == WhitePawn ) {
7353            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7354                board[EP_STATUS] = EP_PAWN_MOVE;
7355            if( toY-fromY==2) {
7356                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7357                         gameInfo.variant != VariantBerolina || toX < fromX)
7358                       board[EP_STATUS] = toX | berolina;
7359                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7360                         gameInfo.variant != VariantBerolina || toX > fromX) 
7361                       board[EP_STATUS] = toX;
7362            }
7363       } else 
7364       if( board[fromY][fromX] == BlackPawn ) {
7365            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7366                board[EP_STATUS] = EP_PAWN_MOVE; 
7367            if( toY-fromY== -2) {
7368                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7369                         gameInfo.variant != VariantBerolina || toX < fromX)
7370                       board[EP_STATUS] = toX | berolina;
7371                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7372                         gameInfo.variant != VariantBerolina || toX > fromX) 
7373                       board[EP_STATUS] = toX;
7374            }
7375        }
7376
7377        for(i=0; i<nrCastlingRights; i++) {
7378            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7379               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7380              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7381        }
7382
7383     }
7384
7385   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7386   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7387        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7388          
7389   if (fromX == toX && fromY == toY) return;
7390
7391   if (fromY == DROP_RANK) {
7392         /* must be first */
7393         piece = board[toY][toX] = (ChessSquare) fromX;
7394   } else {
7395      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7396      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7397      if(gameInfo.variant == VariantKnightmate)
7398          king += (int) WhiteUnicorn - (int) WhiteKing;
7399
7400     /* Code added by Tord: */
7401     /* FRC castling assumed when king captures friendly rook. */
7402     if (board[fromY][fromX] == WhiteKing &&
7403              board[toY][toX] == WhiteRook) {
7404       board[fromY][fromX] = EmptySquare;
7405       board[toY][toX] = EmptySquare;
7406       if(toX > fromX) {
7407         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7408       } else {
7409         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7410       }
7411     } else if (board[fromY][fromX] == BlackKing &&
7412                board[toY][toX] == BlackRook) {
7413       board[fromY][fromX] = EmptySquare;
7414       board[toY][toX] = EmptySquare;
7415       if(toX > fromX) {
7416         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7417       } else {
7418         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7419       }
7420     /* End of code added by Tord */
7421
7422     } else if (board[fromY][fromX] == king
7423         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7424         && toY == fromY && toX > fromX+1) {
7425         board[fromY][fromX] = EmptySquare;
7426         board[toY][toX] = king;
7427         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7428         board[fromY][BOARD_RGHT-1] = EmptySquare;
7429     } else if (board[fromY][fromX] == king
7430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7431                && toY == fromY && toX < fromX-1) {
7432         board[fromY][fromX] = EmptySquare;
7433         board[toY][toX] = king;
7434         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7435         board[fromY][BOARD_LEFT] = EmptySquare;
7436     } else if (board[fromY][fromX] == WhitePawn
7437                && toY == BOARD_HEIGHT-1
7438                && gameInfo.variant != VariantXiangqi
7439                ) {
7440         /* white pawn promotion */
7441         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7442         if (board[toY][toX] == EmptySquare) {
7443             board[toY][toX] = WhiteQueen;
7444         }
7445         if(gameInfo.variant==VariantBughouse ||
7446            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7447             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7448         board[fromY][fromX] = EmptySquare;
7449     } else if ((fromY == BOARD_HEIGHT-4)
7450                && (toX != fromX)
7451                && gameInfo.variant != VariantXiangqi
7452                && gameInfo.variant != VariantBerolina
7453                && (board[fromY][fromX] == WhitePawn)
7454                && (board[toY][toX] == EmptySquare)) {
7455         board[fromY][fromX] = EmptySquare;
7456         board[toY][toX] = WhitePawn;
7457         captured = board[toY - 1][toX];
7458         board[toY - 1][toX] = EmptySquare;
7459     } else if ((fromY == BOARD_HEIGHT-4)
7460                && (toX == fromX)
7461                && gameInfo.variant == VariantBerolina
7462                && (board[fromY][fromX] == WhitePawn)
7463                && (board[toY][toX] == EmptySquare)) {
7464         board[fromY][fromX] = EmptySquare;
7465         board[toY][toX] = WhitePawn;
7466         if(oldEP & EP_BEROLIN_A) {
7467                 captured = board[fromY][fromX-1];
7468                 board[fromY][fromX-1] = EmptySquare;
7469         }else{  captured = board[fromY][fromX+1];
7470                 board[fromY][fromX+1] = EmptySquare;
7471         }
7472     } else if (board[fromY][fromX] == king
7473         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7474                && toY == fromY && toX > fromX+1) {
7475         board[fromY][fromX] = EmptySquare;
7476         board[toY][toX] = king;
7477         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7478         board[fromY][BOARD_RGHT-1] = EmptySquare;
7479     } else if (board[fromY][fromX] == king
7480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481                && toY == fromY && toX < fromX-1) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = king;
7484         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7485         board[fromY][BOARD_LEFT] = EmptySquare;
7486     } else if (fromY == 7 && fromX == 3
7487                && board[fromY][fromX] == BlackKing
7488                && toY == 7 && toX == 5) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = BlackKing;
7491         board[fromY][7] = EmptySquare;
7492         board[toY][4] = BlackRook;
7493     } else if (fromY == 7 && fromX == 3
7494                && board[fromY][fromX] == BlackKing
7495                && toY == 7 && toX == 1) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = BlackKing;
7498         board[fromY][0] = EmptySquare;
7499         board[toY][2] = BlackRook;
7500     } else if (board[fromY][fromX] == BlackPawn
7501                && toY == 0
7502                && gameInfo.variant != VariantXiangqi
7503                ) {
7504         /* black pawn promotion */
7505         board[0][toX] = CharToPiece(ToLower(promoChar));
7506         if (board[0][toX] == EmptySquare) {
7507             board[0][toX] = BlackQueen;
7508         }
7509         if(gameInfo.variant==VariantBughouse ||
7510            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7511             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7512         board[fromY][fromX] = EmptySquare;
7513     } else if ((fromY == 3)
7514                && (toX != fromX)
7515                && gameInfo.variant != VariantXiangqi
7516                && gameInfo.variant != VariantBerolina
7517                && (board[fromY][fromX] == BlackPawn)
7518                && (board[toY][toX] == EmptySquare)) {
7519         board[fromY][fromX] = EmptySquare;
7520         board[toY][toX] = BlackPawn;
7521         captured = board[toY + 1][toX];
7522         board[toY + 1][toX] = EmptySquare;
7523     } else if ((fromY == 3)
7524                && (toX == fromX)
7525                && gameInfo.variant == VariantBerolina
7526                && (board[fromY][fromX] == BlackPawn)
7527                && (board[toY][toX] == EmptySquare)) {
7528         board[fromY][fromX] = EmptySquare;
7529         board[toY][toX] = BlackPawn;
7530         if(oldEP & EP_BEROLIN_A) {
7531                 captured = board[fromY][fromX-1];
7532                 board[fromY][fromX-1] = EmptySquare;
7533         }else{  captured = board[fromY][fromX+1];
7534                 board[fromY][fromX+1] = EmptySquare;
7535         }
7536     } else {
7537         board[toY][toX] = board[fromY][fromX];
7538         board[fromY][fromX] = EmptySquare;
7539     }
7540
7541     /* [HGM] now we promote for Shogi, if needed */
7542     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7543         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7544   }
7545
7546     if (gameInfo.holdingsWidth != 0) {
7547
7548       /* !!A lot more code needs to be written to support holdings  */
7549       /* [HGM] OK, so I have written it. Holdings are stored in the */
7550       /* penultimate board files, so they are automaticlly stored   */
7551       /* in the game history.                                       */
7552       if (fromY == DROP_RANK) {
7553         /* Delete from holdings, by decreasing count */
7554         /* and erasing image if necessary            */
7555         p = (int) fromX;
7556         if(p < (int) BlackPawn) { /* white drop */
7557              p -= (int)WhitePawn;
7558                  p = PieceToNumber((ChessSquare)p);
7559              if(p >= gameInfo.holdingsSize) p = 0;
7560              if(--board[p][BOARD_WIDTH-2] <= 0)
7561                   board[p][BOARD_WIDTH-1] = EmptySquare;
7562              if((int)board[p][BOARD_WIDTH-2] < 0)
7563                         board[p][BOARD_WIDTH-2] = 0;
7564         } else {                  /* black drop */
7565              p -= (int)BlackPawn;
7566                  p = PieceToNumber((ChessSquare)p);
7567              if(p >= gameInfo.holdingsSize) p = 0;
7568              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7569                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7570              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7571                         board[BOARD_HEIGHT-1-p][1] = 0;
7572         }
7573       }
7574       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7575           && gameInfo.variant != VariantBughouse        ) {
7576         /* [HGM] holdings: Add to holdings, if holdings exist */
7577         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7578                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7579                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7580         }
7581         p = (int) captured;
7582         if (p >= (int) BlackPawn) {
7583           p -= (int)BlackPawn;
7584           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7585                   /* in Shogi restore piece to its original  first */
7586                   captured = (ChessSquare) (DEMOTED captured);
7587                   p = DEMOTED p;
7588           }
7589           p = PieceToNumber((ChessSquare)p);
7590           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7591           board[p][BOARD_WIDTH-2]++;
7592           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7593         } else {
7594           p -= (int)WhitePawn;
7595           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7596                   captured = (ChessSquare) (DEMOTED captured);
7597                   p = DEMOTED p;
7598           }
7599           p = PieceToNumber((ChessSquare)p);
7600           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7601           board[BOARD_HEIGHT-1-p][1]++;
7602           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7603         }
7604       }
7605     } else if (gameInfo.variant == VariantAtomic) {
7606       if (captured != EmptySquare) {
7607         int y, x;
7608         for (y = toY-1; y <= toY+1; y++) {
7609           for (x = toX-1; x <= toX+1; x++) {
7610             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7611                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7612               board[y][x] = EmptySquare;
7613             }
7614           }
7615         }
7616         board[toY][toX] = EmptySquare;
7617       }
7618     }
7619     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7620         /* [HGM] Shogi promotions */
7621         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7622     }
7623
7624     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7625                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7626         // [HGM] superchess: take promotion piece out of holdings
7627         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7628         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7629             if(!--board[k][BOARD_WIDTH-2])
7630                 board[k][BOARD_WIDTH-1] = EmptySquare;
7631         } else {
7632             if(!--board[BOARD_HEIGHT-1-k][1])
7633                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7634         }
7635     }
7636
7637 }
7638
7639 /* Updates forwardMostMove */
7640 void
7641 MakeMove(fromX, fromY, toX, toY, promoChar)
7642      int fromX, fromY, toX, toY;
7643      int promoChar;
7644 {
7645 //    forwardMostMove++; // [HGM] bare: moved downstream
7646
7647     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7648         int timeLeft; static int lastLoadFlag=0; int king, piece;
7649         piece = boards[forwardMostMove][fromY][fromX];
7650         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7651         if(gameInfo.variant == VariantKnightmate)
7652             king += (int) WhiteUnicorn - (int) WhiteKing;
7653         if(forwardMostMove == 0) {
7654             if(blackPlaysFirst) 
7655                 fprintf(serverMoves, "%s;", second.tidy);
7656             fprintf(serverMoves, "%s;", first.tidy);
7657             if(!blackPlaysFirst) 
7658                 fprintf(serverMoves, "%s;", second.tidy);
7659         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7660         lastLoadFlag = loadFlag;
7661         // print base move
7662         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7663         // print castling suffix
7664         if( toY == fromY && piece == king ) {
7665             if(toX-fromX > 1)
7666                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7667             if(fromX-toX >1)
7668                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7669         }
7670         // e.p. suffix
7671         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7672              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7673              boards[forwardMostMove][toY][toX] == EmptySquare
7674              && fromX != toX )
7675                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7676         // promotion suffix
7677         if(promoChar != NULLCHAR)
7678                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7679         if(!loadFlag) {
7680             fprintf(serverMoves, "/%d/%d",
7681                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7682             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7683             else                      timeLeft = blackTimeRemaining/1000;
7684             fprintf(serverMoves, "/%d", timeLeft);
7685         }
7686         fflush(serverMoves);
7687     }
7688
7689     if (forwardMostMove+1 >= MAX_MOVES) {
7690       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7691                         0, 1);
7692       return;
7693     }
7694     if (commentList[forwardMostMove+1] != NULL) {
7695         free(commentList[forwardMostMove+1]);
7696         commentList[forwardMostMove+1] = NULL;
7697     }
7698     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7699     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7700     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7701     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7702     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7703     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7704     gameInfo.result = GameUnfinished;
7705     if (gameInfo.resultDetails != NULL) {
7706         free(gameInfo.resultDetails);
7707         gameInfo.resultDetails = NULL;
7708     }
7709     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7710                               moveList[forwardMostMove - 1]);
7711     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7712                              PosFlags(forwardMostMove - 1),
7713                              fromY, fromX, toY, toX, promoChar,
7714                              parseList[forwardMostMove - 1]);
7715     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7716       case MT_NONE:
7717       case MT_STALEMATE:
7718       default:
7719         break;
7720       case MT_CHECK:
7721         if(gameInfo.variant != VariantShogi)
7722             strcat(parseList[forwardMostMove - 1], "+");
7723         break;
7724       case MT_CHECKMATE:
7725       case MT_STAINMATE:
7726         strcat(parseList[forwardMostMove - 1], "#");
7727         break;
7728     }
7729     if (appData.debugMode) {
7730         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7731     }
7732
7733 }
7734
7735 /* Updates currentMove if not pausing */
7736 void
7737 ShowMove(fromX, fromY, toX, toY)
7738 {
7739     int instant = (gameMode == PlayFromGameFile) ?
7740         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7741     if(appData.noGUI) return;
7742     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7743         if (!instant) {
7744             if (forwardMostMove == currentMove + 1) {
7745                 AnimateMove(boards[forwardMostMove - 1],
7746                             fromX, fromY, toX, toY);
7747             }
7748             if (appData.highlightLastMove) {
7749                 SetHighlights(fromX, fromY, toX, toY);
7750             }
7751         }
7752         currentMove = forwardMostMove;
7753     }
7754
7755     if (instant) return;
7756
7757     DisplayMove(currentMove - 1);
7758     DrawPosition(FALSE, boards[currentMove]);
7759     DisplayBothClocks();
7760     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7761 }
7762
7763 void SendEgtPath(ChessProgramState *cps)
7764 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7765         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7766
7767         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7768
7769         while(*p) {
7770             char c, *q = name+1, *r, *s;
7771
7772             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7773             while(*p && *p != ',') *q++ = *p++;
7774             *q++ = ':'; *q = 0;
7775             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7776                 strcmp(name, ",nalimov:") == 0 ) {
7777                 // take nalimov path from the menu-changeable option first, if it is defined
7778                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7779                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7780             } else
7781             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7782                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7783                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7784                 s = r = StrStr(s, ":") + 1; // beginning of path info
7785                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7786                 c = *r; *r = 0;             // temporarily null-terminate path info
7787                     *--q = 0;               // strip of trailig ':' from name
7788                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7789                 *r = c;
7790                 SendToProgram(buf,cps);     // send egtbpath command for this format
7791             }
7792             if(*p == ',') p++; // read away comma to position for next format name
7793         }
7794 }
7795
7796 void
7797 InitChessProgram(cps, setup)
7798      ChessProgramState *cps;
7799      int setup; /* [HGM] needed to setup FRC opening position */
7800 {
7801     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7802     if (appData.noChessProgram) return;
7803     hintRequested = FALSE;
7804     bookRequested = FALSE;
7805
7806     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7807     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7808     if(cps->memSize) { /* [HGM] memory */
7809         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7810         SendToProgram(buf, cps);
7811     }
7812     SendEgtPath(cps); /* [HGM] EGT */
7813     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7814         sprintf(buf, "cores %d\n", appData.smpCores);
7815         SendToProgram(buf, cps);
7816     }
7817
7818     SendToProgram(cps->initString, cps);
7819     if (gameInfo.variant != VariantNormal &&
7820         gameInfo.variant != VariantLoadable
7821         /* [HGM] also send variant if board size non-standard */
7822         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7823                                             ) {
7824       char *v = VariantName(gameInfo.variant);
7825       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7826         /* [HGM] in protocol 1 we have to assume all variants valid */
7827         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7828         DisplayFatalError(buf, 0, 1);
7829         return;
7830       }
7831
7832       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7833       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7834       if( gameInfo.variant == VariantXiangqi )
7835            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7836       if( gameInfo.variant == VariantShogi )
7837            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7838       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7839            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7840       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7841                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7842            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7843       if( gameInfo.variant == VariantCourier )
7844            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7845       if( gameInfo.variant == VariantSuper )
7846            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7847       if( gameInfo.variant == VariantGreat )
7848            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7849
7850       if(overruled) {
7851            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7852                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7853            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7854            if(StrStr(cps->variants, b) == NULL) { 
7855                // specific sized variant not known, check if general sizing allowed
7856                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7857                    if(StrStr(cps->variants, "boardsize") == NULL) {
7858                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7859                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7860                        DisplayFatalError(buf, 0, 1);
7861                        return;
7862                    }
7863                    /* [HGM] here we really should compare with the maximum supported board size */
7864                }
7865            }
7866       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7867       sprintf(buf, "variant %s\n", b);
7868       SendToProgram(buf, cps);
7869     }
7870     currentlyInitializedVariant = gameInfo.variant;
7871
7872     /* [HGM] send opening position in FRC to first engine */
7873     if(setup) {
7874           SendToProgram("force\n", cps);
7875           SendBoard(cps, 0);
7876           /* engine is now in force mode! Set flag to wake it up after first move. */
7877           setboardSpoiledMachineBlack = 1;
7878     }
7879
7880     if (cps->sendICS) {
7881       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7882       SendToProgram(buf, cps);
7883     }
7884     cps->maybeThinking = FALSE;
7885     cps->offeredDraw = 0;
7886     if (!appData.icsActive) {
7887         SendTimeControl(cps, movesPerSession, timeControl,
7888                         timeIncrement, appData.searchDepth,
7889                         searchTime);
7890     }
7891     if (appData.showThinking 
7892         // [HGM] thinking: four options require thinking output to be sent
7893         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7894                                 ) {
7895         SendToProgram("post\n", cps);
7896     }
7897     SendToProgram("hard\n", cps);
7898     if (!appData.ponderNextMove) {
7899         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7900            it without being sure what state we are in first.  "hard"
7901            is not a toggle, so that one is OK.
7902          */
7903         SendToProgram("easy\n", cps);
7904     }
7905     if (cps->usePing) {
7906       sprintf(buf, "ping %d\n", ++cps->lastPing);
7907       SendToProgram(buf, cps);
7908     }
7909     cps->initDone = TRUE;
7910 }   
7911
7912
7913 void
7914 StartChessProgram(cps)
7915      ChessProgramState *cps;
7916 {
7917     char buf[MSG_SIZ];
7918     int err;
7919
7920     if (appData.noChessProgram) return;
7921     cps->initDone = FALSE;
7922
7923     if (strcmp(cps->host, "localhost") == 0) {
7924         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7925     } else if (*appData.remoteShell == NULLCHAR) {
7926         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7927     } else {
7928         if (*appData.remoteUser == NULLCHAR) {
7929           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7930                     cps->program);
7931         } else {
7932           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7933                     cps->host, appData.remoteUser, cps->program);
7934         }
7935         err = StartChildProcess(buf, "", &cps->pr);
7936     }
7937     
7938     if (err != 0) {
7939         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7940         DisplayFatalError(buf, err, 1);
7941         cps->pr = NoProc;
7942         cps->isr = NULL;
7943         return;
7944     }
7945     
7946     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7947     if (cps->protocolVersion > 1) {
7948       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7949       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7950       cps->comboCnt = 0;  //                and values of combo boxes
7951       SendToProgram(buf, cps);
7952     } else {
7953       SendToProgram("xboard\n", cps);
7954     }
7955 }
7956
7957
7958 void
7959 TwoMachinesEventIfReady P((void))
7960 {
7961   if (first.lastPing != first.lastPong) {
7962     DisplayMessage("", _("Waiting for first chess program"));
7963     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7964     return;
7965   }
7966   if (second.lastPing != second.lastPong) {
7967     DisplayMessage("", _("Waiting for second chess program"));
7968     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7969     return;
7970   }
7971   ThawUI();
7972   TwoMachinesEvent();
7973 }
7974
7975 void
7976 NextMatchGame P((void))
7977 {
7978     int index; /* [HGM] autoinc: step load index during match */
7979     Reset(FALSE, TRUE);
7980     if (*appData.loadGameFile != NULLCHAR) {
7981         index = appData.loadGameIndex;
7982         if(index < 0) { // [HGM] autoinc
7983             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7984             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7985         } 
7986         LoadGameFromFile(appData.loadGameFile,
7987                          index,
7988                          appData.loadGameFile, FALSE);
7989     } else if (*appData.loadPositionFile != NULLCHAR) {
7990         index = appData.loadPositionIndex;
7991         if(index < 0) { // [HGM] autoinc
7992             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7993             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7994         } 
7995         LoadPositionFromFile(appData.loadPositionFile,
7996                              index,
7997                              appData.loadPositionFile);
7998     }
7999     TwoMachinesEventIfReady();
8000 }
8001
8002 void UserAdjudicationEvent( int result )
8003 {
8004     ChessMove gameResult = GameIsDrawn;
8005
8006     if( result > 0 ) {
8007         gameResult = WhiteWins;
8008     }
8009     else if( result < 0 ) {
8010         gameResult = BlackWins;
8011     }
8012
8013     if( gameMode == TwoMachinesPlay ) {
8014         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8015     }
8016 }
8017
8018
8019 // [HGM] save: calculate checksum of game to make games easily identifiable
8020 int StringCheckSum(char *s)
8021 {
8022         int i = 0;
8023         if(s==NULL) return 0;
8024         while(*s) i = i*259 + *s++;
8025         return i;
8026 }
8027
8028 int GameCheckSum()
8029 {
8030         int i, sum=0;
8031         for(i=backwardMostMove; i<forwardMostMove; i++) {
8032                 sum += pvInfoList[i].depth;
8033                 sum += StringCheckSum(parseList[i]);
8034                 sum += StringCheckSum(commentList[i]);
8035                 sum *= 261;
8036         }
8037         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8038         return sum + StringCheckSum(commentList[i]);
8039 } // end of save patch
8040
8041 void
8042 GameEnds(result, resultDetails, whosays)
8043      ChessMove result;
8044      char *resultDetails;
8045      int whosays;
8046 {
8047     GameMode nextGameMode;
8048     int isIcsGame;
8049     char buf[MSG_SIZ];
8050
8051     if(endingGame) return; /* [HGM] crash: forbid recursion */
8052     endingGame = 1;
8053
8054     if (appData.debugMode) {
8055       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8056               result, resultDetails ? resultDetails : "(null)", whosays);
8057     }
8058
8059     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8060         /* If we are playing on ICS, the server decides when the
8061            game is over, but the engine can offer to draw, claim 
8062            a draw, or resign. 
8063          */
8064 #if ZIPPY
8065         if (appData.zippyPlay && first.initDone) {
8066             if (result == GameIsDrawn) {
8067                 /* In case draw still needs to be claimed */
8068                 SendToICS(ics_prefix);
8069                 SendToICS("draw\n");
8070             } else if (StrCaseStr(resultDetails, "resign")) {
8071                 SendToICS(ics_prefix);
8072                 SendToICS("resign\n");
8073             }
8074         }
8075 #endif
8076         endingGame = 0; /* [HGM] crash */
8077         return;
8078     }
8079
8080     /* If we're loading the game from a file, stop */
8081     if (whosays == GE_FILE) {
8082       (void) StopLoadGameTimer();
8083       gameFileFP = NULL;
8084     }
8085
8086     /* Cancel draw offers */
8087     first.offeredDraw = second.offeredDraw = 0;
8088
8089     /* If this is an ICS game, only ICS can really say it's done;
8090        if not, anyone can. */
8091     isIcsGame = (gameMode == IcsPlayingWhite || 
8092                  gameMode == IcsPlayingBlack || 
8093                  gameMode == IcsObserving    || 
8094                  gameMode == IcsExamining);
8095
8096     if (!isIcsGame || whosays == GE_ICS) {
8097         /* OK -- not an ICS game, or ICS said it was done */
8098         StopClocks();
8099         if (!isIcsGame && !appData.noChessProgram) 
8100           SetUserThinkingEnables();
8101     
8102         /* [HGM] if a machine claims the game end we verify this claim */
8103         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8104             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8105                 char claimer;
8106                 ChessMove trueResult = (ChessMove) -1;
8107
8108                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8109                                             first.twoMachinesColor[0] :
8110                                             second.twoMachinesColor[0] ;
8111
8112                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8113                 if((int)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8114                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8115                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8116                 } else
8117                 if((int)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8118                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8119                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8120                 } else
8121                 if((int)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8122                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8123                 }
8124
8125                 // now verify win claims, but not in drop games, as we don't understand those yet
8126                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8127                                                  || gameInfo.variant == VariantGreat) &&
8128                     (result == WhiteWins && claimer == 'w' ||
8129                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8130                       if (appData.debugMode) {
8131                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8132                                 result, (int)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8133                       }
8134                       if(result != trueResult) {
8135                               sprintf(buf, "False win claim: '%s'", resultDetails);
8136                               result = claimer == 'w' ? BlackWins : WhiteWins;
8137                               resultDetails = buf;
8138                       }
8139                 } else
8140                 if( result == GameIsDrawn && (int)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8141                     && (forwardMostMove <= backwardMostMove ||
8142                         (int)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8143                         (claimer=='b')==(forwardMostMove&1))
8144                                                                                   ) {
8145                       /* [HGM] verify: draws that were not flagged are false claims */
8146                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8147                       result = claimer == 'w' ? BlackWins : WhiteWins;
8148                       resultDetails = buf;
8149                 }
8150                 /* (Claiming a loss is accepted no questions asked!) */
8151             }
8152             /* [HGM] bare: don't allow bare King to win */
8153             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8154                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8155                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8156                && result != GameIsDrawn)
8157             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8158                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8159                         int p = (int)boards[forwardMostMove][i][j] - color;
8160                         if(p >= 0 && p <= (int)WhiteKing) k++;
8161                 }
8162                 if (appData.debugMode) {
8163                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8164                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8165                 }
8166                 if(k <= 1) {
8167                         result = GameIsDrawn;
8168                         sprintf(buf, "%s but bare king", resultDetails);
8169                         resultDetails = buf;
8170                 }
8171             }
8172         }
8173
8174
8175         if(serverMoves != NULL && !loadFlag) { char c = '=';
8176             if(result==WhiteWins) c = '+';
8177             if(result==BlackWins) c = '-';
8178             if(resultDetails != NULL)
8179                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8180         }
8181         if (resultDetails != NULL) {
8182             gameInfo.result = result;
8183             gameInfo.resultDetails = StrSave(resultDetails);
8184
8185             /* display last move only if game was not loaded from file */
8186             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8187                 DisplayMove(currentMove - 1);
8188     
8189             if (forwardMostMove != 0) {
8190                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8191                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8192                                                                 ) {
8193                     if (*appData.saveGameFile != NULLCHAR) {
8194                         SaveGameToFile(appData.saveGameFile, TRUE);
8195                     } else if (appData.autoSaveGames) {
8196                         AutoSaveGame();
8197                     }
8198                     if (*appData.savePositionFile != NULLCHAR) {
8199                         SavePositionToFile(appData.savePositionFile);
8200                     }
8201                 }
8202             }
8203
8204             /* Tell program how game ended in case it is learning */
8205             /* [HGM] Moved this to after saving the PGN, just in case */
8206             /* engine died and we got here through time loss. In that */
8207             /* case we will get a fatal error writing the pipe, which */
8208             /* would otherwise lose us the PGN.                       */
8209             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8210             /* output during GameEnds should never be fatal anymore   */
8211             if (gameMode == MachinePlaysWhite ||
8212                 gameMode == MachinePlaysBlack ||
8213                 gameMode == TwoMachinesPlay ||
8214                 gameMode == IcsPlayingWhite ||
8215                 gameMode == IcsPlayingBlack ||
8216                 gameMode == BeginningOfGame) {
8217                 char buf[MSG_SIZ];
8218                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8219                         resultDetails);
8220                 if (first.pr != NoProc) {
8221                     SendToProgram(buf, &first);
8222                 }
8223                 if (second.pr != NoProc &&
8224                     gameMode == TwoMachinesPlay) {
8225                     SendToProgram(buf, &second);
8226                 }
8227             }
8228         }
8229
8230         if (appData.icsActive) {
8231             if (appData.quietPlay &&
8232                 (gameMode == IcsPlayingWhite ||
8233                  gameMode == IcsPlayingBlack)) {
8234                 SendToICS(ics_prefix);
8235                 SendToICS("set shout 1\n");
8236             }
8237             nextGameMode = IcsIdle;
8238             ics_user_moved = FALSE;
8239             /* clean up premove.  It's ugly when the game has ended and the
8240              * premove highlights are still on the board.
8241              */
8242             if (gotPremove) {
8243               gotPremove = FALSE;
8244               ClearPremoveHighlights();
8245               DrawPosition(FALSE, boards[currentMove]);
8246             }
8247             if (whosays == GE_ICS) {
8248                 switch (result) {
8249                 case WhiteWins:
8250                     if (gameMode == IcsPlayingWhite)
8251                         PlayIcsWinSound();
8252                     else if(gameMode == IcsPlayingBlack)
8253                         PlayIcsLossSound();
8254                     break;
8255                 case BlackWins:
8256                     if (gameMode == IcsPlayingBlack)
8257                         PlayIcsWinSound();
8258                     else if(gameMode == IcsPlayingWhite)
8259                         PlayIcsLossSound();
8260                     break;
8261                 case GameIsDrawn:
8262                     PlayIcsDrawSound();
8263                     break;
8264                 default:
8265                     PlayIcsUnfinishedSound();
8266                 }
8267             }
8268         } else if (gameMode == EditGame ||
8269                    gameMode == PlayFromGameFile || 
8270                    gameMode == AnalyzeMode || 
8271                    gameMode == AnalyzeFile) {
8272             nextGameMode = gameMode;
8273         } else {
8274             nextGameMode = EndOfGame;
8275         }
8276         pausing = FALSE;
8277         ModeHighlight();
8278     } else {
8279         nextGameMode = gameMode;
8280     }
8281
8282     if (appData.noChessProgram) {
8283         gameMode = nextGameMode;
8284         ModeHighlight();
8285         endingGame = 0; /* [HGM] crash */
8286         return;
8287     }
8288
8289     if (first.reuse) {
8290         /* Put first chess program into idle state */
8291         if (first.pr != NoProc &&
8292             (gameMode == MachinePlaysWhite ||
8293              gameMode == MachinePlaysBlack ||
8294              gameMode == TwoMachinesPlay ||
8295              gameMode == IcsPlayingWhite ||
8296              gameMode == IcsPlayingBlack ||
8297              gameMode == BeginningOfGame)) {
8298             SendToProgram("force\n", &first);
8299             if (first.usePing) {
8300               char buf[MSG_SIZ];
8301               sprintf(buf, "ping %d\n", ++first.lastPing);
8302               SendToProgram(buf, &first);
8303             }
8304         }
8305     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8306         /* Kill off first chess program */
8307         if (first.isr != NULL)
8308           RemoveInputSource(first.isr);
8309         first.isr = NULL;
8310     
8311         if (first.pr != NoProc) {
8312             ExitAnalyzeMode();
8313             DoSleep( appData.delayBeforeQuit );
8314             SendToProgram("quit\n", &first);
8315             DoSleep( appData.delayAfterQuit );
8316             DestroyChildProcess(first.pr, first.useSigterm);
8317         }
8318         first.pr = NoProc;
8319     }
8320     if (second.reuse) {
8321         /* Put second chess program into idle state */
8322         if (second.pr != NoProc &&
8323             gameMode == TwoMachinesPlay) {
8324             SendToProgram("force\n", &second);
8325             if (second.usePing) {
8326               char buf[MSG_SIZ];
8327               sprintf(buf, "ping %d\n", ++second.lastPing);
8328               SendToProgram(buf, &second);
8329             }
8330         }
8331     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8332         /* Kill off second chess program */
8333         if (second.isr != NULL)
8334           RemoveInputSource(second.isr);
8335         second.isr = NULL;
8336     
8337         if (second.pr != NoProc) {
8338             DoSleep( appData.delayBeforeQuit );
8339             SendToProgram("quit\n", &second);
8340             DoSleep( appData.delayAfterQuit );
8341             DestroyChildProcess(second.pr, second.useSigterm);
8342         }
8343         second.pr = NoProc;
8344     }
8345
8346     if (matchMode && gameMode == TwoMachinesPlay) {
8347         switch (result) {
8348         case WhiteWins:
8349           if (first.twoMachinesColor[0] == 'w') {
8350             first.matchWins++;
8351           } else {
8352             second.matchWins++;
8353           }
8354           break;
8355         case BlackWins:
8356           if (first.twoMachinesColor[0] == 'b') {
8357             first.matchWins++;
8358           } else {
8359             second.matchWins++;
8360           }
8361           break;
8362         default:
8363           break;
8364         }
8365         if (matchGame < appData.matchGames) {
8366             char *tmp;
8367             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8368                 tmp = first.twoMachinesColor;
8369                 first.twoMachinesColor = second.twoMachinesColor;
8370                 second.twoMachinesColor = tmp;
8371             }
8372             gameMode = nextGameMode;
8373             matchGame++;
8374             if(appData.matchPause>10000 || appData.matchPause<10)
8375                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8376             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8377             endingGame = 0; /* [HGM] crash */
8378             return;
8379         } else {
8380             char buf[MSG_SIZ];
8381             gameMode = nextGameMode;
8382             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8383                     first.tidy, second.tidy,
8384                     first.matchWins, second.matchWins,
8385                     appData.matchGames - (first.matchWins + second.matchWins));
8386             DisplayFatalError(buf, 0, 0);
8387         }
8388     }
8389     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8390         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8391       ExitAnalyzeMode();
8392     gameMode = nextGameMode;
8393     ModeHighlight();
8394     endingGame = 0;  /* [HGM] crash */
8395 }
8396
8397 /* Assumes program was just initialized (initString sent).
8398    Leaves program in force mode. */
8399 void
8400 FeedMovesToProgram(cps, upto) 
8401      ChessProgramState *cps;
8402      int upto;
8403 {
8404     int i;
8405     
8406     if (appData.debugMode)
8407       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8408               startedFromSetupPosition ? "position and " : "",
8409               backwardMostMove, upto, cps->which);
8410     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8411         // [HGM] variantswitch: make engine aware of new variant
8412         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8413                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8414         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8415         SendToProgram(buf, cps);
8416         currentlyInitializedVariant = gameInfo.variant;
8417     }
8418     SendToProgram("force\n", cps);
8419     if (startedFromSetupPosition) {
8420         SendBoard(cps, backwardMostMove);
8421     if (appData.debugMode) {
8422         fprintf(debugFP, "feedMoves\n");
8423     }
8424     }
8425     for (i = backwardMostMove; i < upto; i++) {
8426         SendMoveToProgram(i, cps);
8427     }
8428 }
8429
8430
8431 void
8432 ResurrectChessProgram()
8433 {
8434      /* The chess program may have exited.
8435         If so, restart it and feed it all the moves made so far. */
8436
8437     if (appData.noChessProgram || first.pr != NoProc) return;
8438     
8439     StartChessProgram(&first);
8440     InitChessProgram(&first, FALSE);
8441     FeedMovesToProgram(&first, currentMove);
8442
8443     if (!first.sendTime) {
8444         /* can't tell gnuchess what its clock should read,
8445            so we bow to its notion. */
8446         ResetClocks();
8447         timeRemaining[0][currentMove] = whiteTimeRemaining;
8448         timeRemaining[1][currentMove] = blackTimeRemaining;
8449     }
8450
8451     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8452                 appData.icsEngineAnalyze) && first.analysisSupport) {
8453       SendToProgram("analyze\n", &first);
8454       first.analyzing = TRUE;
8455     }
8456 }
8457
8458 /*
8459  * Button procedures
8460  */
8461 void
8462 Reset(redraw, init)
8463      int redraw, init;
8464 {
8465     int i;
8466
8467     if (appData.debugMode) {
8468         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8469                 redraw, init, gameMode);
8470     }
8471     pausing = pauseExamInvalid = FALSE;
8472     startedFromSetupPosition = blackPlaysFirst = FALSE;
8473     firstMove = TRUE;
8474     whiteFlag = blackFlag = FALSE;
8475     userOfferedDraw = FALSE;
8476     hintRequested = bookRequested = FALSE;
8477     first.maybeThinking = FALSE;
8478     second.maybeThinking = FALSE;
8479     first.bookSuspend = FALSE; // [HGM] book
8480     second.bookSuspend = FALSE;
8481     thinkOutput[0] = NULLCHAR;
8482     lastHint[0] = NULLCHAR;
8483     ClearGameInfo(&gameInfo);
8484     gameInfo.variant = StringToVariant(appData.variant);
8485     ics_user_moved = ics_clock_paused = FALSE;
8486     ics_getting_history = H_FALSE;
8487     ics_gamenum = -1;
8488     white_holding[0] = black_holding[0] = NULLCHAR;
8489     ClearProgramStats();
8490     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8491     
8492     ResetFrontEnd();
8493     ClearHighlights();
8494     flipView = appData.flipView;
8495     ClearPremoveHighlights();
8496     gotPremove = FALSE;
8497     alarmSounded = FALSE;
8498
8499     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8500     if(appData.serverMovesName != NULL) {
8501         /* [HGM] prepare to make moves file for broadcasting */
8502         clock_t t = clock();
8503         if(serverMoves != NULL) fclose(serverMoves);
8504         serverMoves = fopen(appData.serverMovesName, "r");
8505         if(serverMoves != NULL) {
8506             fclose(serverMoves);
8507             /* delay 15 sec before overwriting, so all clients can see end */
8508             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8509         }
8510         serverMoves = fopen(appData.serverMovesName, "w");
8511     }
8512
8513     ExitAnalyzeMode();
8514     gameMode = BeginningOfGame;
8515     ModeHighlight();
8516     if(appData.icsActive) gameInfo.variant = VariantNormal;
8517     currentMove = forwardMostMove = backwardMostMove = 0;
8518     InitPosition(redraw);
8519     for (i = 0; i < MAX_MOVES; i++) {
8520         if (commentList[i] != NULL) {
8521             free(commentList[i]);
8522             commentList[i] = NULL;
8523         }
8524     }
8525     ResetClocks();
8526     timeRemaining[0][0] = whiteTimeRemaining;
8527     timeRemaining[1][0] = blackTimeRemaining;
8528     if (first.pr == NULL) {
8529         StartChessProgram(&first);
8530     }
8531     if (init) {
8532             InitChessProgram(&first, startedFromSetupPosition);
8533     }
8534     DisplayTitle("");
8535     DisplayMessage("", "");
8536     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8537     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8538 }
8539
8540 void
8541 AutoPlayGameLoop()
8542 {
8543     for (;;) {
8544         if (!AutoPlayOneMove())
8545           return;
8546         if (matchMode || appData.timeDelay == 0)
8547           continue;
8548         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8549           return;
8550         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8551         break;
8552     }
8553 }
8554
8555
8556 int
8557 AutoPlayOneMove()
8558 {
8559     int fromX, fromY, toX, toY;
8560
8561     if (appData.debugMode) {
8562       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8563     }
8564
8565     if (gameMode != PlayFromGameFile)
8566       return FALSE;
8567
8568     if (currentMove >= forwardMostMove) {
8569       gameMode = EditGame;
8570       ModeHighlight();
8571
8572       /* [AS] Clear current move marker at the end of a game */
8573       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8574
8575       return FALSE;
8576     }
8577     
8578     toX = moveList[currentMove][2] - AAA;
8579     toY = moveList[currentMove][3] - ONE;
8580
8581     if (moveList[currentMove][1] == '@') {
8582         if (appData.highlightLastMove) {
8583             SetHighlights(-1, -1, toX, toY);
8584         }
8585     } else {
8586         fromX = moveList[currentMove][0] - AAA;
8587         fromY = moveList[currentMove][1] - ONE;
8588
8589         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8590
8591         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8592
8593         if (appData.highlightLastMove) {
8594             SetHighlights(fromX, fromY, toX, toY);
8595         }
8596     }
8597     DisplayMove(currentMove);
8598     SendMoveToProgram(currentMove++, &first);
8599     DisplayBothClocks();
8600     DrawPosition(FALSE, boards[currentMove]);
8601     // [HGM] PV info: always display, routine tests if empty
8602     DisplayComment(currentMove - 1, commentList[currentMove]);
8603     return TRUE;
8604 }
8605
8606
8607 int
8608 LoadGameOneMove(readAhead)
8609      ChessMove readAhead;
8610 {
8611     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8612     char promoChar = NULLCHAR;
8613     ChessMove moveType;
8614     char move[MSG_SIZ];
8615     char *p, *q;
8616     
8617     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8618         gameMode != AnalyzeMode && gameMode != Training) {
8619         gameFileFP = NULL;
8620         return FALSE;
8621     }
8622     
8623     yyboardindex = forwardMostMove;
8624     if (readAhead != (ChessMove)0) {
8625       moveType = readAhead;
8626     } else {
8627       if (gameFileFP == NULL)
8628           return FALSE;
8629       moveType = (ChessMove) yylex();
8630     }
8631     
8632     done = FALSE;
8633     switch (moveType) {
8634       case Comment:
8635         if (appData.debugMode) 
8636           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8637         p = yy_text;
8638         if (*p == '{' || *p == '[' || *p == '(') {
8639             p[strlen(p) - 1] = NULLCHAR;
8640             p++;
8641         }
8642
8643         /* append the comment but don't display it */
8644         while (*p == '\n') p++;
8645         AppendComment(currentMove, p);
8646         return TRUE;
8647
8648       case WhiteCapturesEnPassant:
8649       case BlackCapturesEnPassant:
8650       case WhitePromotionChancellor:
8651       case BlackPromotionChancellor:
8652       case WhitePromotionArchbishop:
8653       case BlackPromotionArchbishop:
8654       case WhitePromotionCentaur:
8655       case BlackPromotionCentaur:
8656       case WhitePromotionQueen:
8657       case BlackPromotionQueen:
8658       case WhitePromotionRook:
8659       case BlackPromotionRook:
8660       case WhitePromotionBishop:
8661       case BlackPromotionBishop:
8662       case WhitePromotionKnight:
8663       case BlackPromotionKnight:
8664       case WhitePromotionKing:
8665       case BlackPromotionKing:
8666       case NormalMove:
8667       case WhiteKingSideCastle:
8668       case WhiteQueenSideCastle:
8669       case BlackKingSideCastle:
8670       case BlackQueenSideCastle:
8671       case WhiteKingSideCastleWild:
8672       case WhiteQueenSideCastleWild:
8673       case BlackKingSideCastleWild:
8674       case BlackQueenSideCastleWild:
8675       /* PUSH Fabien */
8676       case WhiteHSideCastleFR:
8677       case WhiteASideCastleFR:
8678       case BlackHSideCastleFR:
8679       case BlackASideCastleFR:
8680       /* POP Fabien */
8681         if (appData.debugMode)
8682           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8683         fromX = currentMoveString[0] - AAA;
8684         fromY = currentMoveString[1] - ONE;
8685         toX = currentMoveString[2] - AAA;
8686         toY = currentMoveString[3] - ONE;
8687         promoChar = currentMoveString[4];
8688         break;
8689
8690       case WhiteDrop:
8691       case BlackDrop:
8692         if (appData.debugMode)
8693           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8694         fromX = moveType == WhiteDrop ?
8695           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8696         (int) CharToPiece(ToLower(currentMoveString[0]));
8697         fromY = DROP_RANK;
8698         toX = currentMoveString[2] - AAA;
8699         toY = currentMoveString[3] - ONE;
8700         break;
8701
8702       case WhiteWins:
8703       case BlackWins:
8704       case GameIsDrawn:
8705       case GameUnfinished:
8706         if (appData.debugMode)
8707           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8708         p = strchr(yy_text, '{');
8709         if (p == NULL) p = strchr(yy_text, '(');
8710         if (p == NULL) {
8711             p = yy_text;
8712             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8713         } else {
8714             q = strchr(p, *p == '{' ? '}' : ')');
8715             if (q != NULL) *q = NULLCHAR;
8716             p++;
8717         }
8718         GameEnds(moveType, p, GE_FILE);
8719         done = TRUE;
8720         if (cmailMsgLoaded) {
8721             ClearHighlights();
8722             flipView = WhiteOnMove(currentMove);
8723             if (moveType == GameUnfinished) flipView = !flipView;
8724             if (appData.debugMode)
8725               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8726         }
8727         break;
8728
8729       case (ChessMove) 0:       /* end of file */
8730         if (appData.debugMode)
8731           fprintf(debugFP, "Parser hit end of file\n");
8732         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8733           case MT_NONE:
8734           case MT_CHECK:
8735             break;
8736           case MT_CHECKMATE:
8737           case MT_STAINMATE:
8738             if (WhiteOnMove(currentMove)) {
8739                 GameEnds(BlackWins, "Black mates", GE_FILE);
8740             } else {
8741                 GameEnds(WhiteWins, "White mates", GE_FILE);
8742             }
8743             break;
8744           case MT_STALEMATE:
8745             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8746             break;
8747         }
8748         done = TRUE;
8749         break;
8750
8751       case MoveNumberOne:
8752         if (lastLoadGameStart == GNUChessGame) {
8753             /* GNUChessGames have numbers, but they aren't move numbers */
8754             if (appData.debugMode)
8755               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8756                       yy_text, (int) moveType);
8757             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8758         }
8759         /* else fall thru */
8760
8761       case XBoardGame:
8762       case GNUChessGame:
8763       case PGNTag:
8764         /* Reached start of next game in file */
8765         if (appData.debugMode)
8766           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8767         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8768           case MT_NONE:
8769           case MT_CHECK:
8770             break;
8771           case MT_CHECKMATE:
8772           case MT_STAINMATE:
8773             if (WhiteOnMove(currentMove)) {
8774                 GameEnds(BlackWins, "Black mates", GE_FILE);
8775             } else {
8776                 GameEnds(WhiteWins, "White mates", GE_FILE);
8777             }
8778             break;
8779           case MT_STALEMATE:
8780             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8781             break;
8782         }
8783         done = TRUE;
8784         break;
8785
8786       case PositionDiagram:     /* should not happen; ignore */
8787       case ElapsedTime:         /* ignore */
8788       case NAG:                 /* ignore */
8789         if (appData.debugMode)
8790           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8791                   yy_text, (int) moveType);
8792         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8793
8794       case IllegalMove:
8795         if (appData.testLegality) {
8796             if (appData.debugMode)
8797               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8798             sprintf(move, _("Illegal move: %d.%s%s"),
8799                     (forwardMostMove / 2) + 1,
8800                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8801             DisplayError(move, 0);
8802             done = TRUE;
8803         } else {
8804             if (appData.debugMode)
8805               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8806                       yy_text, currentMoveString);
8807             fromX = currentMoveString[0] - AAA;
8808             fromY = currentMoveString[1] - ONE;
8809             toX = currentMoveString[2] - AAA;
8810             toY = currentMoveString[3] - ONE;
8811             promoChar = currentMoveString[4];
8812         }
8813         break;
8814
8815       case AmbiguousMove:
8816         if (appData.debugMode)
8817           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8818         sprintf(move, _("Ambiguous move: %d.%s%s"),
8819                 (forwardMostMove / 2) + 1,
8820                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8821         DisplayError(move, 0);
8822         done = TRUE;
8823         break;
8824
8825       default:
8826       case ImpossibleMove:
8827         if (appData.debugMode)
8828           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8829         sprintf(move, _("Illegal move: %d.%s%s"),
8830                 (forwardMostMove / 2) + 1,
8831                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8832         DisplayError(move, 0);
8833         done = TRUE;
8834         break;
8835     }
8836
8837     if (done) {
8838         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8839             DrawPosition(FALSE, boards[currentMove]);
8840             DisplayBothClocks();
8841             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8842               DisplayComment(currentMove - 1, commentList[currentMove]);
8843         }
8844         (void) StopLoadGameTimer();
8845         gameFileFP = NULL;
8846         cmailOldMove = forwardMostMove;
8847         return FALSE;
8848     } else {
8849         /* currentMoveString is set as a side-effect of yylex */
8850         strcat(currentMoveString, "\n");
8851         strcpy(moveList[forwardMostMove], currentMoveString);
8852         
8853         thinkOutput[0] = NULLCHAR;
8854         MakeMove(fromX, fromY, toX, toY, promoChar);
8855         currentMove = forwardMostMove;
8856         return TRUE;
8857     }
8858 }
8859
8860 /* Load the nth game from the given file */
8861 int
8862 LoadGameFromFile(filename, n, title, useList)
8863      char *filename;
8864      int n;
8865      char *title;
8866      /*Boolean*/ int useList;
8867 {
8868     FILE *f;
8869     char buf[MSG_SIZ];
8870
8871     if (strcmp(filename, "-") == 0) {
8872         f = stdin;
8873         title = "stdin";
8874     } else {
8875         f = fopen(filename, "rb");
8876         if (f == NULL) {
8877           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8878             DisplayError(buf, errno);
8879             return FALSE;
8880         }
8881     }
8882     if (fseek(f, 0, 0) == -1) {
8883         /* f is not seekable; probably a pipe */
8884         useList = FALSE;
8885     }
8886     if (useList && n == 0) {
8887         int error = GameListBuild(f);
8888         if (error) {
8889             DisplayError(_("Cannot build game list"), error);
8890         } else if (!ListEmpty(&gameList) &&
8891                    ((ListGame *) gameList.tailPred)->number > 1) {
8892             GameListPopUp(f, title);
8893             return TRUE;
8894         }
8895         GameListDestroy();
8896         n = 1;
8897     }
8898     if (n == 0) n = 1;
8899     return LoadGame(f, n, title, FALSE);
8900 }
8901
8902
8903 void
8904 MakeRegisteredMove()
8905 {
8906     int fromX, fromY, toX, toY;
8907     char promoChar;
8908     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8909         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8910           case CMAIL_MOVE:
8911           case CMAIL_DRAW:
8912             if (appData.debugMode)
8913               fprintf(debugFP, "Restoring %s for game %d\n",
8914                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8915     
8916             thinkOutput[0] = NULLCHAR;
8917             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8918             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8919             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8920             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8921             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8922             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8923             MakeMove(fromX, fromY, toX, toY, promoChar);
8924             ShowMove(fromX, fromY, toX, toY);
8925               
8926             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8927               case MT_NONE:
8928               case MT_CHECK:
8929                 break;
8930                 
8931               case MT_CHECKMATE:
8932               case MT_STAINMATE:
8933                 if (WhiteOnMove(currentMove)) {
8934                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8935                 } else {
8936                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8937                 }
8938                 break;
8939                 
8940               case MT_STALEMATE:
8941                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8942                 break;
8943             }
8944
8945             break;
8946             
8947           case CMAIL_RESIGN:
8948             if (WhiteOnMove(currentMove)) {
8949                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8950             } else {
8951                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8952             }
8953             break;
8954             
8955           case CMAIL_ACCEPT:
8956             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8957             break;
8958               
8959           default:
8960             break;
8961         }
8962     }
8963
8964     return;
8965 }
8966
8967 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8968 int
8969 CmailLoadGame(f, gameNumber, title, useList)
8970      FILE *f;
8971      int gameNumber;
8972      char *title;
8973      int useList;
8974 {
8975     int retVal;
8976
8977     if (gameNumber > nCmailGames) {
8978         DisplayError(_("No more games in this message"), 0);
8979         return FALSE;
8980     }
8981     if (f == lastLoadGameFP) {
8982         int offset = gameNumber - lastLoadGameNumber;
8983         if (offset == 0) {
8984             cmailMsg[0] = NULLCHAR;
8985             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8986                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8987                 nCmailMovesRegistered--;
8988             }
8989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8990             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8991                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8992             }
8993         } else {
8994             if (! RegisterMove()) return FALSE;
8995         }
8996     }
8997
8998     retVal = LoadGame(f, gameNumber, title, useList);
8999
9000     /* Make move registered during previous look at this game, if any */
9001     MakeRegisteredMove();
9002
9003     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9004         commentList[currentMove]
9005           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9006         DisplayComment(currentMove - 1, commentList[currentMove]);
9007     }
9008
9009     return retVal;
9010 }
9011
9012 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9013 int
9014 ReloadGame(offset)
9015      int offset;
9016 {
9017     int gameNumber = lastLoadGameNumber + offset;
9018     if (lastLoadGameFP == NULL) {
9019         DisplayError(_("No game has been loaded yet"), 0);
9020         return FALSE;
9021     }
9022     if (gameNumber <= 0) {
9023         DisplayError(_("Can't back up any further"), 0);
9024         return FALSE;
9025     }
9026     if (cmailMsgLoaded) {
9027         return CmailLoadGame(lastLoadGameFP, gameNumber,
9028                              lastLoadGameTitle, lastLoadGameUseList);
9029     } else {
9030         return LoadGame(lastLoadGameFP, gameNumber,
9031                         lastLoadGameTitle, lastLoadGameUseList);
9032     }
9033 }
9034
9035
9036
9037 /* Load the nth game from open file f */
9038 int
9039 LoadGame(f, gameNumber, title, useList)
9040      FILE *f;
9041      int gameNumber;
9042      char *title;
9043      int useList;
9044 {
9045     ChessMove cm;
9046     char buf[MSG_SIZ];
9047     int gn = gameNumber;
9048     ListGame *lg = NULL;
9049     int numPGNTags = 0;
9050     int err;
9051     GameMode oldGameMode;
9052     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9053
9054     if (appData.debugMode) 
9055         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9056
9057     if (gameMode == Training )
9058         SetTrainingModeOff();
9059
9060     oldGameMode = gameMode;
9061     if (gameMode != BeginningOfGame) {
9062       Reset(FALSE, TRUE);
9063     }
9064
9065     gameFileFP = f;
9066     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9067         fclose(lastLoadGameFP);
9068     }
9069
9070     if (useList) {
9071         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9072         
9073         if (lg) {
9074             fseek(f, lg->offset, 0);
9075             GameListHighlight(gameNumber);
9076             gn = 1;
9077         }
9078         else {
9079             DisplayError(_("Game number out of range"), 0);
9080             return FALSE;
9081         }
9082     } else {
9083         GameListDestroy();
9084         if (fseek(f, 0, 0) == -1) {
9085             if (f == lastLoadGameFP ?
9086                 gameNumber == lastLoadGameNumber + 1 :
9087                 gameNumber == 1) {
9088                 gn = 1;
9089             } else {
9090                 DisplayError(_("Can't seek on game file"), 0);
9091                 return FALSE;
9092             }
9093         }
9094     }
9095     lastLoadGameFP = f;
9096     lastLoadGameNumber = gameNumber;
9097     strcpy(lastLoadGameTitle, title);
9098     lastLoadGameUseList = useList;
9099
9100     yynewfile(f);
9101
9102     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9103       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9104                 lg->gameInfo.black);
9105             DisplayTitle(buf);
9106     } else if (*title != NULLCHAR) {
9107         if (gameNumber > 1) {
9108             sprintf(buf, "%s %d", title, gameNumber);
9109             DisplayTitle(buf);
9110         } else {
9111             DisplayTitle(title);
9112         }
9113     }
9114
9115     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9116         gameMode = PlayFromGameFile;
9117         ModeHighlight();
9118     }
9119
9120     currentMove = forwardMostMove = backwardMostMove = 0;
9121     CopyBoard(boards[0], initialPosition);
9122     StopClocks();
9123
9124     /*
9125      * Skip the first gn-1 games in the file.
9126      * Also skip over anything that precedes an identifiable 
9127      * start of game marker, to avoid being confused by 
9128      * garbage at the start of the file.  Currently 
9129      * recognized start of game markers are the move number "1",
9130      * the pattern "gnuchess .* game", the pattern
9131      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9132      * A game that starts with one of the latter two patterns
9133      * will also have a move number 1, possibly
9134      * following a position diagram.
9135      * 5-4-02: Let's try being more lenient and allowing a game to
9136      * start with an unnumbered move.  Does that break anything?
9137      */
9138     cm = lastLoadGameStart = (ChessMove) 0;
9139     while (gn > 0) {
9140         yyboardindex = forwardMostMove;
9141         cm = (ChessMove) yylex();
9142         switch (cm) {
9143           case (ChessMove) 0:
9144             if (cmailMsgLoaded) {
9145                 nCmailGames = CMAIL_MAX_GAMES - gn;
9146             } else {
9147                 Reset(TRUE, TRUE);
9148                 DisplayError(_("Game not found in file"), 0);
9149             }
9150             return FALSE;
9151
9152           case GNUChessGame:
9153           case XBoardGame:
9154             gn--;
9155             lastLoadGameStart = cm;
9156             break;
9157             
9158           case MoveNumberOne:
9159             switch (lastLoadGameStart) {
9160               case GNUChessGame:
9161               case XBoardGame:
9162               case PGNTag:
9163                 break;
9164               case MoveNumberOne:
9165               case (ChessMove) 0:
9166                 gn--;           /* count this game */
9167                 lastLoadGameStart = cm;
9168                 break;
9169               default:
9170                 /* impossible */
9171                 break;
9172             }
9173             break;
9174
9175           case PGNTag:
9176             switch (lastLoadGameStart) {
9177               case GNUChessGame:
9178               case PGNTag:
9179               case MoveNumberOne:
9180               case (ChessMove) 0:
9181                 gn--;           /* count this game */
9182                 lastLoadGameStart = cm;
9183                 break;
9184               case XBoardGame:
9185                 lastLoadGameStart = cm; /* game counted already */
9186                 break;
9187               default:
9188                 /* impossible */
9189                 break;
9190             }
9191             if (gn > 0) {
9192                 do {
9193                     yyboardindex = forwardMostMove;
9194                     cm = (ChessMove) yylex();
9195                 } while (cm == PGNTag || cm == Comment);
9196             }
9197             break;
9198
9199           case WhiteWins:
9200           case BlackWins:
9201           case GameIsDrawn:
9202             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9203                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9204                     != CMAIL_OLD_RESULT) {
9205                     nCmailResults ++ ;
9206                     cmailResult[  CMAIL_MAX_GAMES
9207                                 - gn - 1] = CMAIL_OLD_RESULT;
9208                 }
9209             }
9210             break;
9211
9212           case NormalMove:
9213             /* Only a NormalMove can be at the start of a game
9214              * without a position diagram. */
9215             if (lastLoadGameStart == (ChessMove) 0) {
9216               gn--;
9217               lastLoadGameStart = MoveNumberOne;
9218             }
9219             break;
9220
9221           default:
9222             break;
9223         }
9224     }
9225     
9226     if (appData.debugMode)
9227       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9228
9229     if (cm == XBoardGame) {
9230         /* Skip any header junk before position diagram and/or move 1 */
9231         for (;;) {
9232             yyboardindex = forwardMostMove;
9233             cm = (ChessMove) yylex();
9234
9235             if (cm == (ChessMove) 0 ||
9236                 cm == GNUChessGame || cm == XBoardGame) {
9237                 /* Empty game; pretend end-of-file and handle later */
9238                 cm = (ChessMove) 0;
9239                 break;
9240             }
9241
9242             if (cm == MoveNumberOne || cm == PositionDiagram ||
9243                 cm == PGNTag || cm == Comment)
9244               break;
9245         }
9246     } else if (cm == GNUChessGame) {
9247         if (gameInfo.event != NULL) {
9248             free(gameInfo.event);
9249         }
9250         gameInfo.event = StrSave(yy_text);
9251     }   
9252
9253     startedFromSetupPosition = FALSE;
9254     while (cm == PGNTag) {
9255         if (appData.debugMode) 
9256           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9257         err = ParsePGNTag(yy_text, &gameInfo);
9258         if (!err) numPGNTags++;
9259
9260         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9261         if(gameInfo.variant != oldVariant) {
9262             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9263             InitPosition(TRUE);
9264             oldVariant = gameInfo.variant;
9265             if (appData.debugMode) 
9266               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9267         }
9268
9269
9270         if (gameInfo.fen != NULL) {
9271           Board initial_position;
9272           startedFromSetupPosition = TRUE;
9273           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9274             Reset(TRUE, TRUE);
9275             DisplayError(_("Bad FEN position in file"), 0);
9276             return FALSE;
9277           }
9278           CopyBoard(boards[0], initial_position);
9279           if (blackPlaysFirst) {
9280             currentMove = forwardMostMove = backwardMostMove = 1;
9281             CopyBoard(boards[1], initial_position);
9282             strcpy(moveList[0], "");
9283             strcpy(parseList[0], "");
9284             timeRemaining[0][1] = whiteTimeRemaining;
9285             timeRemaining[1][1] = blackTimeRemaining;
9286             if (commentList[0] != NULL) {
9287               commentList[1] = commentList[0];
9288               commentList[0] = NULL;
9289             }
9290           } else {
9291             currentMove = forwardMostMove = backwardMostMove = 0;
9292           }
9293           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9294           {   int i;
9295               initialRulePlies = FENrulePlies;
9296               for( i=0; i< nrCastlingRights; i++ )
9297                   initialRights[i] = initial_position[CASTLING][i];
9298           }
9299           yyboardindex = forwardMostMove;
9300           free(gameInfo.fen);
9301           gameInfo.fen = NULL;
9302         }
9303
9304         yyboardindex = forwardMostMove;
9305         cm = (ChessMove) yylex();
9306
9307         /* Handle comments interspersed among the tags */
9308         while (cm == Comment) {
9309             char *p;
9310             if (appData.debugMode) 
9311               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9312             p = yy_text;
9313             if (*p == '{' || *p == '[' || *p == '(') {
9314                 p[strlen(p) - 1] = NULLCHAR;
9315                 p++;
9316             }
9317             while (*p == '\n') p++;
9318             AppendComment(currentMove, p);
9319             yyboardindex = forwardMostMove;
9320             cm = (ChessMove) yylex();
9321         }
9322     }
9323
9324     /* don't rely on existence of Event tag since if game was
9325      * pasted from clipboard the Event tag may not exist
9326      */
9327     if (numPGNTags > 0){
9328         char *tags;
9329         if (gameInfo.variant == VariantNormal) {
9330           gameInfo.variant = StringToVariant(gameInfo.event);
9331         }
9332         if (!matchMode) {
9333           if( appData.autoDisplayTags ) {
9334             tags = PGNTags(&gameInfo);
9335             TagsPopUp(tags, CmailMsg());
9336             free(tags);
9337           }
9338         }
9339     } else {
9340         /* Make something up, but don't display it now */
9341         SetGameInfo();
9342         TagsPopDown();
9343     }
9344
9345     if (cm == PositionDiagram) {
9346         int i, j;
9347         char *p;
9348         Board initial_position;
9349
9350         if (appData.debugMode)
9351           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9352
9353         if (!startedFromSetupPosition) {
9354             p = yy_text;
9355             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9356               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9357                 switch (*p) {
9358                   case '[':
9359                   case '-':
9360                   case ' ':
9361                   case '\t':
9362                   case '\n':
9363                   case '\r':
9364                     break;
9365                   default:
9366                     initial_position[i][j++] = CharToPiece(*p);
9367                     break;
9368                 }
9369             while (*p == ' ' || *p == '\t' ||
9370                    *p == '\n' || *p == '\r') p++;
9371         
9372             if (strncmp(p, "black", strlen("black"))==0)
9373               blackPlaysFirst = TRUE;
9374             else
9375               blackPlaysFirst = FALSE;
9376             startedFromSetupPosition = TRUE;
9377         
9378             CopyBoard(boards[0], initial_position);
9379             if (blackPlaysFirst) {
9380                 currentMove = forwardMostMove = backwardMostMove = 1;
9381                 CopyBoard(boards[1], initial_position);
9382                 strcpy(moveList[0], "");
9383                 strcpy(parseList[0], "");
9384                 timeRemaining[0][1] = whiteTimeRemaining;
9385                 timeRemaining[1][1] = blackTimeRemaining;
9386                 if (commentList[0] != NULL) {
9387                     commentList[1] = commentList[0];
9388                     commentList[0] = NULL;
9389                 }
9390             } else {
9391                 currentMove = forwardMostMove = backwardMostMove = 0;
9392             }
9393         }
9394         yyboardindex = forwardMostMove;
9395         cm = (ChessMove) yylex();
9396     }
9397
9398     if (first.pr == NoProc) {
9399         StartChessProgram(&first);
9400     }
9401     InitChessProgram(&first, FALSE);
9402     SendToProgram("force\n", &first);
9403     if (startedFromSetupPosition) {
9404         SendBoard(&first, forwardMostMove);
9405     if (appData.debugMode) {
9406         fprintf(debugFP, "Load Game\n");
9407     }
9408         DisplayBothClocks();
9409     }      
9410
9411     /* [HGM] server: flag to write setup moves in broadcast file as one */
9412     loadFlag = appData.suppressLoadMoves;
9413
9414     while (cm == Comment) {
9415         char *p;
9416         if (appData.debugMode) 
9417           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9418         p = yy_text;
9419         if (*p == '{' || *p == '[' || *p == '(') {
9420             p[strlen(p) - 1] = NULLCHAR;
9421             p++;
9422         }
9423         while (*p == '\n') p++;
9424         AppendComment(currentMove, p);
9425         yyboardindex = forwardMostMove;
9426         cm = (ChessMove) yylex();
9427     }
9428
9429     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9430         cm == WhiteWins || cm == BlackWins ||
9431         cm == GameIsDrawn || cm == GameUnfinished) {
9432         DisplayMessage("", _("No moves in game"));
9433         if (cmailMsgLoaded) {
9434             if (appData.debugMode)
9435               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9436             ClearHighlights();
9437             flipView = FALSE;
9438         }
9439         DrawPosition(FALSE, boards[currentMove]);
9440         DisplayBothClocks();
9441         gameMode = EditGame;
9442         ModeHighlight();
9443         gameFileFP = NULL;
9444         cmailOldMove = 0;
9445         return TRUE;
9446     }
9447
9448     // [HGM] PV info: routine tests if comment empty
9449     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9450         DisplayComment(currentMove - 1, commentList[currentMove]);
9451     }
9452     if (!matchMode && appData.timeDelay != 0) 
9453       DrawPosition(FALSE, boards[currentMove]);
9454
9455     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9456       programStats.ok_to_send = 1;
9457     }
9458
9459     /* if the first token after the PGN tags is a move
9460      * and not move number 1, retrieve it from the parser 
9461      */
9462     if (cm != MoveNumberOne)
9463         LoadGameOneMove(cm);
9464
9465     /* load the remaining moves from the file */
9466     while (LoadGameOneMove((ChessMove)0)) {
9467       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9468       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9469     }
9470
9471     /* rewind to the start of the game */
9472     currentMove = backwardMostMove;
9473
9474     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9475
9476     if (oldGameMode == AnalyzeFile ||
9477         oldGameMode == AnalyzeMode) {
9478       AnalyzeFileEvent();
9479     }
9480
9481     if (matchMode || appData.timeDelay == 0) {
9482       ToEndEvent();
9483       gameMode = EditGame;
9484       ModeHighlight();
9485     } else if (appData.timeDelay > 0) {
9486       AutoPlayGameLoop();
9487     }
9488
9489     if (appData.debugMode) 
9490         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9491
9492     loadFlag = 0; /* [HGM] true game starts */
9493     return TRUE;
9494 }
9495
9496 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9497 int
9498 ReloadPosition(offset)
9499      int offset;
9500 {
9501     int positionNumber = lastLoadPositionNumber + offset;
9502     if (lastLoadPositionFP == NULL) {
9503         DisplayError(_("No position has been loaded yet"), 0);
9504         return FALSE;
9505     }
9506     if (positionNumber <= 0) {
9507         DisplayError(_("Can't back up any further"), 0);
9508         return FALSE;
9509     }
9510     return LoadPosition(lastLoadPositionFP, positionNumber,
9511                         lastLoadPositionTitle);
9512 }
9513
9514 /* Load the nth position from the given file */
9515 int
9516 LoadPositionFromFile(filename, n, title)
9517      char *filename;
9518      int n;
9519      char *title;
9520 {
9521     FILE *f;
9522     char buf[MSG_SIZ];
9523
9524     if (strcmp(filename, "-") == 0) {
9525         return LoadPosition(stdin, n, "stdin");
9526     } else {
9527         f = fopen(filename, "rb");
9528         if (f == NULL) {
9529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9530             DisplayError(buf, errno);
9531             return FALSE;
9532         } else {
9533             return LoadPosition(f, n, title);
9534         }
9535     }
9536 }
9537
9538 /* Load the nth position from the given open file, and close it */
9539 int
9540 LoadPosition(f, positionNumber, title)
9541      FILE *f;
9542      int positionNumber;
9543      char *title;
9544 {
9545     char *p, line[MSG_SIZ];
9546     Board initial_position;
9547     int i, j, fenMode, pn;
9548     
9549     if (gameMode == Training )
9550         SetTrainingModeOff();
9551
9552     if (gameMode != BeginningOfGame) {
9553         Reset(FALSE, TRUE);
9554     }
9555     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9556         fclose(lastLoadPositionFP);
9557     }
9558     if (positionNumber == 0) positionNumber = 1;
9559     lastLoadPositionFP = f;
9560     lastLoadPositionNumber = positionNumber;
9561     strcpy(lastLoadPositionTitle, title);
9562     if (first.pr == NoProc) {
9563       StartChessProgram(&first);
9564       InitChessProgram(&first, FALSE);
9565     }    
9566     pn = positionNumber;
9567     if (positionNumber < 0) {
9568         /* Negative position number means to seek to that byte offset */
9569         if (fseek(f, -positionNumber, 0) == -1) {
9570             DisplayError(_("Can't seek on position file"), 0);
9571             return FALSE;
9572         };
9573         pn = 1;
9574     } else {
9575         if (fseek(f, 0, 0) == -1) {
9576             if (f == lastLoadPositionFP ?
9577                 positionNumber == lastLoadPositionNumber + 1 :
9578                 positionNumber == 1) {
9579                 pn = 1;
9580             } else {
9581                 DisplayError(_("Can't seek on position file"), 0);
9582                 return FALSE;
9583             }
9584         }
9585     }
9586     /* See if this file is FEN or old-style xboard */
9587     if (fgets(line, MSG_SIZ, f) == NULL) {
9588         DisplayError(_("Position not found in file"), 0);
9589         return FALSE;
9590     }
9591     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9592     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9593
9594     if (pn >= 2) {
9595         if (fenMode || line[0] == '#') pn--;
9596         while (pn > 0) {
9597             /* skip positions before number pn */
9598             if (fgets(line, MSG_SIZ, f) == NULL) {
9599                 Reset(TRUE, TRUE);
9600                 DisplayError(_("Position not found in file"), 0);
9601                 return FALSE;
9602             }
9603             if (fenMode || line[0] == '#') pn--;
9604         }
9605     }
9606
9607     if (fenMode) {
9608         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9609             DisplayError(_("Bad FEN position in file"), 0);
9610             return FALSE;
9611         }
9612     } else {
9613         (void) fgets(line, MSG_SIZ, f);
9614         (void) fgets(line, MSG_SIZ, f);
9615     
9616         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9617             (void) fgets(line, MSG_SIZ, f);
9618             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9619                 if (*p == ' ')
9620                   continue;
9621                 initial_position[i][j++] = CharToPiece(*p);
9622             }
9623         }
9624     
9625         blackPlaysFirst = FALSE;
9626         if (!feof(f)) {
9627             (void) fgets(line, MSG_SIZ, f);
9628             if (strncmp(line, "black", strlen("black"))==0)
9629               blackPlaysFirst = TRUE;
9630         }
9631     }
9632     startedFromSetupPosition = TRUE;
9633     
9634     SendToProgram("force\n", &first);
9635     CopyBoard(boards[0], initial_position);
9636     if (blackPlaysFirst) {
9637         currentMove = forwardMostMove = backwardMostMove = 1;
9638         strcpy(moveList[0], "");
9639         strcpy(parseList[0], "");
9640         CopyBoard(boards[1], initial_position);
9641         DisplayMessage("", _("Black to play"));
9642     } else {
9643         currentMove = forwardMostMove = backwardMostMove = 0;
9644         DisplayMessage("", _("White to play"));
9645     }
9646     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9647     SendBoard(&first, forwardMostMove);
9648     if (appData.debugMode) {
9649 int i, j;
9650   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9651   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9652         fprintf(debugFP, "Load Position\n");
9653     }
9654
9655     if (positionNumber > 1) {
9656         sprintf(line, "%s %d", title, positionNumber);
9657         DisplayTitle(line);
9658     } else {
9659         DisplayTitle(title);
9660     }
9661     gameMode = EditGame;
9662     ModeHighlight();
9663     ResetClocks();
9664     timeRemaining[0][1] = whiteTimeRemaining;
9665     timeRemaining[1][1] = blackTimeRemaining;
9666     DrawPosition(FALSE, boards[currentMove]);
9667    
9668     return TRUE;
9669 }
9670
9671
9672 void
9673 CopyPlayerNameIntoFileName(dest, src)
9674      char **dest, *src;
9675 {
9676     while (*src != NULLCHAR && *src != ',') {
9677         if (*src == ' ') {
9678             *(*dest)++ = '_';
9679             src++;
9680         } else {
9681             *(*dest)++ = *src++;
9682         }
9683     }
9684 }
9685
9686 char *DefaultFileName(ext)
9687      char *ext;
9688 {
9689     static char def[MSG_SIZ];
9690     char *p;
9691
9692     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9693         p = def;
9694         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9695         *p++ = '-';
9696         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9697         *p++ = '.';
9698         strcpy(p, ext);
9699     } else {
9700         def[0] = NULLCHAR;
9701     }
9702     return def;
9703 }
9704
9705 /* Save the current game to the given file */
9706 int
9707 SaveGameToFile(filename, append)
9708      char *filename;
9709      int append;
9710 {
9711     FILE *f;
9712     char buf[MSG_SIZ];
9713
9714     if (strcmp(filename, "-") == 0) {
9715         return SaveGame(stdout, 0, NULL);
9716     } else {
9717         f = fopen(filename, append ? "a" : "w");
9718         if (f == NULL) {
9719             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9720             DisplayError(buf, errno);
9721             return FALSE;
9722         } else {
9723             return SaveGame(f, 0, NULL);
9724         }
9725     }
9726 }
9727
9728 char *
9729 SavePart(str)
9730      char *str;
9731 {
9732     static char buf[MSG_SIZ];
9733     char *p;
9734     
9735     p = strchr(str, ' ');
9736     if (p == NULL) return str;
9737     strncpy(buf, str, p - str);
9738     buf[p - str] = NULLCHAR;
9739     return buf;
9740 }
9741
9742 #define PGN_MAX_LINE 75
9743
9744 #define PGN_SIDE_WHITE  0
9745 #define PGN_SIDE_BLACK  1
9746
9747 /* [AS] */
9748 static int FindFirstMoveOutOfBook( int side )
9749 {
9750     int result = -1;
9751
9752     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9753         int index = backwardMostMove;
9754         int has_book_hit = 0;
9755
9756         if( (index % 2) != side ) {
9757             index++;
9758         }
9759
9760         while( index < forwardMostMove ) {
9761             /* Check to see if engine is in book */
9762             int depth = pvInfoList[index].depth;
9763             int score = pvInfoList[index].score;
9764             int in_book = 0;
9765
9766             if( depth <= 2 ) {
9767                 in_book = 1;
9768             }
9769             else if( score == 0 && depth == 63 ) {
9770                 in_book = 1; /* Zappa */
9771             }
9772             else if( score == 2 && depth == 99 ) {
9773                 in_book = 1; /* Abrok */
9774             }
9775
9776             has_book_hit += in_book;
9777
9778             if( ! in_book ) {
9779                 result = index;
9780
9781                 break;
9782             }
9783
9784             index += 2;
9785         }
9786     }
9787
9788     return result;
9789 }
9790
9791 /* [AS] */
9792 void GetOutOfBookInfo( char * buf )
9793 {
9794     int oob[2];
9795     int i;
9796     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9797
9798     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9799     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9800
9801     *buf = '\0';
9802
9803     if( oob[0] >= 0 || oob[1] >= 0 ) {
9804         for( i=0; i<2; i++ ) {
9805             int idx = oob[i];
9806
9807             if( idx >= 0 ) {
9808                 if( i > 0 && oob[0] >= 0 ) {
9809                     strcat( buf, "   " );
9810                 }
9811
9812                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9813                 sprintf( buf+strlen(buf), "%s%.2f", 
9814                     pvInfoList[idx].score >= 0 ? "+" : "",
9815                     pvInfoList[idx].score / 100.0 );
9816             }
9817         }
9818     }
9819 }
9820
9821 /* Save game in PGN style and close the file */
9822 int
9823 SaveGamePGN(f)
9824      FILE *f;
9825 {
9826     int i, offset, linelen, newblock;
9827     time_t tm;
9828 //    char *movetext;
9829     char numtext[32];
9830     int movelen, numlen, blank;
9831     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9832
9833     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9834     
9835     tm = time((time_t *) NULL);
9836     
9837     PrintPGNTags(f, &gameInfo);
9838     
9839     if (backwardMostMove > 0 || startedFromSetupPosition) {
9840         char *fen = PositionToFEN(backwardMostMove, NULL);
9841         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9842         fprintf(f, "\n{--------------\n");
9843         PrintPosition(f, backwardMostMove);
9844         fprintf(f, "--------------}\n");
9845         free(fen);
9846     }
9847     else {
9848         /* [AS] Out of book annotation */
9849         if( appData.saveOutOfBookInfo ) {
9850             char buf[64];
9851
9852             GetOutOfBookInfo( buf );
9853
9854             if( buf[0] != '\0' ) {
9855                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9856             }
9857         }
9858
9859         fprintf(f, "\n");
9860     }
9861
9862     i = backwardMostMove;
9863     linelen = 0;
9864     newblock = TRUE;
9865
9866     while (i < forwardMostMove) {
9867         /* Print comments preceding this move */
9868         if (commentList[i] != NULL) {
9869             if (linelen > 0) fprintf(f, "\n");
9870             fprintf(f, "{\n%s}\n", commentList[i]);
9871             linelen = 0;
9872             newblock = TRUE;
9873         }
9874
9875         /* Format move number */
9876         if ((i % 2) == 0) {
9877             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9878         } else {
9879             if (newblock) {
9880                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9881             } else {
9882                 numtext[0] = NULLCHAR;
9883             }
9884         }
9885         numlen = strlen(numtext);
9886         newblock = FALSE;
9887
9888         /* Print move number */
9889         blank = linelen > 0 && numlen > 0;
9890         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9891             fprintf(f, "\n");
9892             linelen = 0;
9893             blank = 0;
9894         }
9895         if (blank) {
9896             fprintf(f, " ");
9897             linelen++;
9898         }
9899         fprintf(f, "%s", numtext);
9900         linelen += numlen;
9901
9902         /* Get move */
9903         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9904         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9905
9906         /* Print move */
9907         blank = linelen > 0 && movelen > 0;
9908         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9909             fprintf(f, "\n");
9910             linelen = 0;
9911             blank = 0;
9912         }
9913         if (blank) {
9914             fprintf(f, " ");
9915             linelen++;
9916         }
9917         fprintf(f, "%s", move_buffer);
9918         linelen += movelen;
9919
9920         /* [AS] Add PV info if present */
9921         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9922             /* [HGM] add time */
9923             char buf[MSG_SIZ]; int seconds = 0;
9924
9925             if(i >= backwardMostMove) {
9926                 if(WhiteOnMove(i))
9927                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9928                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9929                 else
9930                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9931                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9932             }
9933             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9934
9935             if( seconds <= 0) buf[0] = 0; else
9936             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9937                 seconds = (seconds + 4)/10; // round to full seconds
9938                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9939                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9940             }
9941
9942             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9943                 pvInfoList[i].score >= 0 ? "+" : "",
9944                 pvInfoList[i].score / 100.0,
9945                 pvInfoList[i].depth,
9946                 buf );
9947
9948             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9949
9950             /* Print score/depth */
9951             blank = linelen > 0 && movelen > 0;
9952             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9953                 fprintf(f, "\n");
9954                 linelen = 0;
9955                 blank = 0;
9956             }
9957             if (blank) {
9958                 fprintf(f, " ");
9959                 linelen++;
9960             }
9961             fprintf(f, "%s", move_buffer);
9962             linelen += movelen;
9963         }
9964
9965         i++;
9966     }
9967     
9968     /* Start a new line */
9969     if (linelen > 0) fprintf(f, "\n");
9970
9971     /* Print comments after last move */
9972     if (commentList[i] != NULL) {
9973         fprintf(f, "{\n%s}\n", commentList[i]);
9974     }
9975
9976     /* Print result */
9977     if (gameInfo.resultDetails != NULL &&
9978         gameInfo.resultDetails[0] != NULLCHAR) {
9979         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9980                 PGNResult(gameInfo.result));
9981     } else {
9982         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9983     }
9984
9985     fclose(f);
9986     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9987     return TRUE;
9988 }
9989
9990 /* Save game in old style and close the file */
9991 int
9992 SaveGameOldStyle(f)
9993      FILE *f;
9994 {
9995     int i, offset;
9996     time_t tm;
9997     
9998     tm = time((time_t *) NULL);
9999     
10000     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10001     PrintOpponents(f);
10002     
10003     if (backwardMostMove > 0 || startedFromSetupPosition) {
10004         fprintf(f, "\n[--------------\n");
10005         PrintPosition(f, backwardMostMove);
10006         fprintf(f, "--------------]\n");
10007     } else {
10008         fprintf(f, "\n");
10009     }
10010
10011     i = backwardMostMove;
10012     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10013
10014     while (i < forwardMostMove) {
10015         if (commentList[i] != NULL) {
10016             fprintf(f, "[%s]\n", commentList[i]);
10017         }
10018
10019         if ((i % 2) == 1) {
10020             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10021             i++;
10022         } else {
10023             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10024             i++;
10025             if (commentList[i] != NULL) {
10026                 fprintf(f, "\n");
10027                 continue;
10028             }
10029             if (i >= forwardMostMove) {
10030                 fprintf(f, "\n");
10031                 break;
10032             }
10033             fprintf(f, "%s\n", parseList[i]);
10034             i++;
10035         }
10036     }
10037     
10038     if (commentList[i] != NULL) {
10039         fprintf(f, "[%s]\n", commentList[i]);
10040     }
10041
10042     /* This isn't really the old style, but it's close enough */
10043     if (gameInfo.resultDetails != NULL &&
10044         gameInfo.resultDetails[0] != NULLCHAR) {
10045         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10046                 gameInfo.resultDetails);
10047     } else {
10048         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10049     }
10050
10051     fclose(f);
10052     return TRUE;
10053 }
10054
10055 /* Save the current game to open file f and close the file */
10056 int
10057 SaveGame(f, dummy, dummy2)
10058      FILE *f;
10059      int dummy;
10060      char *dummy2;
10061 {
10062     if (gameMode == EditPosition) EditPositionDone(TRUE);
10063     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10064     if (appData.oldSaveStyle)
10065       return SaveGameOldStyle(f);
10066     else
10067       return SaveGamePGN(f);
10068 }
10069
10070 /* Save the current position to the given file */
10071 int
10072 SavePositionToFile(filename)
10073      char *filename;
10074 {
10075     FILE *f;
10076     char buf[MSG_SIZ];
10077
10078     if (strcmp(filename, "-") == 0) {
10079         return SavePosition(stdout, 0, NULL);
10080     } else {
10081         f = fopen(filename, "a");
10082         if (f == NULL) {
10083             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10084             DisplayError(buf, errno);
10085             return FALSE;
10086         } else {
10087             SavePosition(f, 0, NULL);
10088             return TRUE;
10089         }
10090     }
10091 }
10092
10093 /* Save the current position to the given open file and close the file */
10094 int
10095 SavePosition(f, dummy, dummy2)
10096      FILE *f;
10097      int dummy;
10098      char *dummy2;
10099 {
10100     time_t tm;
10101     char *fen;
10102     
10103     if (appData.oldSaveStyle) {
10104         tm = time((time_t *) NULL);
10105     
10106         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10107         PrintOpponents(f);
10108         fprintf(f, "[--------------\n");
10109         PrintPosition(f, currentMove);
10110         fprintf(f, "--------------]\n");
10111     } else {
10112         fen = PositionToFEN(currentMove, NULL);
10113         fprintf(f, "%s\n", fen);
10114         free(fen);
10115     }
10116     fclose(f);
10117     return TRUE;
10118 }
10119
10120 void
10121 ReloadCmailMsgEvent(unregister)
10122      int unregister;
10123 {
10124 #if !WIN32
10125     static char *inFilename = NULL;
10126     static char *outFilename;
10127     int i;
10128     struct stat inbuf, outbuf;
10129     int status;
10130     
10131     /* Any registered moves are unregistered if unregister is set, */
10132     /* i.e. invoked by the signal handler */
10133     if (unregister) {
10134         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10135             cmailMoveRegistered[i] = FALSE;
10136             if (cmailCommentList[i] != NULL) {
10137                 free(cmailCommentList[i]);
10138                 cmailCommentList[i] = NULL;
10139             }
10140         }
10141         nCmailMovesRegistered = 0;
10142     }
10143
10144     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10145         cmailResult[i] = CMAIL_NOT_RESULT;
10146     }
10147     nCmailResults = 0;
10148
10149     if (inFilename == NULL) {
10150         /* Because the filenames are static they only get malloced once  */
10151         /* and they never get freed                                      */
10152         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10153         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10154
10155         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10156         sprintf(outFilename, "%s.out", appData.cmailGameName);
10157     }
10158     
10159     status = stat(outFilename, &outbuf);
10160     if (status < 0) {
10161         cmailMailedMove = FALSE;
10162     } else {
10163         status = stat(inFilename, &inbuf);
10164         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10165     }
10166     
10167     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10168        counts the games, notes how each one terminated, etc.
10169        
10170        It would be nice to remove this kludge and instead gather all
10171        the information while building the game list.  (And to keep it
10172        in the game list nodes instead of having a bunch of fixed-size
10173        parallel arrays.)  Note this will require getting each game's
10174        termination from the PGN tags, as the game list builder does
10175        not process the game moves.  --mann
10176        */
10177     cmailMsgLoaded = TRUE;
10178     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10179     
10180     /* Load first game in the file or popup game menu */
10181     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10182
10183 #endif /* !WIN32 */
10184     return;
10185 }
10186
10187 int
10188 RegisterMove()
10189 {
10190     FILE *f;
10191     char string[MSG_SIZ];
10192
10193     if (   cmailMailedMove
10194         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10195         return TRUE;            /* Allow free viewing  */
10196     }
10197
10198     /* Unregister move to ensure that we don't leave RegisterMove        */
10199     /* with the move registered when the conditions for registering no   */
10200     /* longer hold                                                       */
10201     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10202         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10203         nCmailMovesRegistered --;
10204
10205         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10206           {
10207               free(cmailCommentList[lastLoadGameNumber - 1]);
10208               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10209           }
10210     }
10211
10212     if (cmailOldMove == -1) {
10213         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10214         return FALSE;
10215     }
10216
10217     if (currentMove > cmailOldMove + 1) {
10218         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10219         return FALSE;
10220     }
10221
10222     if (currentMove < cmailOldMove) {
10223         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10224         return FALSE;
10225     }
10226
10227     if (forwardMostMove > currentMove) {
10228         /* Silently truncate extra moves */
10229         TruncateGame();
10230     }
10231
10232     if (   (currentMove == cmailOldMove + 1)
10233         || (   (currentMove == cmailOldMove)
10234             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10235                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10236         if (gameInfo.result != GameUnfinished) {
10237             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10238         }
10239
10240         if (commentList[currentMove] != NULL) {
10241             cmailCommentList[lastLoadGameNumber - 1]
10242               = StrSave(commentList[currentMove]);
10243         }
10244         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10245
10246         if (appData.debugMode)
10247           fprintf(debugFP, "Saving %s for game %d\n",
10248                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10249
10250         sprintf(string,
10251                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10252         
10253         f = fopen(string, "w");
10254         if (appData.oldSaveStyle) {
10255             SaveGameOldStyle(f); /* also closes the file */
10256             
10257             sprintf(string, "%s.pos.out", appData.cmailGameName);
10258             f = fopen(string, "w");
10259             SavePosition(f, 0, NULL); /* also closes the file */
10260         } else {
10261             fprintf(f, "{--------------\n");
10262             PrintPosition(f, currentMove);
10263             fprintf(f, "--------------}\n\n");
10264             
10265             SaveGame(f, 0, NULL); /* also closes the file*/
10266         }
10267         
10268         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10269         nCmailMovesRegistered ++;
10270     } else if (nCmailGames == 1) {
10271         DisplayError(_("You have not made a move yet"), 0);
10272         return FALSE;
10273     }
10274
10275     return TRUE;
10276 }
10277
10278 void
10279 MailMoveEvent()
10280 {
10281 #if !WIN32
10282     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10283     FILE *commandOutput;
10284     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10285     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10286     int nBuffers;
10287     int i;
10288     int archived;
10289     char *arcDir;
10290
10291     if (! cmailMsgLoaded) {
10292         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10293         return;
10294     }
10295
10296     if (nCmailGames == nCmailResults) {
10297         DisplayError(_("No unfinished games"), 0);
10298         return;
10299     }
10300
10301 #if CMAIL_PROHIBIT_REMAIL
10302     if (cmailMailedMove) {
10303         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);
10304         DisplayError(msg, 0);
10305         return;
10306     }
10307 #endif
10308
10309     if (! (cmailMailedMove || RegisterMove())) return;
10310     
10311     if (   cmailMailedMove
10312         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10313         sprintf(string, partCommandString,
10314                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10315         commandOutput = popen(string, "r");
10316
10317         if (commandOutput == NULL) {
10318             DisplayError(_("Failed to invoke cmail"), 0);
10319         } else {
10320             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10321                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10322             }
10323             if (nBuffers > 1) {
10324                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10325                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10326                 nBytes = MSG_SIZ - 1;
10327             } else {
10328                 (void) memcpy(msg, buffer, nBytes);
10329             }
10330             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10331
10332             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10333                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10334
10335                 archived = TRUE;
10336                 for (i = 0; i < nCmailGames; i ++) {
10337                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10338                         archived = FALSE;
10339                     }
10340                 }
10341                 if (   archived
10342                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10343                         != NULL)) {
10344                     sprintf(buffer, "%s/%s.%s.archive",
10345                             arcDir,
10346                             appData.cmailGameName,
10347                             gameInfo.date);
10348                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10349                     cmailMsgLoaded = FALSE;
10350                 }
10351             }
10352
10353             DisplayInformation(msg);
10354             pclose(commandOutput);
10355         }
10356     } else {
10357         if ((*cmailMsg) != '\0') {
10358             DisplayInformation(cmailMsg);
10359         }
10360     }
10361
10362     return;
10363 #endif /* !WIN32 */
10364 }
10365
10366 char *
10367 CmailMsg()
10368 {
10369 #if WIN32
10370     return NULL;
10371 #else
10372     int  prependComma = 0;
10373     char number[5];
10374     char string[MSG_SIZ];       /* Space for game-list */
10375     int  i;
10376     
10377     if (!cmailMsgLoaded) return "";
10378
10379     if (cmailMailedMove) {
10380         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10381     } else {
10382         /* Create a list of games left */
10383         sprintf(string, "[");
10384         for (i = 0; i < nCmailGames; i ++) {
10385             if (! (   cmailMoveRegistered[i]
10386                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10387                 if (prependComma) {
10388                     sprintf(number, ",%d", i + 1);
10389                 } else {
10390                     sprintf(number, "%d", i + 1);
10391                     prependComma = 1;
10392                 }
10393                 
10394                 strcat(string, number);
10395             }
10396         }
10397         strcat(string, "]");
10398
10399         if (nCmailMovesRegistered + nCmailResults == 0) {
10400             switch (nCmailGames) {
10401               case 1:
10402                 sprintf(cmailMsg,
10403                         _("Still need to make move for game\n"));
10404                 break;
10405                 
10406               case 2:
10407                 sprintf(cmailMsg,
10408                         _("Still need to make moves for both games\n"));
10409                 break;
10410                 
10411               default:
10412                 sprintf(cmailMsg,
10413                         _("Still need to make moves for all %d games\n"),
10414                         nCmailGames);
10415                 break;
10416             }
10417         } else {
10418             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10419               case 1:
10420                 sprintf(cmailMsg,
10421                         _("Still need to make a move for game %s\n"),
10422                         string);
10423                 break;
10424                 
10425               case 0:
10426                 if (nCmailResults == nCmailGames) {
10427                     sprintf(cmailMsg, _("No unfinished games\n"));
10428                 } else {
10429                     sprintf(cmailMsg, _("Ready to send mail\n"));
10430                 }
10431                 break;
10432                 
10433               default:
10434                 sprintf(cmailMsg,
10435                         _("Still need to make moves for games %s\n"),
10436                         string);
10437             }
10438         }
10439     }
10440     return cmailMsg;
10441 #endif /* WIN32 */
10442 }
10443
10444 void
10445 ResetGameEvent()
10446 {
10447     if (gameMode == Training)
10448       SetTrainingModeOff();
10449
10450     Reset(TRUE, TRUE);
10451     cmailMsgLoaded = FALSE;
10452     if (appData.icsActive) {
10453       SendToICS(ics_prefix);
10454       SendToICS("refresh\n");
10455     }
10456 }
10457
10458 void
10459 ExitEvent(status)
10460      int status;
10461 {
10462     exiting++;
10463     if (exiting > 2) {
10464       /* Give up on clean exit */
10465       exit(status);
10466     }
10467     if (exiting > 1) {
10468       /* Keep trying for clean exit */
10469       return;
10470     }
10471
10472     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10473
10474     if (telnetISR != NULL) {
10475       RemoveInputSource(telnetISR);
10476     }
10477     if (icsPR != NoProc) {
10478       DestroyChildProcess(icsPR, TRUE);
10479     }
10480
10481     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10482     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10483
10484     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10485     /* make sure this other one finishes before killing it!                  */
10486     if(endingGame) { int count = 0;
10487         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10488         while(endingGame && count++ < 10) DoSleep(1);
10489         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10490     }
10491
10492     /* Kill off chess programs */
10493     if (first.pr != NoProc) {
10494         ExitAnalyzeMode();
10495         
10496         DoSleep( appData.delayBeforeQuit );
10497         SendToProgram("quit\n", &first);
10498         DoSleep( appData.delayAfterQuit );
10499         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10500     }
10501     if (second.pr != NoProc) {
10502         DoSleep( appData.delayBeforeQuit );
10503         SendToProgram("quit\n", &second);
10504         DoSleep( appData.delayAfterQuit );
10505         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10506     }
10507     if (first.isr != NULL) {
10508         RemoveInputSource(first.isr);
10509     }
10510     if (second.isr != NULL) {
10511         RemoveInputSource(second.isr);
10512     }
10513
10514     ShutDownFrontEnd();
10515     exit(status);
10516 }
10517
10518 void
10519 PauseEvent()
10520 {
10521     if (appData.debugMode)
10522         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10523     if (pausing) {
10524         pausing = FALSE;
10525         ModeHighlight();
10526         if (gameMode == MachinePlaysWhite ||
10527             gameMode == MachinePlaysBlack) {
10528             StartClocks();
10529         } else {
10530             DisplayBothClocks();
10531         }
10532         if (gameMode == PlayFromGameFile) {
10533             if (appData.timeDelay >= 0) 
10534                 AutoPlayGameLoop();
10535         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10536             Reset(FALSE, TRUE);
10537             SendToICS(ics_prefix);
10538             SendToICS("refresh\n");
10539         } else if (currentMove < forwardMostMove) {
10540             ForwardInner(forwardMostMove);
10541         }
10542         pauseExamInvalid = FALSE;
10543     } else {
10544         switch (gameMode) {
10545           default:
10546             return;
10547           case IcsExamining:
10548             pauseExamForwardMostMove = forwardMostMove;
10549             pauseExamInvalid = FALSE;
10550             /* fall through */
10551           case IcsObserving:
10552           case IcsPlayingWhite:
10553           case IcsPlayingBlack:
10554             pausing = TRUE;
10555             ModeHighlight();
10556             return;
10557           case PlayFromGameFile:
10558             (void) StopLoadGameTimer();
10559             pausing = TRUE;
10560             ModeHighlight();
10561             break;
10562           case BeginningOfGame:
10563             if (appData.icsActive) return;
10564             /* else fall through */
10565           case MachinePlaysWhite:
10566           case MachinePlaysBlack:
10567           case TwoMachinesPlay:
10568             if (forwardMostMove == 0)
10569               return;           /* don't pause if no one has moved */
10570             if ((gameMode == MachinePlaysWhite &&
10571                  !WhiteOnMove(forwardMostMove)) ||
10572                 (gameMode == MachinePlaysBlack &&
10573                  WhiteOnMove(forwardMostMove))) {
10574                 StopClocks();
10575             }
10576             pausing = TRUE;
10577             ModeHighlight();
10578             break;
10579         }
10580     }
10581 }
10582
10583 void
10584 EditCommentEvent()
10585 {
10586     char title[MSG_SIZ];
10587
10588     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10589         strcpy(title, _("Edit comment"));
10590     } else {
10591         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10592                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10593                 parseList[currentMove - 1]);
10594     }
10595
10596     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10597 }
10598
10599
10600 void
10601 EditTagsEvent()
10602 {
10603     char *tags = PGNTags(&gameInfo);
10604     EditTagsPopUp(tags);
10605     free(tags);
10606 }
10607
10608 void
10609 AnalyzeModeEvent()
10610 {
10611     if (appData.noChessProgram || gameMode == AnalyzeMode)
10612       return;
10613
10614     if (gameMode != AnalyzeFile) {
10615         if (!appData.icsEngineAnalyze) {
10616                EditGameEvent();
10617                if (gameMode != EditGame) return;
10618         }
10619         ResurrectChessProgram();
10620         SendToProgram("analyze\n", &first);
10621         first.analyzing = TRUE;
10622         /*first.maybeThinking = TRUE;*/
10623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10624         EngineOutputPopUp();
10625     }
10626     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10627     pausing = FALSE;
10628     ModeHighlight();
10629     SetGameInfo();
10630
10631     StartAnalysisClock();
10632     GetTimeMark(&lastNodeCountTime);
10633     lastNodeCount = 0;
10634 }
10635
10636 void
10637 AnalyzeFileEvent()
10638 {
10639     if (appData.noChessProgram || gameMode == AnalyzeFile)
10640       return;
10641
10642     if (gameMode != AnalyzeMode) {
10643         EditGameEvent();
10644         if (gameMode != EditGame) return;
10645         ResurrectChessProgram();
10646         SendToProgram("analyze\n", &first);
10647         first.analyzing = TRUE;
10648         /*first.maybeThinking = TRUE;*/
10649         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10650         EngineOutputPopUp();
10651     }
10652     gameMode = AnalyzeFile;
10653     pausing = FALSE;
10654     ModeHighlight();
10655     SetGameInfo();
10656
10657     StartAnalysisClock();
10658     GetTimeMark(&lastNodeCountTime);
10659     lastNodeCount = 0;
10660 }
10661
10662 void
10663 MachineWhiteEvent()
10664 {
10665     char buf[MSG_SIZ];
10666     char *bookHit = NULL;
10667
10668     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10669       return;
10670
10671
10672     if (gameMode == PlayFromGameFile || 
10673         gameMode == TwoMachinesPlay  || 
10674         gameMode == Training         || 
10675         gameMode == AnalyzeMode      || 
10676         gameMode == EndOfGame)
10677         EditGameEvent();
10678
10679     if (gameMode == EditPosition) 
10680         EditPositionDone(TRUE);
10681
10682     if (!WhiteOnMove(currentMove)) {
10683         DisplayError(_("It is not White's turn"), 0);
10684         return;
10685     }
10686   
10687     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10688       ExitAnalyzeMode();
10689
10690     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10691         gameMode == AnalyzeFile)
10692         TruncateGame();
10693
10694     ResurrectChessProgram();    /* in case it isn't running */
10695     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10696         gameMode = MachinePlaysWhite;
10697         ResetClocks();
10698     } else
10699     gameMode = MachinePlaysWhite;
10700     pausing = FALSE;
10701     ModeHighlight();
10702     SetGameInfo();
10703     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10704     DisplayTitle(buf);
10705     if (first.sendName) {
10706       sprintf(buf, "name %s\n", gameInfo.black);
10707       SendToProgram(buf, &first);
10708     }
10709     if (first.sendTime) {
10710       if (first.useColors) {
10711         SendToProgram("black\n", &first); /*gnu kludge*/
10712       }
10713       SendTimeRemaining(&first, TRUE);
10714     }
10715     if (first.useColors) {
10716       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10717     }
10718     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10719     SetMachineThinkingEnables();
10720     first.maybeThinking = TRUE;
10721     StartClocks();
10722     firstMove = FALSE;
10723
10724     if (appData.autoFlipView && !flipView) {
10725       flipView = !flipView;
10726       DrawPosition(FALSE, NULL);
10727       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10728     }
10729
10730     if(bookHit) { // [HGM] book: simulate book reply
10731         static char bookMove[MSG_SIZ]; // a bit generous?
10732
10733         programStats.nodes = programStats.depth = programStats.time = 
10734         programStats.score = programStats.got_only_move = 0;
10735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10736
10737         strcpy(bookMove, "move ");
10738         strcat(bookMove, bookHit);
10739         HandleMachineMove(bookMove, &first);
10740     }
10741 }
10742
10743 void
10744 MachineBlackEvent()
10745 {
10746     char buf[MSG_SIZ];
10747    char *bookHit = NULL;
10748
10749     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10750         return;
10751
10752
10753     if (gameMode == PlayFromGameFile || 
10754         gameMode == TwoMachinesPlay  || 
10755         gameMode == Training         || 
10756         gameMode == AnalyzeMode      || 
10757         gameMode == EndOfGame)
10758         EditGameEvent();
10759
10760     if (gameMode == EditPosition) 
10761         EditPositionDone(TRUE);
10762
10763     if (WhiteOnMove(currentMove)) {
10764         DisplayError(_("It is not Black's turn"), 0);
10765         return;
10766     }
10767     
10768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10769       ExitAnalyzeMode();
10770
10771     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10772         gameMode == AnalyzeFile)
10773         TruncateGame();
10774
10775     ResurrectChessProgram();    /* in case it isn't running */
10776     gameMode = MachinePlaysBlack;
10777     pausing = FALSE;
10778     ModeHighlight();
10779     SetGameInfo();
10780     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10781     DisplayTitle(buf);
10782     if (first.sendName) {
10783       sprintf(buf, "name %s\n", gameInfo.white);
10784       SendToProgram(buf, &first);
10785     }
10786     if (first.sendTime) {
10787       if (first.useColors) {
10788         SendToProgram("white\n", &first); /*gnu kludge*/
10789       }
10790       SendTimeRemaining(&first, FALSE);
10791     }
10792     if (first.useColors) {
10793       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10794     }
10795     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10796     SetMachineThinkingEnables();
10797     first.maybeThinking = TRUE;
10798     StartClocks();
10799
10800     if (appData.autoFlipView && flipView) {
10801       flipView = !flipView;
10802       DrawPosition(FALSE, NULL);
10803       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10804     }
10805     if(bookHit) { // [HGM] book: simulate book reply
10806         static char bookMove[MSG_SIZ]; // a bit generous?
10807
10808         programStats.nodes = programStats.depth = programStats.time = 
10809         programStats.score = programStats.got_only_move = 0;
10810         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10811
10812         strcpy(bookMove, "move ");
10813         strcat(bookMove, bookHit);
10814         HandleMachineMove(bookMove, &first);
10815     }
10816 }
10817
10818
10819 void
10820 DisplayTwoMachinesTitle()
10821 {
10822     char buf[MSG_SIZ];
10823     if (appData.matchGames > 0) {
10824         if (first.twoMachinesColor[0] == 'w') {
10825             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10826                     gameInfo.white, gameInfo.black,
10827                     first.matchWins, second.matchWins,
10828                     matchGame - 1 - (first.matchWins + second.matchWins));
10829         } else {
10830             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10831                     gameInfo.white, gameInfo.black,
10832                     second.matchWins, first.matchWins,
10833                     matchGame - 1 - (first.matchWins + second.matchWins));
10834         }
10835     } else {
10836         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10837     }
10838     DisplayTitle(buf);
10839 }
10840
10841 void
10842 TwoMachinesEvent P((void))
10843 {
10844     int i;
10845     char buf[MSG_SIZ];
10846     ChessProgramState *onmove;
10847     char *bookHit = NULL;
10848     
10849     if (appData.noChessProgram) return;
10850
10851     switch (gameMode) {
10852       case TwoMachinesPlay:
10853         return;
10854       case MachinePlaysWhite:
10855       case MachinePlaysBlack:
10856         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10857             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10858             return;
10859         }
10860         /* fall through */
10861       case BeginningOfGame:
10862       case PlayFromGameFile:
10863       case EndOfGame:
10864         EditGameEvent();
10865         if (gameMode != EditGame) return;
10866         break;
10867       case EditPosition:
10868         EditPositionDone(TRUE);
10869         break;
10870       case AnalyzeMode:
10871       case AnalyzeFile:
10872         ExitAnalyzeMode();
10873         break;
10874       case EditGame:
10875       default:
10876         break;
10877     }
10878
10879     forwardMostMove = currentMove;
10880     ResurrectChessProgram();    /* in case first program isn't running */
10881
10882     if (second.pr == NULL) {
10883         StartChessProgram(&second);
10884         if (second.protocolVersion == 1) {
10885           TwoMachinesEventIfReady();
10886         } else {
10887           /* kludge: allow timeout for initial "feature" command */
10888           FreezeUI();
10889           DisplayMessage("", _("Starting second chess program"));
10890           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10891         }
10892         return;
10893     }
10894     DisplayMessage("", "");
10895     InitChessProgram(&second, FALSE);
10896     SendToProgram("force\n", &second);
10897     if (startedFromSetupPosition) {
10898         SendBoard(&second, backwardMostMove);
10899     if (appData.debugMode) {
10900         fprintf(debugFP, "Two Machines\n");
10901     }
10902     }
10903     for (i = backwardMostMove; i < forwardMostMove; i++) {
10904         SendMoveToProgram(i, &second);
10905     }
10906
10907     gameMode = TwoMachinesPlay;
10908     pausing = FALSE;
10909     ModeHighlight();
10910     SetGameInfo();
10911     DisplayTwoMachinesTitle();
10912     firstMove = TRUE;
10913     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10914         onmove = &first;
10915     } else {
10916         onmove = &second;
10917     }
10918
10919     SendToProgram(first.computerString, &first);
10920     if (first.sendName) {
10921       sprintf(buf, "name %s\n", second.tidy);
10922       SendToProgram(buf, &first);
10923     }
10924     SendToProgram(second.computerString, &second);
10925     if (second.sendName) {
10926       sprintf(buf, "name %s\n", first.tidy);
10927       SendToProgram(buf, &second);
10928     }
10929
10930     ResetClocks();
10931     if (!first.sendTime || !second.sendTime) {
10932         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10933         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10934     }
10935     if (onmove->sendTime) {
10936       if (onmove->useColors) {
10937         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10938       }
10939       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10940     }
10941     if (onmove->useColors) {
10942       SendToProgram(onmove->twoMachinesColor, onmove);
10943     }
10944     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10945 //    SendToProgram("go\n", onmove);
10946     onmove->maybeThinking = TRUE;
10947     SetMachineThinkingEnables();
10948
10949     StartClocks();
10950
10951     if(bookHit) { // [HGM] book: simulate book reply
10952         static char bookMove[MSG_SIZ]; // a bit generous?
10953
10954         programStats.nodes = programStats.depth = programStats.time = 
10955         programStats.score = programStats.got_only_move = 0;
10956         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10957
10958         strcpy(bookMove, "move ");
10959         strcat(bookMove, bookHit);
10960         savedMessage = bookMove; // args for deferred call
10961         savedState = onmove;
10962         ScheduleDelayedEvent(DeferredBookMove, 1);
10963     }
10964 }
10965
10966 void
10967 TrainingEvent()
10968 {
10969     if (gameMode == Training) {
10970       SetTrainingModeOff();
10971       gameMode = PlayFromGameFile;
10972       DisplayMessage("", _("Training mode off"));
10973     } else {
10974       gameMode = Training;
10975       animateTraining = appData.animate;
10976
10977       /* make sure we are not already at the end of the game */
10978       if (currentMove < forwardMostMove) {
10979         SetTrainingModeOn();
10980         DisplayMessage("", _("Training mode on"));
10981       } else {
10982         gameMode = PlayFromGameFile;
10983         DisplayError(_("Already at end of game"), 0);
10984       }
10985     }
10986     ModeHighlight();
10987 }
10988
10989 void
10990 IcsClientEvent()
10991 {
10992     if (!appData.icsActive) return;
10993     switch (gameMode) {
10994       case IcsPlayingWhite:
10995       case IcsPlayingBlack:
10996       case IcsObserving:
10997       case IcsIdle:
10998       case BeginningOfGame:
10999       case IcsExamining:
11000         return;
11001
11002       case EditGame:
11003         break;
11004
11005       case EditPosition:
11006         EditPositionDone(TRUE);
11007         break;
11008
11009       case AnalyzeMode:
11010       case AnalyzeFile:
11011         ExitAnalyzeMode();
11012         break;
11013         
11014       default:
11015         EditGameEvent();
11016         break;
11017     }
11018
11019     gameMode = IcsIdle;
11020     ModeHighlight();
11021     return;
11022 }
11023
11024
11025 void
11026 EditGameEvent()
11027 {
11028     int i;
11029
11030     switch (gameMode) {
11031       case Training:
11032         SetTrainingModeOff();
11033         break;
11034       case MachinePlaysWhite:
11035       case MachinePlaysBlack:
11036       case BeginningOfGame:
11037         SendToProgram("force\n", &first);
11038         SetUserThinkingEnables();
11039         break;
11040       case PlayFromGameFile:
11041         (void) StopLoadGameTimer();
11042         if (gameFileFP != NULL) {
11043             gameFileFP = NULL;
11044         }
11045         break;
11046       case EditPosition:
11047         EditPositionDone(TRUE);
11048         break;
11049       case AnalyzeMode:
11050       case AnalyzeFile:
11051         ExitAnalyzeMode();
11052         SendToProgram("force\n", &first);
11053         break;
11054       case TwoMachinesPlay:
11055         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11056         ResurrectChessProgram();
11057         SetUserThinkingEnables();
11058         break;
11059       case EndOfGame:
11060         ResurrectChessProgram();
11061         break;
11062       case IcsPlayingBlack:
11063       case IcsPlayingWhite:
11064         DisplayError(_("Warning: You are still playing a game"), 0);
11065         break;
11066       case IcsObserving:
11067         DisplayError(_("Warning: You are still observing a game"), 0);
11068         break;
11069       case IcsExamining:
11070         DisplayError(_("Warning: You are still examining a game"), 0);
11071         break;
11072       case IcsIdle:
11073         break;
11074       case EditGame:
11075       default:
11076         return;
11077     }
11078     
11079     pausing = FALSE;
11080     StopClocks();
11081     first.offeredDraw = second.offeredDraw = 0;
11082
11083     if (gameMode == PlayFromGameFile) {
11084         whiteTimeRemaining = timeRemaining[0][currentMove];
11085         blackTimeRemaining = timeRemaining[1][currentMove];
11086         DisplayTitle("");
11087     }
11088
11089     if (gameMode == MachinePlaysWhite ||
11090         gameMode == MachinePlaysBlack ||
11091         gameMode == TwoMachinesPlay ||
11092         gameMode == EndOfGame) {
11093         i = forwardMostMove;
11094         while (i > currentMove) {
11095             SendToProgram("undo\n", &first);
11096             i--;
11097         }
11098         whiteTimeRemaining = timeRemaining[0][currentMove];
11099         blackTimeRemaining = timeRemaining[1][currentMove];
11100         DisplayBothClocks();
11101         if (whiteFlag || blackFlag) {
11102             whiteFlag = blackFlag = 0;
11103         }
11104         DisplayTitle("");
11105     }           
11106     
11107     gameMode = EditGame;
11108     ModeHighlight();
11109     SetGameInfo();
11110 }
11111
11112
11113 void
11114 EditPositionEvent()
11115 {
11116     if (gameMode == EditPosition) {
11117         EditGameEvent();
11118         return;
11119     }
11120     
11121     EditGameEvent();
11122     if (gameMode != EditGame) return;
11123     
11124     gameMode = EditPosition;
11125     ModeHighlight();
11126     SetGameInfo();
11127     if (currentMove > 0)
11128       CopyBoard(boards[0], boards[currentMove]);
11129     
11130     blackPlaysFirst = !WhiteOnMove(currentMove);
11131     ResetClocks();
11132     currentMove = forwardMostMove = backwardMostMove = 0;
11133     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11134     DisplayMove(-1);
11135 }
11136
11137 void
11138 ExitAnalyzeMode()
11139 {
11140     /* [DM] icsEngineAnalyze - possible call from other functions */
11141     if (appData.icsEngineAnalyze) {
11142         appData.icsEngineAnalyze = FALSE;
11143
11144         DisplayMessage("",_("Close ICS engine analyze..."));
11145     }
11146     if (first.analysisSupport && first.analyzing) {
11147       SendToProgram("exit\n", &first);
11148       first.analyzing = FALSE;
11149     }
11150     thinkOutput[0] = NULLCHAR;
11151 }
11152
11153 void
11154 EditPositionDone(Boolean fakeRights)
11155 {
11156     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11157
11158     startedFromSetupPosition = TRUE;
11159     InitChessProgram(&first, FALSE);
11160     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11161       boards[0][EP_STATUS] = EP_NONE;
11162       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11163     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11164         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11165         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11166       } else boards[0][CASTLING][2] = NoRights;
11167     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11168         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11169         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11170       } else boards[0][CASTLING][5] = NoRights;
11171     }
11172     SendToProgram("force\n", &first);
11173     if (blackPlaysFirst) {
11174         strcpy(moveList[0], "");
11175         strcpy(parseList[0], "");
11176         currentMove = forwardMostMove = backwardMostMove = 1;
11177         CopyBoard(boards[1], boards[0]);
11178     } else {
11179         currentMove = forwardMostMove = backwardMostMove = 0;
11180     }
11181     SendBoard(&first, forwardMostMove);
11182     if (appData.debugMode) {
11183         fprintf(debugFP, "EditPosDone\n");
11184     }
11185     DisplayTitle("");
11186     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11187     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11188     gameMode = EditGame;
11189     ModeHighlight();
11190     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11191     ClearHighlights(); /* [AS] */
11192 }
11193
11194 /* Pause for `ms' milliseconds */
11195 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11196 void
11197 TimeDelay(ms)
11198      long ms;
11199 {
11200     TimeMark m1, m2;
11201
11202     GetTimeMark(&m1);
11203     do {
11204         GetTimeMark(&m2);
11205     } while (SubtractTimeMarks(&m2, &m1) < ms);
11206 }
11207
11208 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11209 void
11210 SendMultiLineToICS(buf)
11211      char *buf;
11212 {
11213     char temp[MSG_SIZ+1], *p;
11214     int len;
11215
11216     len = strlen(buf);
11217     if (len > MSG_SIZ)
11218       len = MSG_SIZ;
11219   
11220     strncpy(temp, buf, len);
11221     temp[len] = 0;
11222
11223     p = temp;
11224     while (*p) {
11225         if (*p == '\n' || *p == '\r')
11226           *p = ' ';
11227         ++p;
11228     }
11229
11230     strcat(temp, "\n");
11231     SendToICS(temp);
11232     SendToPlayer(temp, strlen(temp));
11233 }
11234
11235 void
11236 SetWhiteToPlayEvent()
11237 {
11238     if (gameMode == EditPosition) {
11239         blackPlaysFirst = FALSE;
11240         DisplayBothClocks();    /* works because currentMove is 0 */
11241     } else if (gameMode == IcsExamining) {
11242         SendToICS(ics_prefix);
11243         SendToICS("tomove white\n");
11244     }
11245 }
11246
11247 void
11248 SetBlackToPlayEvent()
11249 {
11250     if (gameMode == EditPosition) {
11251         blackPlaysFirst = TRUE;
11252         currentMove = 1;        /* kludge */
11253         DisplayBothClocks();
11254         currentMove = 0;
11255     } else if (gameMode == IcsExamining) {
11256         SendToICS(ics_prefix);
11257         SendToICS("tomove black\n");
11258     }
11259 }
11260
11261 void
11262 EditPositionMenuEvent(selection, x, y)
11263      ChessSquare selection;
11264      int x, y;
11265 {
11266     char buf[MSG_SIZ];
11267     ChessSquare piece = boards[0][y][x];
11268
11269     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11270
11271     switch (selection) {
11272       case ClearBoard:
11273         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11274             SendToICS(ics_prefix);
11275             SendToICS("bsetup clear\n");
11276         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11277             SendToICS(ics_prefix);
11278             SendToICS("clearboard\n");
11279         } else {
11280             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11281                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11282                 for (y = 0; y < BOARD_HEIGHT; y++) {
11283                     if (gameMode == IcsExamining) {
11284                         if (boards[currentMove][y][x] != EmptySquare) {
11285                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11286                                     AAA + x, ONE + y);
11287                             SendToICS(buf);
11288                         }
11289                     } else {
11290                         boards[0][y][x] = p;
11291                     }
11292                 }
11293             }
11294         }
11295         if (gameMode == EditPosition) {
11296             DrawPosition(FALSE, boards[0]);
11297         }
11298         break;
11299
11300       case WhitePlay:
11301         SetWhiteToPlayEvent();
11302         break;
11303
11304       case BlackPlay:
11305         SetBlackToPlayEvent();
11306         break;
11307
11308       case EmptySquare:
11309         if (gameMode == IcsExamining) {
11310             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11311             SendToICS(buf);
11312         } else {
11313             boards[0][y][x] = EmptySquare;
11314             DrawPosition(FALSE, boards[0]);
11315         }
11316         break;
11317
11318       case PromotePiece:
11319         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11320            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11321             selection = (ChessSquare) (PROMOTED piece);
11322         } else if(piece == EmptySquare) selection = WhiteSilver;
11323         else selection = (ChessSquare)((int)piece - 1);
11324         goto defaultlabel;
11325
11326       case DemotePiece:
11327         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11328            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11329             selection = (ChessSquare) (DEMOTED piece);
11330         } else if(piece == EmptySquare) selection = BlackSilver;
11331         else selection = (ChessSquare)((int)piece + 1);       
11332         goto defaultlabel;
11333
11334       case WhiteQueen:
11335       case BlackQueen:
11336         if(gameInfo.variant == VariantShatranj ||
11337            gameInfo.variant == VariantXiangqi  ||
11338            gameInfo.variant == VariantCourier    )
11339             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11340         goto defaultlabel;
11341
11342       case WhiteKing:
11343       case BlackKing:
11344         if(gameInfo.variant == VariantXiangqi)
11345             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11346         if(gameInfo.variant == VariantKnightmate)
11347             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11348       default:
11349         defaultlabel:
11350         if (gameMode == IcsExamining) {
11351             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11352                     PieceToChar(selection), AAA + x, ONE + y);
11353             SendToICS(buf);
11354         } else {
11355             boards[0][y][x] = selection;
11356             DrawPosition(FALSE, boards[0]);
11357         }
11358         break;
11359     }
11360 }
11361
11362
11363 void
11364 DropMenuEvent(selection, x, y)
11365      ChessSquare selection;
11366      int x, y;
11367 {
11368     ChessMove moveType;
11369
11370     switch (gameMode) {
11371       case IcsPlayingWhite:
11372       case MachinePlaysBlack:
11373         if (!WhiteOnMove(currentMove)) {
11374             DisplayMoveError(_("It is Black's turn"));
11375             return;
11376         }
11377         moveType = WhiteDrop;
11378         break;
11379       case IcsPlayingBlack:
11380       case MachinePlaysWhite:
11381         if (WhiteOnMove(currentMove)) {
11382             DisplayMoveError(_("It is White's turn"));
11383             return;
11384         }
11385         moveType = BlackDrop;
11386         break;
11387       case EditGame:
11388         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11389         break;
11390       default:
11391         return;
11392     }
11393
11394     if (moveType == BlackDrop && selection < BlackPawn) {
11395       selection = (ChessSquare) ((int) selection
11396                                  + (int) BlackPawn - (int) WhitePawn);
11397     }
11398     if (boards[currentMove][y][x] != EmptySquare) {
11399         DisplayMoveError(_("That square is occupied"));
11400         return;
11401     }
11402
11403     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11404 }
11405
11406 void
11407 AcceptEvent()
11408 {
11409     /* Accept a pending offer of any kind from opponent */
11410     
11411     if (appData.icsActive) {
11412         SendToICS(ics_prefix);
11413         SendToICS("accept\n");
11414     } else if (cmailMsgLoaded) {
11415         if (currentMove == cmailOldMove &&
11416             commentList[cmailOldMove] != NULL &&
11417             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11418                    "Black offers a draw" : "White offers a draw")) {
11419             TruncateGame();
11420             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11421             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11422         } else {
11423             DisplayError(_("There is no pending offer on this move"), 0);
11424             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11425         }
11426     } else {
11427         /* Not used for offers from chess program */
11428     }
11429 }
11430
11431 void
11432 DeclineEvent()
11433 {
11434     /* Decline a pending offer of any kind from opponent */
11435     
11436     if (appData.icsActive) {
11437         SendToICS(ics_prefix);
11438         SendToICS("decline\n");
11439     } else if (cmailMsgLoaded) {
11440         if (currentMove == cmailOldMove &&
11441             commentList[cmailOldMove] != NULL &&
11442             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11443                    "Black offers a draw" : "White offers a draw")) {
11444 #ifdef NOTDEF
11445             AppendComment(cmailOldMove, "Draw declined");
11446             DisplayComment(cmailOldMove - 1, "Draw declined");
11447 #endif /*NOTDEF*/
11448         } else {
11449             DisplayError(_("There is no pending offer on this move"), 0);
11450         }
11451     } else {
11452         /* Not used for offers from chess program */
11453     }
11454 }
11455
11456 void
11457 RematchEvent()
11458 {
11459     /* Issue ICS rematch command */
11460     if (appData.icsActive) {
11461         SendToICS(ics_prefix);
11462         SendToICS("rematch\n");
11463     }
11464 }
11465
11466 void
11467 CallFlagEvent()
11468 {
11469     /* Call your opponent's flag (claim a win on time) */
11470     if (appData.icsActive) {
11471         SendToICS(ics_prefix);
11472         SendToICS("flag\n");
11473     } else {
11474         switch (gameMode) {
11475           default:
11476             return;
11477           case MachinePlaysWhite:
11478             if (whiteFlag) {
11479                 if (blackFlag)
11480                   GameEnds(GameIsDrawn, "Both players ran out of time",
11481                            GE_PLAYER);
11482                 else
11483                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11484             } else {
11485                 DisplayError(_("Your opponent is not out of time"), 0);
11486             }
11487             break;
11488           case MachinePlaysBlack:
11489             if (blackFlag) {
11490                 if (whiteFlag)
11491                   GameEnds(GameIsDrawn, "Both players ran out of time",
11492                            GE_PLAYER);
11493                 else
11494                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11495             } else {
11496                 DisplayError(_("Your opponent is not out of time"), 0);
11497             }
11498             break;
11499         }
11500     }
11501 }
11502
11503 void
11504 DrawEvent()
11505 {
11506     /* Offer draw or accept pending draw offer from opponent */
11507     
11508     if (appData.icsActive) {
11509         /* Note: tournament rules require draw offers to be
11510            made after you make your move but before you punch
11511            your clock.  Currently ICS doesn't let you do that;
11512            instead, you immediately punch your clock after making
11513            a move, but you can offer a draw at any time. */
11514         
11515         SendToICS(ics_prefix);
11516         SendToICS("draw\n");
11517     } else if (cmailMsgLoaded) {
11518         if (currentMove == cmailOldMove &&
11519             commentList[cmailOldMove] != NULL &&
11520             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11521                    "Black offers a draw" : "White offers a draw")) {
11522             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11523             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11524         } else if (currentMove == cmailOldMove + 1) {
11525             char *offer = WhiteOnMove(cmailOldMove) ?
11526               "White offers a draw" : "Black offers a draw";
11527             AppendComment(currentMove, offer);
11528             DisplayComment(currentMove - 1, offer);
11529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11530         } else {
11531             DisplayError(_("You must make your move before offering a draw"), 0);
11532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11533         }
11534     } else if (first.offeredDraw) {
11535         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11536     } else {
11537         if (first.sendDrawOffers) {
11538             SendToProgram("draw\n", &first);
11539             userOfferedDraw = TRUE;
11540         }
11541     }
11542 }
11543
11544 void
11545 AdjournEvent()
11546 {
11547     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11548     
11549     if (appData.icsActive) {
11550         SendToICS(ics_prefix);
11551         SendToICS("adjourn\n");
11552     } else {
11553         /* Currently GNU Chess doesn't offer or accept Adjourns */
11554     }
11555 }
11556
11557
11558 void
11559 AbortEvent()
11560 {
11561     /* Offer Abort or accept pending Abort offer from opponent */
11562     
11563     if (appData.icsActive) {
11564         SendToICS(ics_prefix);
11565         SendToICS("abort\n");
11566     } else {
11567         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11568     }
11569 }
11570
11571 void
11572 ResignEvent()
11573 {
11574     /* Resign.  You can do this even if it's not your turn. */
11575     
11576     if (appData.icsActive) {
11577         SendToICS(ics_prefix);
11578         SendToICS("resign\n");
11579     } else {
11580         switch (gameMode) {
11581           case MachinePlaysWhite:
11582             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11583             break;
11584           case MachinePlaysBlack:
11585             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11586             break;
11587           case EditGame:
11588             if (cmailMsgLoaded) {
11589                 TruncateGame();
11590                 if (WhiteOnMove(cmailOldMove)) {
11591                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11592                 } else {
11593                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11594                 }
11595                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11596             }
11597             break;
11598           default:
11599             break;
11600         }
11601     }
11602 }
11603
11604
11605 void
11606 StopObservingEvent()
11607 {
11608     /* Stop observing current games */
11609     SendToICS(ics_prefix);
11610     SendToICS("unobserve\n");
11611 }
11612
11613 void
11614 StopExaminingEvent()
11615 {
11616     /* Stop observing current game */
11617     SendToICS(ics_prefix);
11618     SendToICS("unexamine\n");
11619 }
11620
11621 void
11622 ForwardInner(target)
11623      int target;
11624 {
11625     int limit;
11626
11627     if (appData.debugMode)
11628         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11629                 target, currentMove, forwardMostMove);
11630
11631     if (gameMode == EditPosition)
11632       return;
11633
11634     if (gameMode == PlayFromGameFile && !pausing)
11635       PauseEvent();
11636     
11637     if (gameMode == IcsExamining && pausing)
11638       limit = pauseExamForwardMostMove;
11639     else
11640       limit = forwardMostMove;
11641     
11642     if (target > limit) target = limit;
11643
11644     if (target > 0 && moveList[target - 1][0]) {
11645         int fromX, fromY, toX, toY;
11646         toX = moveList[target - 1][2] - AAA;
11647         toY = moveList[target - 1][3] - ONE;
11648         if (moveList[target - 1][1] == '@') {
11649             if (appData.highlightLastMove) {
11650                 SetHighlights(-1, -1, toX, toY);
11651             }
11652         } else {
11653             fromX = moveList[target - 1][0] - AAA;
11654             fromY = moveList[target - 1][1] - ONE;
11655             if (target == currentMove + 1) {
11656                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11657             }
11658             if (appData.highlightLastMove) {
11659                 SetHighlights(fromX, fromY, toX, toY);
11660             }
11661         }
11662     }
11663     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11664         gameMode == Training || gameMode == PlayFromGameFile || 
11665         gameMode == AnalyzeFile) {
11666         while (currentMove < target) {
11667             SendMoveToProgram(currentMove++, &first);
11668         }
11669     } else {
11670         currentMove = target;
11671     }
11672     
11673     if (gameMode == EditGame || gameMode == EndOfGame) {
11674         whiteTimeRemaining = timeRemaining[0][currentMove];
11675         blackTimeRemaining = timeRemaining[1][currentMove];
11676     }
11677     DisplayBothClocks();
11678     DisplayMove(currentMove - 1);
11679     DrawPosition(FALSE, boards[currentMove]);
11680     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11681     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11682         DisplayComment(currentMove - 1, commentList[currentMove]);
11683     }
11684 }
11685
11686
11687 void
11688 ForwardEvent()
11689 {
11690     if (gameMode == IcsExamining && !pausing) {
11691         SendToICS(ics_prefix);
11692         SendToICS("forward\n");
11693     } else {
11694         ForwardInner(currentMove + 1);
11695     }
11696 }
11697
11698 void
11699 ToEndEvent()
11700 {
11701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11702         /* to optimze, we temporarily turn off analysis mode while we feed
11703          * the remaining moves to the engine. Otherwise we get analysis output
11704          * after each move.
11705          */ 
11706         if (first.analysisSupport) {
11707           SendToProgram("exit\nforce\n", &first);
11708           first.analyzing = FALSE;
11709         }
11710     }
11711         
11712     if (gameMode == IcsExamining && !pausing) {
11713         SendToICS(ics_prefix);
11714         SendToICS("forward 999999\n");
11715     } else {
11716         ForwardInner(forwardMostMove);
11717     }
11718
11719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11720         /* we have fed all the moves, so reactivate analysis mode */
11721         SendToProgram("analyze\n", &first);
11722         first.analyzing = TRUE;
11723         /*first.maybeThinking = TRUE;*/
11724         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11725     }
11726 }
11727
11728 void
11729 BackwardInner(target)
11730      int target;
11731 {
11732     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11733
11734     if (appData.debugMode)
11735         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11736                 target, currentMove, forwardMostMove);
11737
11738     if (gameMode == EditPosition) return;
11739     if (currentMove <= backwardMostMove) {
11740         ClearHighlights();
11741         DrawPosition(full_redraw, boards[currentMove]);
11742         return;
11743     }
11744     if (gameMode == PlayFromGameFile && !pausing)
11745       PauseEvent();
11746     
11747     if (moveList[target][0]) {
11748         int fromX, fromY, toX, toY;
11749         toX = moveList[target][2] - AAA;
11750         toY = moveList[target][3] - ONE;
11751         if (moveList[target][1] == '@') {
11752             if (appData.highlightLastMove) {
11753                 SetHighlights(-1, -1, toX, toY);
11754             }
11755         } else {
11756             fromX = moveList[target][0] - AAA;
11757             fromY = moveList[target][1] - ONE;
11758             if (target == currentMove - 1) {
11759                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11760             }
11761             if (appData.highlightLastMove) {
11762                 SetHighlights(fromX, fromY, toX, toY);
11763             }
11764         }
11765     }
11766     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11767         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11768         while (currentMove > target) {
11769             SendToProgram("undo\n", &first);
11770             currentMove--;
11771         }
11772     } else {
11773         currentMove = target;
11774     }
11775     
11776     if (gameMode == EditGame || gameMode == EndOfGame) {
11777         whiteTimeRemaining = timeRemaining[0][currentMove];
11778         blackTimeRemaining = timeRemaining[1][currentMove];
11779     }
11780     DisplayBothClocks();
11781     DisplayMove(currentMove - 1);
11782     DrawPosition(full_redraw, boards[currentMove]);
11783     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11784     // [HGM] PV info: routine tests if comment empty
11785     DisplayComment(currentMove - 1, commentList[currentMove]);
11786 }
11787
11788 void
11789 BackwardEvent()
11790 {
11791     if (gameMode == IcsExamining && !pausing) {
11792         SendToICS(ics_prefix);
11793         SendToICS("backward\n");
11794     } else {
11795         BackwardInner(currentMove - 1);
11796     }
11797 }
11798
11799 void
11800 ToStartEvent()
11801 {
11802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11803         /* to optimze, we temporarily turn off analysis mode while we undo
11804          * all the moves. Otherwise we get analysis output after each undo.
11805          */ 
11806         if (first.analysisSupport) {
11807           SendToProgram("exit\nforce\n", &first);
11808           first.analyzing = FALSE;
11809         }
11810     }
11811
11812     if (gameMode == IcsExamining && !pausing) {
11813         SendToICS(ics_prefix);
11814         SendToICS("backward 999999\n");
11815     } else {
11816         BackwardInner(backwardMostMove);
11817     }
11818
11819     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11820         /* we have fed all the moves, so reactivate analysis mode */
11821         SendToProgram("analyze\n", &first);
11822         first.analyzing = TRUE;
11823         /*first.maybeThinking = TRUE;*/
11824         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11825     }
11826 }
11827
11828 void
11829 ToNrEvent(int to)
11830 {
11831   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11832   if (to >= forwardMostMove) to = forwardMostMove;
11833   if (to <= backwardMostMove) to = backwardMostMove;
11834   if (to < currentMove) {
11835     BackwardInner(to);
11836   } else {
11837     ForwardInner(to);
11838   }
11839 }
11840
11841 void
11842 RevertEvent()
11843 {
11844     if (gameMode != IcsExamining) {
11845         DisplayError(_("You are not examining a game"), 0);
11846         return;
11847     }
11848     if (pausing) {
11849         DisplayError(_("You can't revert while pausing"), 0);
11850         return;
11851     }
11852     SendToICS(ics_prefix);
11853     SendToICS("revert\n");
11854 }
11855
11856 void
11857 RetractMoveEvent()
11858 {
11859     switch (gameMode) {
11860       case MachinePlaysWhite:
11861       case MachinePlaysBlack:
11862         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11863             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11864             return;
11865         }
11866         if (forwardMostMove < 2) return;
11867         currentMove = forwardMostMove = forwardMostMove - 2;
11868         whiteTimeRemaining = timeRemaining[0][currentMove];
11869         blackTimeRemaining = timeRemaining[1][currentMove];
11870         DisplayBothClocks();
11871         DisplayMove(currentMove - 1);
11872         ClearHighlights();/*!! could figure this out*/
11873         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11874         SendToProgram("remove\n", &first);
11875         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11876         break;
11877
11878       case BeginningOfGame:
11879       default:
11880         break;
11881
11882       case IcsPlayingWhite:
11883       case IcsPlayingBlack:
11884         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11885             SendToICS(ics_prefix);
11886             SendToICS("takeback 2\n");
11887         } else {
11888             SendToICS(ics_prefix);
11889             SendToICS("takeback 1\n");
11890         }
11891         break;
11892     }
11893 }
11894
11895 void
11896 MoveNowEvent()
11897 {
11898     ChessProgramState *cps;
11899
11900     switch (gameMode) {
11901       case MachinePlaysWhite:
11902         if (!WhiteOnMove(forwardMostMove)) {
11903             DisplayError(_("It is your turn"), 0);
11904             return;
11905         }
11906         cps = &first;
11907         break;
11908       case MachinePlaysBlack:
11909         if (WhiteOnMove(forwardMostMove)) {
11910             DisplayError(_("It is your turn"), 0);
11911             return;
11912         }
11913         cps = &first;
11914         break;
11915       case TwoMachinesPlay:
11916         if (WhiteOnMove(forwardMostMove) ==
11917             (first.twoMachinesColor[0] == 'w')) {
11918             cps = &first;
11919         } else {
11920             cps = &second;
11921         }
11922         break;
11923       case BeginningOfGame:
11924       default:
11925         return;
11926     }
11927     SendToProgram("?\n", cps);
11928 }
11929
11930 void
11931 TruncateGameEvent()
11932 {
11933     EditGameEvent();
11934     if (gameMode != EditGame) return;
11935     TruncateGame();
11936 }
11937
11938 void
11939 TruncateGame()
11940 {
11941     if (forwardMostMove > currentMove) {
11942         if (gameInfo.resultDetails != NULL) {
11943             free(gameInfo.resultDetails);
11944             gameInfo.resultDetails = NULL;
11945             gameInfo.result = GameUnfinished;
11946         }
11947         forwardMostMove = currentMove;
11948         HistorySet(parseList, backwardMostMove, forwardMostMove,
11949                    currentMove-1);
11950     }
11951 }
11952
11953 void
11954 HintEvent()
11955 {
11956     if (appData.noChessProgram) return;
11957     switch (gameMode) {
11958       case MachinePlaysWhite:
11959         if (WhiteOnMove(forwardMostMove)) {
11960             DisplayError(_("Wait until your turn"), 0);
11961             return;
11962         }
11963         break;
11964       case BeginningOfGame:
11965       case MachinePlaysBlack:
11966         if (!WhiteOnMove(forwardMostMove)) {
11967             DisplayError(_("Wait until your turn"), 0);
11968             return;
11969         }
11970         break;
11971       default:
11972         DisplayError(_("No hint available"), 0);
11973         return;
11974     }
11975     SendToProgram("hint\n", &first);
11976     hintRequested = TRUE;
11977 }
11978
11979 void
11980 BookEvent()
11981 {
11982     if (appData.noChessProgram) return;
11983     switch (gameMode) {
11984       case MachinePlaysWhite:
11985         if (WhiteOnMove(forwardMostMove)) {
11986             DisplayError(_("Wait until your turn"), 0);
11987             return;
11988         }
11989         break;
11990       case BeginningOfGame:
11991       case MachinePlaysBlack:
11992         if (!WhiteOnMove(forwardMostMove)) {
11993             DisplayError(_("Wait until your turn"), 0);
11994             return;
11995         }
11996         break;
11997       case EditPosition:
11998         EditPositionDone(TRUE);
11999         break;
12000       case TwoMachinesPlay:
12001         return;
12002       default:
12003         break;
12004     }
12005     SendToProgram("bk\n", &first);
12006     bookOutput[0] = NULLCHAR;
12007     bookRequested = TRUE;
12008 }
12009
12010 void
12011 AboutGameEvent()
12012 {
12013     char *tags = PGNTags(&gameInfo);
12014     TagsPopUp(tags, CmailMsg());
12015     free(tags);
12016 }
12017
12018 /* end button procedures */
12019
12020 void
12021 PrintPosition(fp, move)
12022      FILE *fp;
12023      int move;
12024 {
12025     int i, j;
12026     
12027     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12029             char c = PieceToChar(boards[move][i][j]);
12030             fputc(c == 'x' ? '.' : c, fp);
12031             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12032         }
12033     }
12034     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12035       fprintf(fp, "white to play\n");
12036     else
12037       fprintf(fp, "black to play\n");
12038 }
12039
12040 void
12041 PrintOpponents(fp)
12042      FILE *fp;
12043 {
12044     if (gameInfo.white != NULL) {
12045         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12046     } else {
12047         fprintf(fp, "\n");
12048     }
12049 }
12050
12051 /* Find last component of program's own name, using some heuristics */
12052 void
12053 TidyProgramName(prog, host, buf)
12054      char *prog, *host, buf[MSG_SIZ];
12055 {
12056     char *p, *q;
12057     int local = (strcmp(host, "localhost") == 0);
12058     while (!local && (p = strchr(prog, ';')) != NULL) {
12059         p++;
12060         while (*p == ' ') p++;
12061         prog = p;
12062     }
12063     if (*prog == '"' || *prog == '\'') {
12064         q = strchr(prog + 1, *prog);
12065     } else {
12066         q = strchr(prog, ' ');
12067     }
12068     if (q == NULL) q = prog + strlen(prog);
12069     p = q;
12070     while (p >= prog && *p != '/' && *p != '\\') p--;
12071     p++;
12072     if(p == prog && *p == '"') p++;
12073     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12074     memcpy(buf, p, q - p);
12075     buf[q - p] = NULLCHAR;
12076     if (!local) {
12077         strcat(buf, "@");
12078         strcat(buf, host);
12079     }
12080 }
12081
12082 char *
12083 TimeControlTagValue()
12084 {
12085     char buf[MSG_SIZ];
12086     if (!appData.clockMode) {
12087         strcpy(buf, "-");
12088     } else if (movesPerSession > 0) {
12089         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12090     } else if (timeIncrement == 0) {
12091         sprintf(buf, "%ld", timeControl/1000);
12092     } else {
12093         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12094     }
12095     return StrSave(buf);
12096 }
12097
12098 void
12099 SetGameInfo()
12100 {
12101     /* This routine is used only for certain modes */
12102     VariantClass v = gameInfo.variant;
12103     ClearGameInfo(&gameInfo);
12104     gameInfo.variant = v;
12105
12106     switch (gameMode) {
12107       case MachinePlaysWhite:
12108         gameInfo.event = StrSave( appData.pgnEventHeader );
12109         gameInfo.site = StrSave(HostName());
12110         gameInfo.date = PGNDate();
12111         gameInfo.round = StrSave("-");
12112         gameInfo.white = StrSave(first.tidy);
12113         gameInfo.black = StrSave(UserName());
12114         gameInfo.timeControl = TimeControlTagValue();
12115         break;
12116
12117       case MachinePlaysBlack:
12118         gameInfo.event = StrSave( appData.pgnEventHeader );
12119         gameInfo.site = StrSave(HostName());
12120         gameInfo.date = PGNDate();
12121         gameInfo.round = StrSave("-");
12122         gameInfo.white = StrSave(UserName());
12123         gameInfo.black = StrSave(first.tidy);
12124         gameInfo.timeControl = TimeControlTagValue();
12125         break;
12126
12127       case TwoMachinesPlay:
12128         gameInfo.event = StrSave( appData.pgnEventHeader );
12129         gameInfo.site = StrSave(HostName());
12130         gameInfo.date = PGNDate();
12131         if (matchGame > 0) {
12132             char buf[MSG_SIZ];
12133             sprintf(buf, "%d", matchGame);
12134             gameInfo.round = StrSave(buf);
12135         } else {
12136             gameInfo.round = StrSave("-");
12137         }
12138         if (first.twoMachinesColor[0] == 'w') {
12139             gameInfo.white = StrSave(first.tidy);
12140             gameInfo.black = StrSave(second.tidy);
12141         } else {
12142             gameInfo.white = StrSave(second.tidy);
12143             gameInfo.black = StrSave(first.tidy);
12144         }
12145         gameInfo.timeControl = TimeControlTagValue();
12146         break;
12147
12148       case EditGame:
12149         gameInfo.event = StrSave("Edited game");
12150         gameInfo.site = StrSave(HostName());
12151         gameInfo.date = PGNDate();
12152         gameInfo.round = StrSave("-");
12153         gameInfo.white = StrSave("-");
12154         gameInfo.black = StrSave("-");
12155         break;
12156
12157       case EditPosition:
12158         gameInfo.event = StrSave("Edited position");
12159         gameInfo.site = StrSave(HostName());
12160         gameInfo.date = PGNDate();
12161         gameInfo.round = StrSave("-");
12162         gameInfo.white = StrSave("-");
12163         gameInfo.black = StrSave("-");
12164         break;
12165
12166       case IcsPlayingWhite:
12167       case IcsPlayingBlack:
12168       case IcsObserving:
12169       case IcsExamining:
12170         break;
12171
12172       case PlayFromGameFile:
12173         gameInfo.event = StrSave("Game from non-PGN file");
12174         gameInfo.site = StrSave(HostName());
12175         gameInfo.date = PGNDate();
12176         gameInfo.round = StrSave("-");
12177         gameInfo.white = StrSave("?");
12178         gameInfo.black = StrSave("?");
12179         break;
12180
12181       default:
12182         break;
12183     }
12184 }
12185
12186 void
12187 ReplaceComment(index, text)
12188      int index;
12189      char *text;
12190 {
12191     int len;
12192
12193     while (*text == '\n') text++;
12194     len = strlen(text);
12195     while (len > 0 && text[len - 1] == '\n') len--;
12196
12197     if (commentList[index] != NULL)
12198       free(commentList[index]);
12199
12200     if (len == 0) {
12201         commentList[index] = NULL;
12202         return;
12203     }
12204     commentList[index] = (char *) malloc(len + 2);
12205     strncpy(commentList[index], text, len);
12206     commentList[index][len] = '\n';
12207     commentList[index][len + 1] = NULLCHAR;
12208 }
12209
12210 void
12211 CrushCRs(text)
12212      char *text;
12213 {
12214   char *p = text;
12215   char *q = text;
12216   char ch;
12217
12218   do {
12219     ch = *p++;
12220     if (ch == '\r') continue;
12221     *q++ = ch;
12222   } while (ch != '\0');
12223 }
12224
12225 void
12226 AppendComment(index, text)
12227      int index;
12228      char *text;
12229 {
12230     int oldlen, len;
12231     char *old;
12232
12233     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12234
12235     CrushCRs(text);
12236     while (*text == '\n') text++;
12237     len = strlen(text);
12238     while (len > 0 && text[len - 1] == '\n') len--;
12239
12240     if (len == 0) return;
12241
12242     if (commentList[index] != NULL) {
12243         old = commentList[index];
12244         oldlen = strlen(old);
12245         commentList[index] = (char *) malloc(oldlen + len + 2);
12246         strcpy(commentList[index], old);
12247         free(old);
12248         strncpy(&commentList[index][oldlen], text, len);
12249         commentList[index][oldlen + len] = '\n';
12250         commentList[index][oldlen + len + 1] = NULLCHAR;
12251     } else {
12252         commentList[index] = (char *) malloc(len + 2);
12253         strncpy(commentList[index], text, len);
12254         commentList[index][len] = '\n';
12255         commentList[index][len + 1] = NULLCHAR;
12256     }
12257 }
12258
12259 static char * FindStr( char * text, char * sub_text )
12260 {
12261     char * result = strstr( text, sub_text );
12262
12263     if( result != NULL ) {
12264         result += strlen( sub_text );
12265     }
12266
12267     return result;
12268 }
12269
12270 /* [AS] Try to extract PV info from PGN comment */
12271 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12272 char *GetInfoFromComment( int index, char * text )
12273 {
12274     char * sep = text;
12275
12276     if( text != NULL && index > 0 ) {
12277         int score = 0;
12278         int depth = 0;
12279         int time = -1, sec = 0, deci;
12280         char * s_eval = FindStr( text, "[%eval " );
12281         char * s_emt = FindStr( text, "[%emt " );
12282
12283         if( s_eval != NULL || s_emt != NULL ) {
12284             /* New style */
12285             char delim;
12286
12287             if( s_eval != NULL ) {
12288                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12289                     return text;
12290                 }
12291
12292                 if( delim != ']' ) {
12293                     return text;
12294                 }
12295             }
12296
12297             if( s_emt != NULL ) {
12298             }
12299         }
12300         else {
12301             /* We expect something like: [+|-]nnn.nn/dd */
12302             int score_lo = 0;
12303
12304             sep = strchr( text, '/' );
12305             if( sep == NULL || sep < (text+4) ) {
12306                 return text;
12307             }
12308
12309             time = -1; sec = -1; deci = -1;
12310             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12311                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12312                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12313                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12314                 return text;
12315             }
12316
12317             if( score_lo < 0 || score_lo >= 100 ) {
12318                 return text;
12319             }
12320
12321             if(sec >= 0) time = 600*time + 10*sec; else
12322             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12323
12324             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12325
12326             /* [HGM] PV time: now locate end of PV info */
12327             while( *++sep >= '0' && *sep <= '9'); // strip depth
12328             if(time >= 0)
12329             while( *++sep >= '0' && *sep <= '9'); // strip time
12330             if(sec >= 0)
12331             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12332             if(deci >= 0)
12333             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12334             while(*sep == ' ') sep++;
12335         }
12336
12337         if( depth <= 0 ) {
12338             return text;
12339         }
12340
12341         if( time < 0 ) {
12342             time = -1;
12343         }
12344
12345         pvInfoList[index-1].depth = depth;
12346         pvInfoList[index-1].score = score;
12347         pvInfoList[index-1].time  = 10*time; // centi-sec
12348     }
12349     return sep;
12350 }
12351
12352 void
12353 SendToProgram(message, cps)
12354      char *message;
12355      ChessProgramState *cps;
12356 {
12357     int count, outCount, error;
12358     char buf[MSG_SIZ];
12359
12360     if (cps->pr == NULL) return;
12361     Attention(cps);
12362     
12363     if (appData.debugMode) {
12364         TimeMark now;
12365         GetTimeMark(&now);
12366         fprintf(debugFP, "%ld >%-6s: %s", 
12367                 SubtractTimeMarks(&now, &programStartTime),
12368                 cps->which, message);
12369     }
12370     
12371     count = strlen(message);
12372     outCount = OutputToProcess(cps->pr, message, count, &error);
12373     if (outCount < count && !exiting 
12374                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12375         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12376         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12377             if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12378                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12379                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12380             } else {
12381                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12382             }
12383             gameInfo.resultDetails = buf;
12384         }
12385         DisplayFatalError(buf, error, 1);
12386     }
12387 }
12388
12389 void
12390 ReceiveFromProgram(isr, closure, message, count, error)
12391      InputSourceRef isr;
12392      VOIDSTAR closure;
12393      char *message;
12394      int count;
12395      int error;
12396 {
12397     char *end_str;
12398     char buf[MSG_SIZ];
12399     ChessProgramState *cps = (ChessProgramState *)closure;
12400
12401     if (isr != cps->isr) return; /* Killed intentionally */
12402     if (count <= 0) {
12403         if (count == 0) {
12404             sprintf(buf,
12405                     _("Error: %s chess program (%s) exited unexpectedly"),
12406                     cps->which, cps->program);
12407         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12408                 if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12409                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12410                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12411                 } else {
12412                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12413                 }
12414                 gameInfo.resultDetails = buf;
12415             }
12416             RemoveInputSource(cps->isr);
12417             DisplayFatalError(buf, 0, 1);
12418         } else {
12419             sprintf(buf,
12420                     _("Error reading from %s chess program (%s)"),
12421                     cps->which, cps->program);
12422             RemoveInputSource(cps->isr);
12423
12424             /* [AS] Program is misbehaving badly... kill it */
12425             if( count == -2 ) {
12426                 DestroyChildProcess( cps->pr, 9 );
12427                 cps->pr = NoProc;
12428             }
12429
12430             DisplayFatalError(buf, error, 1);
12431         }
12432         return;
12433     }
12434     
12435     if ((end_str = strchr(message, '\r')) != NULL)
12436       *end_str = NULLCHAR;
12437     if ((end_str = strchr(message, '\n')) != NULL)
12438       *end_str = NULLCHAR;
12439     
12440     if (appData.debugMode) {
12441         TimeMark now; int print = 1;
12442         char *quote = ""; char c; int i;
12443
12444         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12445                 char start = message[0];
12446                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12447                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12448                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12449                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12450                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12451                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12452                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12453                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12454                         { quote = "# "; print = (appData.engineComments == 2); }
12455                 message[0] = start; // restore original message
12456         }
12457         if(print) {
12458                 GetTimeMark(&now);
12459                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12460                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12461                         quote,
12462                         message);
12463         }
12464     }
12465
12466     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12467     if (appData.icsEngineAnalyze) {
12468         if (strstr(message, "whisper") != NULL ||
12469              strstr(message, "kibitz") != NULL || 
12470             strstr(message, "tellics") != NULL) return;
12471     }
12472
12473     HandleMachineMove(message, cps);
12474 }
12475
12476
12477 void
12478 SendTimeControl(cps, mps, tc, inc, sd, st)
12479      ChessProgramState *cps;
12480      int mps, inc, sd, st;
12481      long tc;
12482 {
12483     char buf[MSG_SIZ];
12484     int seconds;
12485
12486     if( timeControl_2 > 0 ) {
12487         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12488             tc = timeControl_2;
12489         }
12490     }
12491     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12492     inc /= cps->timeOdds;
12493     st  /= cps->timeOdds;
12494
12495     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12496
12497     if (st > 0) {
12498       /* Set exact time per move, normally using st command */
12499       if (cps->stKludge) {
12500         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12501         seconds = st % 60;
12502         if (seconds == 0) {
12503           sprintf(buf, "level 1 %d\n", st/60);
12504         } else {
12505           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12506         }
12507       } else {
12508         sprintf(buf, "st %d\n", st);
12509       }
12510     } else {
12511       /* Set conventional or incremental time control, using level command */
12512       if (seconds == 0) {
12513         /* Note old gnuchess bug -- minutes:seconds used to not work.
12514            Fixed in later versions, but still avoid :seconds
12515            when seconds is 0. */
12516         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12517       } else {
12518         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12519                 seconds, inc/1000);
12520       }
12521     }
12522     SendToProgram(buf, cps);
12523
12524     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12525     /* Orthogonally, limit search to given depth */
12526     if (sd > 0) {
12527       if (cps->sdKludge) {
12528         sprintf(buf, "depth\n%d\n", sd);
12529       } else {
12530         sprintf(buf, "sd %d\n", sd);
12531       }
12532       SendToProgram(buf, cps);
12533     }
12534
12535     if(cps->nps > 0) { /* [HGM] nps */
12536         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12537         else {
12538                 sprintf(buf, "nps %d\n", cps->nps);
12539               SendToProgram(buf, cps);
12540         }
12541     }
12542 }
12543
12544 ChessProgramState *WhitePlayer()
12545 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12546 {
12547     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12548        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12549         return &second;
12550     return &first;
12551 }
12552
12553 void
12554 SendTimeRemaining(cps, machineWhite)
12555      ChessProgramState *cps;
12556      int /*boolean*/ machineWhite;
12557 {
12558     char message[MSG_SIZ];
12559     long time, otime;
12560
12561     /* Note: this routine must be called when the clocks are stopped
12562        or when they have *just* been set or switched; otherwise
12563        it will be off by the time since the current tick started.
12564     */
12565     if (machineWhite) {
12566         time = whiteTimeRemaining / 10;
12567         otime = blackTimeRemaining / 10;
12568     } else {
12569         time = blackTimeRemaining / 10;
12570         otime = whiteTimeRemaining / 10;
12571     }
12572     /* [HGM] translate opponent's time by time-odds factor */
12573     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12574     if (appData.debugMode) {
12575         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12576     }
12577
12578     if (time <= 0) time = 1;
12579     if (otime <= 0) otime = 1;
12580     
12581     sprintf(message, "time %ld\n", time);
12582     SendToProgram(message, cps);
12583
12584     sprintf(message, "otim %ld\n", otime);
12585     SendToProgram(message, cps);
12586 }
12587
12588 int
12589 BoolFeature(p, name, loc, cps)
12590      char **p;
12591      char *name;
12592      int *loc;
12593      ChessProgramState *cps;
12594 {
12595   char buf[MSG_SIZ];
12596   int len = strlen(name);
12597   int val;
12598   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12599     (*p) += len + 1;
12600     sscanf(*p, "%d", &val);
12601     *loc = (val != 0);
12602     while (**p && **p != ' ') (*p)++;
12603     sprintf(buf, "accepted %s\n", name);
12604     SendToProgram(buf, cps);
12605     return TRUE;
12606   }
12607   return FALSE;
12608 }
12609
12610 int
12611 IntFeature(p, name, loc, cps)
12612      char **p;
12613      char *name;
12614      int *loc;
12615      ChessProgramState *cps;
12616 {
12617   char buf[MSG_SIZ];
12618   int len = strlen(name);
12619   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12620     (*p) += len + 1;
12621     sscanf(*p, "%d", loc);
12622     while (**p && **p != ' ') (*p)++;
12623     sprintf(buf, "accepted %s\n", name);
12624     SendToProgram(buf, cps);
12625     return TRUE;
12626   }
12627   return FALSE;
12628 }
12629
12630 int
12631 StringFeature(p, name, loc, cps)
12632      char **p;
12633      char *name;
12634      char loc[];
12635      ChessProgramState *cps;
12636 {
12637   char buf[MSG_SIZ];
12638   int len = strlen(name);
12639   if (strncmp((*p), name, len) == 0
12640       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12641     (*p) += len + 2;
12642     sscanf(*p, "%[^\"]", loc);
12643     while (**p && **p != '\"') (*p)++;
12644     if (**p == '\"') (*p)++;
12645     sprintf(buf, "accepted %s\n", name);
12646     SendToProgram(buf, cps);
12647     return TRUE;
12648   }
12649   return FALSE;
12650 }
12651
12652 int 
12653 ParseOption(Option *opt, ChessProgramState *cps)
12654 // [HGM] options: process the string that defines an engine option, and determine
12655 // name, type, default value, and allowed value range
12656 {
12657         char *p, *q, buf[MSG_SIZ];
12658         int n, min = (-1)<<31, max = 1<<31, def;
12659
12660         if(p = strstr(opt->name, " -spin ")) {
12661             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12662             if(max < min) max = min; // enforce consistency
12663             if(def < min) def = min;
12664             if(def > max) def = max;
12665             opt->value = def;
12666             opt->min = min;
12667             opt->max = max;
12668             opt->type = Spin;
12669         } else if((p = strstr(opt->name, " -slider "))) {
12670             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12671             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12672             if(max < min) max = min; // enforce consistency
12673             if(def < min) def = min;
12674             if(def > max) def = max;
12675             opt->value = def;
12676             opt->min = min;
12677             opt->max = max;
12678             opt->type = Spin; // Slider;
12679         } else if((p = strstr(opt->name, " -string "))) {
12680             opt->textValue = p+9;
12681             opt->type = TextBox;
12682         } else if((p = strstr(opt->name, " -file "))) {
12683             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12684             opt->textValue = p+7;
12685             opt->type = TextBox; // FileName;
12686         } else if((p = strstr(opt->name, " -path "))) {
12687             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12688             opt->textValue = p+7;
12689             opt->type = TextBox; // PathName;
12690         } else if(p = strstr(opt->name, " -check ")) {
12691             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12692             opt->value = (def != 0);
12693             opt->type = CheckBox;
12694         } else if(p = strstr(opt->name, " -combo ")) {
12695             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12696             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12697             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12698             opt->value = n = 0;
12699             while(q = StrStr(q, " /// ")) {
12700                 n++; *q = 0;    // count choices, and null-terminate each of them
12701                 q += 5;
12702                 if(*q == '*') { // remember default, which is marked with * prefix
12703                     q++;
12704                     opt->value = n;
12705                 }
12706                 cps->comboList[cps->comboCnt++] = q;
12707             }
12708             cps->comboList[cps->comboCnt++] = NULL;
12709             opt->max = n + 1;
12710             opt->type = ComboBox;
12711         } else if(p = strstr(opt->name, " -button")) {
12712             opt->type = Button;
12713         } else if(p = strstr(opt->name, " -save")) {
12714             opt->type = SaveButton;
12715         } else return FALSE;
12716         *p = 0; // terminate option name
12717         // now look if the command-line options define a setting for this engine option.
12718         if(cps->optionSettings && cps->optionSettings[0])
12719             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12720         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12721                 sprintf(buf, "option %s", p);
12722                 if(p = strstr(buf, ",")) *p = 0;
12723                 strcat(buf, "\n");
12724                 SendToProgram(buf, cps);
12725         }
12726         return TRUE;
12727 }
12728
12729 void
12730 FeatureDone(cps, val)
12731      ChessProgramState* cps;
12732      int val;
12733 {
12734   DelayedEventCallback cb = GetDelayedEvent();
12735   if ((cb == InitBackEnd3 && cps == &first) ||
12736       (cb == TwoMachinesEventIfReady && cps == &second)) {
12737     CancelDelayedEvent();
12738     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12739   }
12740   cps->initDone = val;
12741 }
12742
12743 /* Parse feature command from engine */
12744 void
12745 ParseFeatures(args, cps)
12746      char* args;
12747      ChessProgramState *cps;  
12748 {
12749   char *p = args;
12750   char *q;
12751   int val;
12752   char buf[MSG_SIZ];
12753
12754   for (;;) {
12755     while (*p == ' ') p++;
12756     if (*p == NULLCHAR) return;
12757
12758     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12759     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12760     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12761     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12762     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12763     if (BoolFeature(&p, "reuse", &val, cps)) {
12764       /* Engine can disable reuse, but can't enable it if user said no */
12765       if (!val) cps->reuse = FALSE;
12766       continue;
12767     }
12768     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12769     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12770       if (gameMode == TwoMachinesPlay) {
12771         DisplayTwoMachinesTitle();
12772       } else {
12773         DisplayTitle("");
12774       }
12775       continue;
12776     }
12777     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12778     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12779     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12780     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12781     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12782     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12783     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12784     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12785     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12786     if (IntFeature(&p, "done", &val, cps)) {
12787       FeatureDone(cps, val);
12788       continue;
12789     }
12790     /* Added by Tord: */
12791     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12792     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12793     /* End of additions by Tord */
12794
12795     /* [HGM] added features: */
12796     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12797     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12798     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12799     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12800     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12801     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12802     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12803         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12804             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12805             SendToProgram(buf, cps);
12806             continue;
12807         }
12808         if(cps->nrOptions >= MAX_OPTIONS) {
12809             cps->nrOptions--;
12810             sprintf(buf, "%s engine has too many options\n", cps->which);
12811             DisplayError(buf, 0);
12812         }
12813         continue;
12814     }
12815     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12816     /* End of additions by HGM */
12817
12818     /* unknown feature: complain and skip */
12819     q = p;
12820     while (*q && *q != '=') q++;
12821     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12822     SendToProgram(buf, cps);
12823     p = q;
12824     if (*p == '=') {
12825       p++;
12826       if (*p == '\"') {
12827         p++;
12828         while (*p && *p != '\"') p++;
12829         if (*p == '\"') p++;
12830       } else {
12831         while (*p && *p != ' ') p++;
12832       }
12833     }
12834   }
12835
12836 }
12837
12838 void
12839 PeriodicUpdatesEvent(newState)
12840      int newState;
12841 {
12842     if (newState == appData.periodicUpdates)
12843       return;
12844
12845     appData.periodicUpdates=newState;
12846
12847     /* Display type changes, so update it now */
12848 //    DisplayAnalysis();
12849
12850     /* Get the ball rolling again... */
12851     if (newState) {
12852         AnalysisPeriodicEvent(1);
12853         StartAnalysisClock();
12854     }
12855 }
12856
12857 void
12858 PonderNextMoveEvent(newState)
12859      int newState;
12860 {
12861     if (newState == appData.ponderNextMove) return;
12862     if (gameMode == EditPosition) EditPositionDone(TRUE);
12863     if (newState) {
12864         SendToProgram("hard\n", &first);
12865         if (gameMode == TwoMachinesPlay) {
12866             SendToProgram("hard\n", &second);
12867         }
12868     } else {
12869         SendToProgram("easy\n", &first);
12870         thinkOutput[0] = NULLCHAR;
12871         if (gameMode == TwoMachinesPlay) {
12872             SendToProgram("easy\n", &second);
12873         }
12874     }
12875     appData.ponderNextMove = newState;
12876 }
12877
12878 void
12879 NewSettingEvent(option, command, value)
12880      char *command;
12881      int option, value;
12882 {
12883     char buf[MSG_SIZ];
12884
12885     if (gameMode == EditPosition) EditPositionDone(TRUE);
12886     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12887     SendToProgram(buf, &first);
12888     if (gameMode == TwoMachinesPlay) {
12889         SendToProgram(buf, &second);
12890     }
12891 }
12892
12893 void
12894 ShowThinkingEvent()
12895 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12896 {
12897     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12898     int newState = appData.showThinking
12899         // [HGM] thinking: other features now need thinking output as well
12900         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12901     
12902     if (oldState == newState) return;
12903     oldState = newState;
12904     if (gameMode == EditPosition) EditPositionDone(TRUE);
12905     if (oldState) {
12906         SendToProgram("post\n", &first);
12907         if (gameMode == TwoMachinesPlay) {
12908             SendToProgram("post\n", &second);
12909         }
12910     } else {
12911         SendToProgram("nopost\n", &first);
12912         thinkOutput[0] = NULLCHAR;
12913         if (gameMode == TwoMachinesPlay) {
12914             SendToProgram("nopost\n", &second);
12915         }
12916     }
12917 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12918 }
12919
12920 void
12921 AskQuestionEvent(title, question, replyPrefix, which)
12922      char *title; char *question; char *replyPrefix; char *which;
12923 {
12924   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12925   if (pr == NoProc) return;
12926   AskQuestion(title, question, replyPrefix, pr);
12927 }
12928
12929 void
12930 DisplayMove(moveNumber)
12931      int moveNumber;
12932 {
12933     char message[MSG_SIZ];
12934     char res[MSG_SIZ];
12935     char cpThinkOutput[MSG_SIZ];
12936
12937     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12938     
12939     if (moveNumber == forwardMostMove - 1 || 
12940         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12941
12942         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12943
12944         if (strchr(cpThinkOutput, '\n')) {
12945             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12946         }
12947     } else {
12948         *cpThinkOutput = NULLCHAR;
12949     }
12950
12951     /* [AS] Hide thinking from human user */
12952     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12953         *cpThinkOutput = NULLCHAR;
12954         if( thinkOutput[0] != NULLCHAR ) {
12955             int i;
12956
12957             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12958                 cpThinkOutput[i] = '.';
12959             }
12960             cpThinkOutput[i] = NULLCHAR;
12961             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12962         }
12963     }
12964
12965     if (moveNumber == forwardMostMove - 1 &&
12966         gameInfo.resultDetails != NULL) {
12967         if (gameInfo.resultDetails[0] == NULLCHAR) {
12968             sprintf(res, " %s", PGNResult(gameInfo.result));
12969         } else {
12970             sprintf(res, " {%s} %s",
12971                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12972         }
12973     } else {
12974         res[0] = NULLCHAR;
12975     }
12976
12977     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12978         DisplayMessage(res, cpThinkOutput);
12979     } else {
12980         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12981                 WhiteOnMove(moveNumber) ? " " : ".. ",
12982                 parseList[moveNumber], res);
12983         DisplayMessage(message, cpThinkOutput);
12984     }
12985 }
12986
12987 void
12988 DisplayComment(moveNumber, text)
12989      int moveNumber;
12990      char *text;
12991 {
12992     char title[MSG_SIZ];
12993     char buf[8000]; // comment can be long!
12994     int score, depth;
12995     
12996     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12997       strcpy(title, "Comment");
12998     } else {
12999       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13000               WhiteOnMove(moveNumber) ? " " : ".. ",
13001               parseList[moveNumber]);
13002     }
13003     // [HGM] PV info: display PV info together with (or as) comment
13004     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13005       if(text == NULL) text = "";                                           
13006       score = pvInfoList[moveNumber].score;
13007       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13008               depth, (pvInfoList[moveNumber].time+50)/100, text);
13009       text = buf;
13010     }
13011     if (text != NULL && (appData.autoDisplayComment || commentUp))
13012         CommentPopUp(title, text);
13013 }
13014
13015 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13016  * might be busy thinking or pondering.  It can be omitted if your
13017  * gnuchess is configured to stop thinking immediately on any user
13018  * input.  However, that gnuchess feature depends on the FIONREAD
13019  * ioctl, which does not work properly on some flavors of Unix.
13020  */
13021 void
13022 Attention(cps)
13023      ChessProgramState *cps;
13024 {
13025 #if ATTENTION
13026     if (!cps->useSigint) return;
13027     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13028     switch (gameMode) {
13029       case MachinePlaysWhite:
13030       case MachinePlaysBlack:
13031       case TwoMachinesPlay:
13032       case IcsPlayingWhite:
13033       case IcsPlayingBlack:
13034       case AnalyzeMode:
13035       case AnalyzeFile:
13036         /* Skip if we know it isn't thinking */
13037         if (!cps->maybeThinking) return;
13038         if (appData.debugMode)
13039           fprintf(debugFP, "Interrupting %s\n", cps->which);
13040         InterruptChildProcess(cps->pr);
13041         cps->maybeThinking = FALSE;
13042         break;
13043       default:
13044         break;
13045     }
13046 #endif /*ATTENTION*/
13047 }
13048
13049 int
13050 CheckFlags()
13051 {
13052     if (whiteTimeRemaining <= 0) {
13053         if (!whiteFlag) {
13054             whiteFlag = TRUE;
13055             if (appData.icsActive) {
13056                 if (appData.autoCallFlag &&
13057                     gameMode == IcsPlayingBlack && !blackFlag) {
13058                   SendToICS(ics_prefix);
13059                   SendToICS("flag\n");
13060                 }
13061             } else {
13062                 if (blackFlag) {
13063                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13064                 } else {
13065                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13066                     if (appData.autoCallFlag) {
13067                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13068                         return TRUE;
13069                     }
13070                 }
13071             }
13072         }
13073     }
13074     if (blackTimeRemaining <= 0) {
13075         if (!blackFlag) {
13076             blackFlag = TRUE;
13077             if (appData.icsActive) {
13078                 if (appData.autoCallFlag &&
13079                     gameMode == IcsPlayingWhite && !whiteFlag) {
13080                   SendToICS(ics_prefix);
13081                   SendToICS("flag\n");
13082                 }
13083             } else {
13084                 if (whiteFlag) {
13085                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13086                 } else {
13087                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13088                     if (appData.autoCallFlag) {
13089                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13090                         return TRUE;
13091                     }
13092                 }
13093             }
13094         }
13095     }
13096     return FALSE;
13097 }
13098
13099 void
13100 CheckTimeControl()
13101 {
13102     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13103         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13104
13105     /*
13106      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13107      */
13108     if ( !WhiteOnMove(forwardMostMove) )
13109         /* White made time control */
13110         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13111         /* [HGM] time odds: correct new time quota for time odds! */
13112                                             / WhitePlayer()->timeOdds;
13113       else
13114         /* Black made time control */
13115         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13116                                             / WhitePlayer()->other->timeOdds;
13117 }
13118
13119 void
13120 DisplayBothClocks()
13121 {
13122     int wom = gameMode == EditPosition ?
13123       !blackPlaysFirst : WhiteOnMove(currentMove);
13124     DisplayWhiteClock(whiteTimeRemaining, wom);
13125     DisplayBlackClock(blackTimeRemaining, !wom);
13126 }
13127
13128
13129 /* Timekeeping seems to be a portability nightmare.  I think everyone
13130    has ftime(), but I'm really not sure, so I'm including some ifdefs
13131    to use other calls if you don't.  Clocks will be less accurate if
13132    you have neither ftime nor gettimeofday.
13133 */
13134
13135 /* VS 2008 requires the #include outside of the function */
13136 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13137 #include <sys/timeb.h>
13138 #endif
13139
13140 /* Get the current time as a TimeMark */
13141 void
13142 GetTimeMark(tm)
13143      TimeMark *tm;
13144 {
13145 #if HAVE_GETTIMEOFDAY
13146
13147     struct timeval timeVal;
13148     struct timezone timeZone;
13149
13150     gettimeofday(&timeVal, &timeZone);
13151     tm->sec = (long) timeVal.tv_sec; 
13152     tm->ms = (int) (timeVal.tv_usec / 1000L);
13153
13154 #else /*!HAVE_GETTIMEOFDAY*/
13155 #if HAVE_FTIME
13156
13157 // include <sys/timeb.h> / moved to just above start of function
13158     struct timeb timeB;
13159
13160     ftime(&timeB);
13161     tm->sec = (long) timeB.time;
13162     tm->ms = (int) timeB.millitm;
13163
13164 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13165     tm->sec = (long) time(NULL);
13166     tm->ms = 0;
13167 #endif
13168 #endif
13169 }
13170
13171 /* Return the difference in milliseconds between two
13172    time marks.  We assume the difference will fit in a long!
13173 */
13174 long
13175 SubtractTimeMarks(tm2, tm1)
13176      TimeMark *tm2, *tm1;
13177 {
13178     return 1000L*(tm2->sec - tm1->sec) +
13179            (long) (tm2->ms - tm1->ms);
13180 }
13181
13182
13183 /*
13184  * Code to manage the game clocks.
13185  *
13186  * In tournament play, black starts the clock and then white makes a move.
13187  * We give the human user a slight advantage if he is playing white---the
13188  * clocks don't run until he makes his first move, so it takes zero time.
13189  * Also, we don't account for network lag, so we could get out of sync
13190  * with GNU Chess's clock -- but then, referees are always right.  
13191  */
13192
13193 static TimeMark tickStartTM;
13194 static long intendedTickLength;
13195
13196 long
13197 NextTickLength(timeRemaining)
13198      long timeRemaining;
13199 {
13200     long nominalTickLength, nextTickLength;
13201
13202     if (timeRemaining > 0L && timeRemaining <= 10000L)
13203       nominalTickLength = 100L;
13204     else
13205       nominalTickLength = 1000L;
13206     nextTickLength = timeRemaining % nominalTickLength;
13207     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13208
13209     return nextTickLength;
13210 }
13211
13212 /* Adjust clock one minute up or down */
13213 void
13214 AdjustClock(Boolean which, int dir)
13215 {
13216     if(which) blackTimeRemaining += 60000*dir;
13217     else      whiteTimeRemaining += 60000*dir;
13218     DisplayBothClocks();
13219 }
13220
13221 /* Stop clocks and reset to a fresh time control */
13222 void
13223 ResetClocks() 
13224 {
13225     (void) StopClockTimer();
13226     if (appData.icsActive) {
13227         whiteTimeRemaining = blackTimeRemaining = 0;
13228     } else if (searchTime) {
13229         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13230         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13231     } else { /* [HGM] correct new time quote for time odds */
13232         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13233         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13234     }
13235     if (whiteFlag || blackFlag) {
13236         DisplayTitle("");
13237         whiteFlag = blackFlag = FALSE;
13238     }
13239     DisplayBothClocks();
13240 }
13241
13242 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13243
13244 /* Decrement running clock by amount of time that has passed */
13245 void
13246 DecrementClocks()
13247 {
13248     long timeRemaining;
13249     long lastTickLength, fudge;
13250     TimeMark now;
13251
13252     if (!appData.clockMode) return;
13253     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13254         
13255     GetTimeMark(&now);
13256
13257     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13258
13259     /* Fudge if we woke up a little too soon */
13260     fudge = intendedTickLength - lastTickLength;
13261     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13262
13263     if (WhiteOnMove(forwardMostMove)) {
13264         if(whiteNPS >= 0) lastTickLength = 0;
13265         timeRemaining = whiteTimeRemaining -= lastTickLength;
13266         DisplayWhiteClock(whiteTimeRemaining - fudge,
13267                           WhiteOnMove(currentMove));
13268     } else {
13269         if(blackNPS >= 0) lastTickLength = 0;
13270         timeRemaining = blackTimeRemaining -= lastTickLength;
13271         DisplayBlackClock(blackTimeRemaining - fudge,
13272                           !WhiteOnMove(currentMove));
13273     }
13274
13275     if (CheckFlags()) return;
13276         
13277     tickStartTM = now;
13278     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13279     StartClockTimer(intendedTickLength);
13280
13281     /* if the time remaining has fallen below the alarm threshold, sound the
13282      * alarm. if the alarm has sounded and (due to a takeback or time control
13283      * with increment) the time remaining has increased to a level above the
13284      * threshold, reset the alarm so it can sound again. 
13285      */
13286     
13287     if (appData.icsActive && appData.icsAlarm) {
13288
13289         /* make sure we are dealing with the user's clock */
13290         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13291                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13292            )) return;
13293
13294         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13295             alarmSounded = FALSE;
13296         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13297             PlayAlarmSound();
13298             alarmSounded = TRUE;
13299         }
13300     }
13301 }
13302
13303
13304 /* A player has just moved, so stop the previously running
13305    clock and (if in clock mode) start the other one.
13306    We redisplay both clocks in case we're in ICS mode, because
13307    ICS gives us an update to both clocks after every move.
13308    Note that this routine is called *after* forwardMostMove
13309    is updated, so the last fractional tick must be subtracted
13310    from the color that is *not* on move now.
13311 */
13312 void
13313 SwitchClocks()
13314 {
13315     long lastTickLength;
13316     TimeMark now;
13317     int flagged = FALSE;
13318
13319     GetTimeMark(&now);
13320
13321     if (StopClockTimer() && appData.clockMode) {
13322         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13323         if (WhiteOnMove(forwardMostMove)) {
13324             if(blackNPS >= 0) lastTickLength = 0;
13325             blackTimeRemaining -= lastTickLength;
13326            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13327 //         if(pvInfoList[forwardMostMove-1].time == -1)
13328                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13329                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13330         } else {
13331            if(whiteNPS >= 0) lastTickLength = 0;
13332            whiteTimeRemaining -= lastTickLength;
13333            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13334 //         if(pvInfoList[forwardMostMove-1].time == -1)
13335                  pvInfoList[forwardMostMove-1].time = 
13336                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13337         }
13338         flagged = CheckFlags();
13339     }
13340     CheckTimeControl();
13341
13342     if (flagged || !appData.clockMode) return;
13343
13344     switch (gameMode) {
13345       case MachinePlaysBlack:
13346       case MachinePlaysWhite:
13347       case BeginningOfGame:
13348         if (pausing) return;
13349         break;
13350
13351       case EditGame:
13352       case PlayFromGameFile:
13353       case IcsExamining:
13354         return;
13355
13356       default:
13357         break;
13358     }
13359
13360     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13361         if(WhiteOnMove(forwardMostMove))
13362              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13363         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13364     }
13365
13366     tickStartTM = now;
13367     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13368       whiteTimeRemaining : blackTimeRemaining);
13369     StartClockTimer(intendedTickLength);
13370 }
13371         
13372
13373 /* Stop both clocks */
13374 void
13375 StopClocks()
13376 {       
13377     long lastTickLength;
13378     TimeMark now;
13379
13380     if (!StopClockTimer()) return;
13381     if (!appData.clockMode) return;
13382
13383     GetTimeMark(&now);
13384
13385     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13386     if (WhiteOnMove(forwardMostMove)) {
13387         if(whiteNPS >= 0) lastTickLength = 0;
13388         whiteTimeRemaining -= lastTickLength;
13389         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13390     } else {
13391         if(blackNPS >= 0) lastTickLength = 0;
13392         blackTimeRemaining -= lastTickLength;
13393         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13394     }
13395     CheckFlags();
13396 }
13397         
13398 /* Start clock of player on move.  Time may have been reset, so
13399    if clock is already running, stop and restart it. */
13400 void
13401 StartClocks()
13402 {
13403     (void) StopClockTimer(); /* in case it was running already */
13404     DisplayBothClocks();
13405     if (CheckFlags()) return;
13406
13407     if (!appData.clockMode) return;
13408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13409
13410     GetTimeMark(&tickStartTM);
13411     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13412       whiteTimeRemaining : blackTimeRemaining);
13413
13414    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13415     whiteNPS = blackNPS = -1; 
13416     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13417        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13418         whiteNPS = first.nps;
13419     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13420        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13421         blackNPS = first.nps;
13422     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13423         whiteNPS = second.nps;
13424     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13425         blackNPS = second.nps;
13426     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13427
13428     StartClockTimer(intendedTickLength);
13429 }
13430
13431 char *
13432 TimeString(ms)
13433      long ms;
13434 {
13435     long second, minute, hour, day;
13436     char *sign = "";
13437     static char buf[32];
13438     
13439     if (ms > 0 && ms <= 9900) {
13440       /* convert milliseconds to tenths, rounding up */
13441       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13442
13443       sprintf(buf, " %03.1f ", tenths/10.0);
13444       return buf;
13445     }
13446
13447     /* convert milliseconds to seconds, rounding up */
13448     /* use floating point to avoid strangeness of integer division
13449        with negative dividends on many machines */
13450     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13451
13452     if (second < 0) {
13453         sign = "-";
13454         second = -second;
13455     }
13456     
13457     day = second / (60 * 60 * 24);
13458     second = second % (60 * 60 * 24);
13459     hour = second / (60 * 60);
13460     second = second % (60 * 60);
13461     minute = second / 60;
13462     second = second % 60;
13463     
13464     if (day > 0)
13465       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13466               sign, day, hour, minute, second);
13467     else if (hour > 0)
13468       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13469     else
13470       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13471     
13472     return buf;
13473 }
13474
13475
13476 /*
13477  * This is necessary because some C libraries aren't ANSI C compliant yet.
13478  */
13479 char *
13480 StrStr(string, match)
13481      char *string, *match;
13482 {
13483     int i, length;
13484     
13485     length = strlen(match);
13486     
13487     for (i = strlen(string) - length; i >= 0; i--, string++)
13488       if (!strncmp(match, string, length))
13489         return string;
13490     
13491     return NULL;
13492 }
13493
13494 char *
13495 StrCaseStr(string, match)
13496      char *string, *match;
13497 {
13498     int i, j, length;
13499     
13500     length = strlen(match);
13501     
13502     for (i = strlen(string) - length; i >= 0; i--, string++) {
13503         for (j = 0; j < length; j++) {
13504             if (ToLower(match[j]) != ToLower(string[j]))
13505               break;
13506         }
13507         if (j == length) return string;
13508     }
13509
13510     return NULL;
13511 }
13512
13513 #ifndef _amigados
13514 int
13515 StrCaseCmp(s1, s2)
13516      char *s1, *s2;
13517 {
13518     char c1, c2;
13519     
13520     for (;;) {
13521         c1 = ToLower(*s1++);
13522         c2 = ToLower(*s2++);
13523         if (c1 > c2) return 1;
13524         if (c1 < c2) return -1;
13525         if (c1 == NULLCHAR) return 0;
13526     }
13527 }
13528
13529
13530 int
13531 ToLower(c)
13532      int c;
13533 {
13534     return isupper(c) ? tolower(c) : c;
13535 }
13536
13537
13538 int
13539 ToUpper(c)
13540      int c;
13541 {
13542     return islower(c) ? toupper(c) : c;
13543 }
13544 #endif /* !_amigados    */
13545
13546 char *
13547 StrSave(s)
13548      char *s;
13549 {
13550     char *ret;
13551
13552     if ((ret = (char *) malloc(strlen(s) + 1))) {
13553         strcpy(ret, s);
13554     }
13555     return ret;
13556 }
13557
13558 char *
13559 StrSavePtr(s, savePtr)
13560      char *s, **savePtr;
13561 {
13562     if (*savePtr) {
13563         free(*savePtr);
13564     }
13565     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13566         strcpy(*savePtr, s);
13567     }
13568     return(*savePtr);
13569 }
13570
13571 char *
13572 PGNDate()
13573 {
13574     time_t clock;
13575     struct tm *tm;
13576     char buf[MSG_SIZ];
13577
13578     clock = time((time_t *)NULL);
13579     tm = localtime(&clock);
13580     sprintf(buf, "%04d.%02d.%02d",
13581             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13582     return StrSave(buf);
13583 }
13584
13585
13586 char *
13587 PositionToFEN(move, overrideCastling)
13588      int move;
13589      char *overrideCastling;
13590 {
13591     int i, j, fromX, fromY, toX, toY;
13592     int whiteToPlay;
13593     char buf[128];
13594     char *p, *q;
13595     int emptycount;
13596     ChessSquare piece;
13597
13598     whiteToPlay = (gameMode == EditPosition) ?
13599       !blackPlaysFirst : (move % 2 == 0);
13600     p = buf;
13601
13602     /* Piece placement data */
13603     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13604         emptycount = 0;
13605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13606             if (boards[move][i][j] == EmptySquare) {
13607                 emptycount++;
13608             } else { ChessSquare piece = boards[move][i][j];
13609                 if (emptycount > 0) {
13610                     if(emptycount<10) /* [HGM] can be >= 10 */
13611                         *p++ = '0' + emptycount;
13612                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13613                     emptycount = 0;
13614                 }
13615                 if(PieceToChar(piece) == '+') {
13616                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13617                     *p++ = '+';
13618                     piece = (ChessSquare)(DEMOTED piece);
13619                 } 
13620                 *p++ = PieceToChar(piece);
13621                 if(p[-1] == '~') {
13622                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13623                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13624                     *p++ = '~';
13625                 }
13626             }
13627         }
13628         if (emptycount > 0) {
13629             if(emptycount<10) /* [HGM] can be >= 10 */
13630                 *p++ = '0' + emptycount;
13631             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13632             emptycount = 0;
13633         }
13634         *p++ = '/';
13635     }
13636     *(p - 1) = ' ';
13637
13638     /* [HGM] print Crazyhouse or Shogi holdings */
13639     if( gameInfo.holdingsWidth ) {
13640         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13641         q = p;
13642         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13643             piece = boards[move][i][BOARD_WIDTH-1];
13644             if( piece != EmptySquare )
13645               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13646                   *p++ = PieceToChar(piece);
13647         }
13648         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13649             piece = boards[move][BOARD_HEIGHT-i-1][0];
13650             if( piece != EmptySquare )
13651               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13652                   *p++ = PieceToChar(piece);
13653         }
13654
13655         if( q == p ) *p++ = '-';
13656         *p++ = ']';
13657         *p++ = ' ';
13658     }
13659
13660     /* Active color */
13661     *p++ = whiteToPlay ? 'w' : 'b';
13662     *p++ = ' ';
13663
13664   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13665     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13666   } else {
13667   if(nrCastlingRights) {
13668      q = p;
13669      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13670        /* [HGM] write directly from rights */
13671            if(boards[move][CASTLING][2] != NoRights &&
13672               boards[move][CASTLING][0] != NoRights   )
13673                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13674            if(boards[move][CASTLING][2] != NoRights &&
13675               boards[move][CASTLING][1] != NoRights   )
13676                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13677            if(boards[move][CASTLING][5] != NoRights &&
13678               boards[move][CASTLING][3] != NoRights   )
13679                 *p++ = boards[move][CASTLING][3] + AAA;
13680            if(boards[move][CASTLING][5] != NoRights &&
13681               boards[move][CASTLING][4] != NoRights   )
13682                 *p++ = boards[move][CASTLING][4] + AAA;
13683      } else {
13684
13685         /* [HGM] write true castling rights */
13686         if( nrCastlingRights == 6 ) {
13687             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13688                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13689             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13690                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13691             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13692                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13693             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13694                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13695         }
13696      }
13697      if (q == p) *p++ = '-'; /* No castling rights */
13698      *p++ = ' ';
13699   }
13700
13701   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13702      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13703     /* En passant target square */
13704     if (move > backwardMostMove) {
13705         fromX = moveList[move - 1][0] - AAA;
13706         fromY = moveList[move - 1][1] - ONE;
13707         toX = moveList[move - 1][2] - AAA;
13708         toY = moveList[move - 1][3] - ONE;
13709         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13710             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13711             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13712             fromX == toX) {
13713             /* 2-square pawn move just happened */
13714             *p++ = toX + AAA;
13715             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13716         } else {
13717             *p++ = '-';
13718         }
13719     } else if(move == backwardMostMove) {
13720         // [HGM] perhaps we should always do it like this, and forget the above?
13721         if((int)boards[move][EP_STATUS] >= 0) {
13722             *p++ = boards[move][EP_STATUS] + AAA;
13723             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13724         } else {
13725             *p++ = '-';
13726         }
13727     } else {
13728         *p++ = '-';
13729     }
13730     *p++ = ' ';
13731   }
13732   }
13733
13734     /* [HGM] find reversible plies */
13735     {   int i = 0, j=move;
13736
13737         if (appData.debugMode) { int k;
13738             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13739             for(k=backwardMostMove; k<=forwardMostMove; k++)
13740                 fprintf(debugFP, "e%d. p=%d\n", k, (int)boards[k][EP_STATUS]);
13741
13742         }
13743
13744         while(j > backwardMostMove && (int)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13745         if( j == backwardMostMove ) i += initialRulePlies;
13746         sprintf(p, "%d ", i);
13747         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13748     }
13749     /* Fullmove number */
13750     sprintf(p, "%d", (move / 2) + 1);
13751     
13752     return StrSave(buf);
13753 }
13754
13755 Boolean
13756 ParseFEN(board, blackPlaysFirst, fen)
13757     Board board;
13758      int *blackPlaysFirst;
13759      char *fen;
13760 {
13761     int i, j;
13762     char *p;
13763     int emptycount;
13764     ChessSquare piece;
13765
13766     p = fen;
13767
13768     /* [HGM] by default clear Crazyhouse holdings, if present */
13769     if(gameInfo.holdingsWidth) {
13770        for(i=0; i<BOARD_HEIGHT; i++) {
13771            board[i][0]             = EmptySquare; /* black holdings */
13772            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13773            board[i][1]             = (ChessSquare) 0; /* black counts */
13774            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13775        }
13776     }
13777
13778     /* Piece placement data */
13779     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13780         j = 0;
13781         for (;;) {
13782             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13783                 if (*p == '/') p++;
13784                 emptycount = gameInfo.boardWidth - j;
13785                 while (emptycount--)
13786                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13787                 break;
13788 #if(BOARD_FILES >= 10)
13789             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13790                 p++; emptycount=10;
13791                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13792                 while (emptycount--)
13793                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13794 #endif
13795             } else if (isdigit(*p)) {
13796                 emptycount = *p++ - '0';
13797                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13798                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13799                 while (emptycount--)
13800                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13801             } else if (*p == '+' || isalpha(*p)) {
13802                 if (j >= gameInfo.boardWidth) return FALSE;
13803                 if(*p=='+') {
13804                     piece = CharToPiece(*++p);
13805                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13806                     piece = (ChessSquare) (PROMOTED piece ); p++;
13807                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13808                 } else piece = CharToPiece(*p++);
13809
13810                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13811                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13812                     piece = (ChessSquare) (PROMOTED piece);
13813                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13814                     p++;
13815                 }
13816                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13817             } else {
13818                 return FALSE;
13819             }
13820         }
13821     }
13822     while (*p == '/' || *p == ' ') p++;
13823
13824     /* [HGM] look for Crazyhouse holdings here */
13825     while(*p==' ') p++;
13826     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13827         if(*p == '[') p++;
13828         if(*p == '-' ) *p++; /* empty holdings */ else {
13829             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13830             /* if we would allow FEN reading to set board size, we would   */
13831             /* have to add holdings and shift the board read so far here   */
13832             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13833                 *p++;
13834                 if((int) piece >= (int) BlackPawn ) {
13835                     i = (int)piece - (int)BlackPawn;
13836                     i = PieceToNumber((ChessSquare)i);
13837                     if( i >= gameInfo.holdingsSize ) return FALSE;
13838                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13839                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13840                 } else {
13841                     i = (int)piece - (int)WhitePawn;
13842                     i = PieceToNumber((ChessSquare)i);
13843                     if( i >= gameInfo.holdingsSize ) return FALSE;
13844                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13845                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13846                 }
13847             }
13848         }
13849         if(*p == ']') *p++;
13850     }
13851
13852     while(*p == ' ') p++;
13853
13854     /* Active color */
13855     switch (*p++) {
13856       case 'w':
13857         *blackPlaysFirst = FALSE;
13858         break;
13859       case 'b': 
13860         *blackPlaysFirst = TRUE;
13861         break;
13862       default:
13863         return FALSE;
13864     }
13865
13866     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13867     /* return the extra info in global variiables             */
13868
13869     /* set defaults in case FEN is incomplete */
13870     board[EP_STATUS] = EP_UNKNOWN;
13871     for(i=0; i<nrCastlingRights; i++ ) {
13872         board[CASTLING][i] =
13873             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13874     }   /* assume possible unless obviously impossible */
13875     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13876     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13877     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13878     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13879     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13880     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13881     FENrulePlies = 0;
13882
13883     while(*p==' ') p++;
13884     if(nrCastlingRights) {
13885       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13886           /* castling indicator present, so default becomes no castlings */
13887           for(i=0; i<nrCastlingRights; i++ ) {
13888                  board[CASTLING][i] = NoRights;
13889           }
13890       }
13891       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13892              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13893              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13894              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13895         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13896
13897         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13898             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13899             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13900         }
13901         switch(c) {
13902           case'K':
13903               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13904               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13905               board[CASTLING][2] = whiteKingFile;
13906               break;
13907           case'Q':
13908               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13909               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13910               board[CASTLING][2] = whiteKingFile;
13911               break;
13912           case'k':
13913               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13914               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13915               board[CASTLING][5] = blackKingFile;
13916               break;
13917           case'q':
13918               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13919               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13920               board[CASTLING][5] = blackKingFile;
13921           case '-':
13922               break;
13923           default: /* FRC castlings */
13924               if(c >= 'a') { /* black rights */
13925                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13926                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13927                   if(i == BOARD_RGHT) break;
13928                   board[CASTLING][5] = i;
13929                   c -= AAA;
13930                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13931                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13932                   if(c > i)
13933                       board[CASTLING][3] = c;
13934                   else
13935                       board[CASTLING][4] = c;
13936               } else { /* white rights */
13937                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13938                     if(board[0][i] == WhiteKing) break;
13939                   if(i == BOARD_RGHT) break;
13940                   board[CASTLING][2] = i;
13941                   c -= AAA - 'a' + 'A';
13942                   if(board[0][c] >= WhiteKing) break;
13943                   if(c > i)
13944                       board[CASTLING][0] = c;
13945                   else
13946                       board[CASTLING][1] = c;
13947               }
13948         }
13949       }
13950     if (appData.debugMode) {
13951         fprintf(debugFP, "FEN castling rights:");
13952         for(i=0; i<nrCastlingRights; i++)
13953         fprintf(debugFP, " %d", board[CASTLING][i]);
13954         fprintf(debugFP, "\n");
13955     }
13956
13957       while(*p==' ') p++;
13958     }
13959
13960     /* read e.p. field in games that know e.p. capture */
13961     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13962        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13963       if(*p=='-') {
13964         p++; board[EP_STATUS] = EP_NONE;
13965       } else {
13966          char c = *p++ - AAA;
13967
13968          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13969          if(*p >= '0' && *p <='9') *p++;
13970          board[EP_STATUS] = c;
13971       }
13972     }
13973
13974
13975     if(sscanf(p, "%d", &i) == 1) {
13976         FENrulePlies = i; /* 50-move ply counter */
13977         /* (The move number is still ignored)    */
13978     }
13979
13980     return TRUE;
13981 }
13982       
13983 void
13984 EditPositionPasteFEN(char *fen)
13985 {
13986   if (fen != NULL) {
13987     Board initial_position;
13988
13989     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13990       DisplayError(_("Bad FEN position in clipboard"), 0);
13991       return ;
13992     } else {
13993       int savedBlackPlaysFirst = blackPlaysFirst;
13994       EditPositionEvent();
13995       blackPlaysFirst = savedBlackPlaysFirst;
13996       CopyBoard(boards[0], initial_position);
13997       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13998       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
13999       DisplayBothClocks();
14000       DrawPosition(FALSE, boards[currentMove]);
14001     }
14002   }
14003 }
14004
14005 static char cseq[12] = "\\   ";
14006
14007 Boolean set_cont_sequence(char *new_seq)
14008 {
14009     int len;
14010     Boolean ret;
14011
14012     // handle bad attempts to set the sequence
14013         if (!new_seq)
14014                 return 0; // acceptable error - no debug
14015
14016     len = strlen(new_seq);
14017     ret = (len > 0) && (len < sizeof(cseq));
14018     if (ret)
14019         strcpy(cseq, new_seq);
14020     else if (appData.debugMode)
14021         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14022     return ret;
14023 }
14024
14025 /*
14026     reformat a source message so words don't cross the width boundary.  internal
14027     newlines are not removed.  returns the wrapped size (no null character unless
14028     included in source message).  If dest is NULL, only calculate the size required
14029     for the dest buffer.  lp argument indicats line position upon entry, and it's
14030     passed back upon exit.
14031 */
14032 int wrap(char *dest, char *src, int count, int width, int *lp)
14033 {
14034     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14035
14036     cseq_len = strlen(cseq);
14037     old_line = line = *lp;
14038     ansi = len = clen = 0;
14039
14040     for (i=0; i < count; i++)
14041     {
14042         if (src[i] == '\033')
14043             ansi = 1;
14044
14045         // if we hit the width, back up
14046         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14047         {
14048             // store i & len in case the word is too long
14049             old_i = i, old_len = len;
14050
14051             // find the end of the last word
14052             while (i && src[i] != ' ' && src[i] != '\n')
14053             {
14054                 i--;
14055                 len--;
14056             }
14057
14058             // word too long?  restore i & len before splitting it
14059             if ((old_i-i+clen) >= width)
14060             {
14061                 i = old_i;
14062                 len = old_len;
14063             }
14064
14065             // extra space?
14066             if (i && src[i-1] == ' ')
14067                 len--;
14068
14069             if (src[i] != ' ' && src[i] != '\n')
14070             {
14071                 i--;
14072                 if (len)
14073                     len--;
14074             }
14075
14076             // now append the newline and continuation sequence
14077             if (dest)
14078                 dest[len] = '\n';
14079             len++;
14080             if (dest)
14081                 strncpy(dest+len, cseq, cseq_len);
14082             len += cseq_len;
14083             line = cseq_len;
14084             clen = cseq_len;
14085             continue;
14086         }
14087
14088         if (dest)
14089             dest[len] = src[i];
14090         len++;
14091         if (!ansi)
14092             line++;
14093         if (src[i] == '\n')
14094             line = 0;
14095         if (src[i] == 'm')
14096             ansi = 0;
14097     }
14098     if (dest && appData.debugMode)
14099     {
14100         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14101             count, width, line, len, *lp);
14102         show_bytes(debugFP, src, count);
14103         fprintf(debugFP, "\ndest: ");
14104         show_bytes(debugFP, dest, len);
14105         fprintf(debugFP, "\n");
14106     }
14107     *lp = dest ? line : old_line;
14108
14109     return len;
14110 }