fix double start of zippy engine after switch to gothic
[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, char *castle, char *ep));
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  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944    Board oldBoard;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960
1961    
1962    if (appData.debugMode) {
1963      fprintf(debugFP, "Switch board from %s to %s\n",
1964              VariantName(gameInfo.variant), VariantName(newVariant));
1965      setbuf(debugFP, NULL);
1966    }
1967    shuffleOpenings = 0;       /* [HGM] shuffle */
1968    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969    switch(newVariant) 
1970      {
1971      case VariantShogi:
1972        newWidth = 9;  newHeight = 9;
1973        gameInfo.holdingsSize = 7;
1974      case VariantBughouse:
1975      case VariantCrazyhouse:
1976        newHoldingsWidth = 2; break;
1977      case VariantGreat:
1978        newWidth = 10;
1979      case VariantSuper:
1980        newHoldingsWidth = 2;
1981        gameInfo.holdingsSize = 8;
1982        break;
1983      case VariantGothic:
1984      case VariantCapablanca:
1985      case VariantCapaRandom:
1986        newWidth = 10;
1987      default:
1988        newHoldingsWidth = gameInfo.holdingsSize = 0;
1989      };
1990    
1991    if(newWidth  != gameInfo.boardWidth  ||
1992       newHeight != gameInfo.boardHeight ||
1993       newHoldingsWidth != gameInfo.holdingsWidth ) {
1994      
1995      /* shift position to new playing area, if needed */
1996      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997        for(i=0; i<BOARD_HEIGHT; i++) 
1998          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000              board[i][j];
2001        for(i=0; i<newHeight; i++) {
2002          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004        }
2005      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006        for(i=0; i<BOARD_HEIGHT; i++)
2007          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009              board[i][j];
2010      }
2011      gameInfo.boardWidth  = newWidth;
2012      gameInfo.boardHeight = newHeight;
2013      gameInfo.holdingsWidth = newHoldingsWidth;
2014      gameInfo.variant = newVariant;
2015      InitDrawingSizes(-2, 0);
2016    } else gameInfo.variant = newVariant;
2017    CopyBoard(oldBoard, board);   // remember correctly formatted board
2018      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2019    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2020 }
2021
2022 static int loggedOn = FALSE;
2023
2024 /*-- Game start info cache: --*/
2025 int gs_gamenum;
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\   ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2033
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2036
2037 void
2038 read_from_ics(isr, closure, data, count, error)
2039      InputSourceRef isr;
2040      VOIDSTAR closure;
2041      char *data;
2042      int count;
2043      int error;
2044 {
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2054     
2055     static int started = STARTED_NONE;
2056     static char parse[20000];
2057     static int parse_pos = 0;
2058     static char buf[BUF_SIZE + 1];
2059     static int firstTime = TRUE, intfSet = FALSE;
2060     static ColorClass prevColor = ColorNormal;
2061     static int savingComment = FALSE;
2062     static int cmatch = 0; // continuation sequence match
2063     char *bp;
2064     char str[500];
2065     int i, oldi;
2066     int buf_len;
2067     int next_out;
2068     int tkind;
2069     int backup;    /* [DM] For zippy color lines */
2070     char *p;
2071     char talker[MSG_SIZ]; // [HGM] chat
2072     int channel;
2073
2074     if (appData.debugMode) {
2075       if (!error) {
2076         fprintf(debugFP, "<ICS: ");
2077         show_bytes(debugFP, data, count);
2078         fprintf(debugFP, "\n");
2079       }
2080     }
2081
2082     if (appData.debugMode) { int f = forwardMostMove;
2083         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085     }
2086     if (count > 0) {
2087         /* If last read ended with a partial line that we couldn't parse,
2088            prepend it to the new read and try again. */
2089         if (leftover_len > 0) {
2090             for (i=0; i<leftover_len; i++)
2091               buf[i] = buf[leftover_start + i];
2092         }
2093
2094     /* copy new characters into the buffer */
2095     bp = buf + leftover_len;
2096     buf_len=leftover_len;
2097     for (i=0; i<count; i++)
2098     {
2099         // ignore these
2100         if (data[i] == '\r')
2101             continue;
2102
2103         // join lines split by ICS?
2104         if (!appData.noJoin)
2105         {
2106             /*
2107                 Joining just consists of finding matches against the
2108                 continuation sequence, and discarding that sequence
2109                 if found instead of copying it.  So, until a match
2110                 fails, there's nothing to do since it might be the
2111                 complete sequence, and thus, something we don't want
2112                 copied.
2113             */
2114             if (data[i] == cont_seq[cmatch])
2115             {
2116                 cmatch++;
2117                 if (cmatch == strlen(cont_seq))
2118                 {
2119                     cmatch = 0; // complete match.  just reset the counter
2120
2121                     /*
2122                         it's possible for the ICS to not include the space
2123                         at the end of the last word, making our [correct]
2124                         join operation fuse two separate words.  the server
2125                         does this when the space occurs at the width setting.
2126                     */
2127                     if (!buf_len || buf[buf_len-1] != ' ')
2128                     {
2129                         *bp++ = ' ';
2130                         buf_len++;
2131                     }
2132                 }
2133                 continue;
2134             }
2135             else if (cmatch)
2136             {
2137                 /*
2138                     match failed, so we have to copy what matched before
2139                     falling through and copying this character.  In reality,
2140                     this will only ever be just the newline character, but
2141                     it doesn't hurt to be precise.
2142                 */
2143                 strncpy(bp, cont_seq, cmatch);
2144                 bp += cmatch;
2145                 buf_len += cmatch;
2146                 cmatch = 0;
2147             }
2148         }
2149
2150         // copy this char
2151         *bp++ = data[i];
2152         buf_len++;
2153     }
2154
2155         buf[buf_len] = NULLCHAR;
2156         next_out = leftover_len;
2157         leftover_start = 0;
2158         
2159         i = 0;
2160         while (i < buf_len) {
2161             /* Deal with part of the TELNET option negotiation
2162                protocol.  We refuse to do anything beyond the
2163                defaults, except that we allow the WILL ECHO option,
2164                which ICS uses to turn off password echoing when we are
2165                directly connected to it.  We reject this option
2166                if localLineEditing mode is on (always on in xboard)
2167                and we are talking to port 23, which might be a real
2168                telnet server that will try to keep WILL ECHO on permanently.
2169              */
2170             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172                 unsigned char option;
2173                 oldi = i;
2174                 switch ((unsigned char) buf[++i]) {
2175                   case TN_WILL:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<WILL ");
2178                     switch (option = (unsigned char) buf[++i]) {
2179                       case TN_ECHO:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "ECHO ");
2182                         /* Reply only if this is a change, according
2183                            to the protocol rules. */
2184                         if (remoteEchoOption) break;
2185                         if (appData.localLineEditing &&
2186                             atoi(appData.icsPort) == TN_PORT) {
2187                             TelnetRequest(TN_DONT, TN_ECHO);
2188                         } else {
2189                             EchoOff();
2190                             TelnetRequest(TN_DO, TN_ECHO);
2191                             remoteEchoOption = TRUE;
2192                         }
2193                         break;
2194                       default:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         /* Whatever this is, we don't want it. */
2198                         TelnetRequest(TN_DONT, option);
2199                         break;
2200                     }
2201                     break;
2202                   case TN_WONT:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<WONT ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       case TN_ECHO:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "ECHO ");
2209                         /* Reply only if this is a change, according
2210                            to the protocol rules. */
2211                         if (!remoteEchoOption) break;
2212                         EchoOn();
2213                         TelnetRequest(TN_DONT, TN_ECHO);
2214                         remoteEchoOption = FALSE;
2215                         break;
2216                       default:
2217                         if (appData.debugMode)
2218                           fprintf(debugFP, "%d ", (unsigned char) option);
2219                         /* Whatever this is, it must already be turned
2220                            off, because we never agree to turn on
2221                            anything non-default, so according to the
2222                            protocol rules, we don't reply. */
2223                         break;
2224                     }
2225                     break;
2226                   case TN_DO:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<DO ");
2229                     switch (option = (unsigned char) buf[++i]) {
2230                       default:
2231                         /* Whatever this is, we refuse to do it. */
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", option);
2234                         TelnetRequest(TN_WONT, option);
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DONT:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DONT ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         /* Whatever this is, we are already not doing
2246                            it, because we never agree to do anything
2247                            non-default, so according to the protocol
2248                            rules, we don't reply. */
2249                         break;
2250                     }
2251                     break;
2252                   case TN_IAC:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<IAC ");
2255                     /* Doubled IAC; pass it through */
2256                     i--;
2257                     break;
2258                   default:
2259                     if (appData.debugMode)
2260                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261                     /* Drop all other telnet commands on the floor */
2262                     break;
2263                 }
2264                 if (oldi > next_out)
2265                   SendToPlayer(&buf[next_out], oldi - next_out);
2266                 if (++i > next_out)
2267                   next_out = i;
2268                 continue;
2269             }
2270                 
2271             /* OK, this at least will *usually* work */
2272             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273                 loggedOn = TRUE;
2274             }
2275             
2276             if (loggedOn && !intfSet) {
2277                 if (ics_type == ICS_ICC) {
2278                   sprintf(str,
2279                           "/set-quietly interface %s\n/set-quietly style 12\n",
2280                           programVersion);
2281                 } else if (ics_type == ICS_CHESSNET) {
2282                   sprintf(str, "/style 12\n");
2283                 } else {
2284                   strcpy(str, "alias $ @\n$set interface ");
2285                   strcat(str, programVersion);
2286                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2287 #ifdef WIN32
2288                   strcat(str, "$iset nohighlight 1\n");
2289 #endif
2290                   strcat(str, "$iset lock 1\n$style 12\n");
2291                 }
2292                 SendToICS(str);
2293                 NotifyFrontendLogin();
2294                 intfSet = TRUE;
2295             }
2296
2297             if (started == STARTED_COMMENT) {
2298                 /* Accumulate characters in comment */
2299                 parse[parse_pos++] = buf[i];
2300                 if (buf[i] == '\n') {
2301                     parse[parse_pos] = NULLCHAR;
2302                     if(chattingPartner>=0) {
2303                         char mess[MSG_SIZ];
2304                         sprintf(mess, "%s%s", talker, parse);
2305                         OutputChatMessage(chattingPartner, mess);
2306                         chattingPartner = -1;
2307                     } else
2308                     if(!suppressKibitz) // [HGM] kibitz
2309                         AppendComment(forwardMostMove, StripHighlight(parse));
2310                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311                         int nrDigit = 0, nrAlph = 0, i;
2312                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314                         parse[parse_pos] = NULLCHAR;
2315                         // try to be smart: if it does not look like search info, it should go to
2316                         // ICS interaction window after all, not to engine-output window.
2317                         for(i=0; i<parse_pos; i++) { // count letters and digits
2318                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2320                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2321                         }
2322                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323                             int depth=0; float score;
2324                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326                                 pvInfoList[forwardMostMove-1].depth = depth;
2327                                 pvInfoList[forwardMostMove-1].score = 100*score;
2328                             }
2329                             OutputKibitz(suppressKibitz, parse);
2330                         } else {
2331                             char tmp[MSG_SIZ];
2332                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333                             SendToPlayer(tmp, strlen(tmp));
2334                         }
2335                     }
2336                     started = STARTED_NONE;
2337                 } else {
2338                     /* Don't match patterns against characters in chatter */
2339                     i++;
2340                     continue;
2341                 }
2342             }
2343             if (started == STARTED_CHATTER) {
2344                 if (buf[i] != '\n') {
2345                     /* Don't match patterns against characters in chatter */
2346                     i++;
2347                     continue;
2348                 }
2349                 started = STARTED_NONE;
2350             }
2351
2352             /* Kludge to deal with rcmd protocol */
2353             if (firstTime && looking_at(buf, &i, "\001*")) {
2354                 DisplayFatalError(&buf[1], 0, 1);
2355                 continue;
2356             } else {
2357                 firstTime = FALSE;
2358             }
2359
2360             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361                 ics_type = ICS_ICC;
2362                 ics_prefix = "/";
2363                 if (appData.debugMode)
2364                   fprintf(debugFP, "ics_type %d\n", ics_type);
2365                 continue;
2366             }
2367             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368                 ics_type = ICS_FICS;
2369                 ics_prefix = "$";
2370                 if (appData.debugMode)
2371                   fprintf(debugFP, "ics_type %d\n", ics_type);
2372                 continue;
2373             }
2374             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375                 ics_type = ICS_CHESSNET;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381
2382             if (!loggedOn &&
2383                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2385                  looking_at(buf, &i, "will be \"*\""))) {
2386               strcpy(ics_handle, star_match[0]);
2387               continue;
2388             }
2389
2390             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2391               char buf[MSG_SIZ];
2392               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393               DisplayIcsInteractionTitle(buf);
2394               have_set_title = TRUE;
2395             }
2396
2397             /* skip finger notes */
2398             if (started == STARTED_NONE &&
2399                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400                  (buf[i] == '1' && buf[i+1] == '0')) &&
2401                 buf[i+2] == ':' && buf[i+3] == ' ') {
2402               started = STARTED_CHATTER;
2403               i += 3;
2404               continue;
2405             }
2406
2407             /* skip formula vars */
2408             if (started == STARTED_NONE &&
2409                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             oldi = i;
2416             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417             if (appData.autoKibitz && started == STARTED_NONE && 
2418                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2419                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420                 if(looking_at(buf, &i, "* kibitzes: ") &&
2421                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2422                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2423                         suppressKibitz = TRUE;
2424                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425                                 && (gameMode == IcsPlayingWhite)) ||
2426                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2428                             started = STARTED_CHATTER; // own kibitz we simply discard
2429                         else {
2430                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431                             parse_pos = 0; parse[0] = NULLCHAR;
2432                             savingComment = TRUE;
2433                             suppressKibitz = gameMode != IcsObserving ? 2 :
2434                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435                         } 
2436                         continue;
2437                 } else
2438                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439                     started = STARTED_CHATTER;
2440                     suppressKibitz = TRUE;
2441                 }
2442             } // [HGM] kibitz: end of patch
2443
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2445
2446             // [HGM] chat: intercept tells by users for which we have an open chat window
2447             channel = -1;
2448             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2449                                            looking_at(buf, &i, "* whispers:") ||
2450                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2452                 int p;
2453                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454                 chattingPartner = -1;
2455
2456                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457                 for(p=0; p<MAX_CHAT; p++) {
2458                     if(channel == atoi(chatPartner[p])) {
2459                     talker[0] = '['; strcat(talker, "]");
2460                     chattingPartner = p; break;
2461                     }
2462                 } else
2463                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464                 for(p=0; p<MAX_CHAT; p++) {
2465                     if(!strcmp("WHISPER", chatPartner[p])) {
2466                         talker[0] = '['; strcat(talker, "]");
2467                         chattingPartner = p; break;
2468                     }
2469                 }
2470                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2472                     talker[0] = 0;
2473                     chattingPartner = p; break;
2474                 }
2475                 if(chattingPartner<0) i = oldi; else {
2476                     started = STARTED_COMMENT;
2477                     parse_pos = 0; parse[0] = NULLCHAR;
2478                     savingComment = TRUE;
2479                     suppressKibitz = TRUE;
2480                 }
2481             } // [HGM] chat: end of patch
2482
2483             if (appData.zippyTalk || appData.zippyPlay) {
2484                 /* [DM] Backup address for color zippy lines */
2485                 backup = i;
2486 #if ZIPPY
2487        #ifdef WIN32
2488                if (loggedOn == TRUE)
2489                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2491        #else
2492                 if (ZippyControl(buf, &i) ||
2493                     ZippyConverse(buf, &i) ||
2494                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2495                       loggedOn = TRUE;
2496                       if (!appData.colorize) continue;
2497                 }
2498        #endif
2499 #endif
2500             } // [DM] 'else { ' deleted
2501                 if (
2502                     /* Regular tells and says */
2503                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2505                     looking_at(buf, &i, "* says: ") ||
2506                     /* Don't color "message" or "messages" output */
2507                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508                     looking_at(buf, &i, "*. * at *:*: ") ||
2509                     looking_at(buf, &i, "--* (*:*): ") ||
2510                     /* Message notifications (same color as tells) */
2511                     looking_at(buf, &i, "* has left a message ") ||
2512                     looking_at(buf, &i, "* just sent you a message:\n") ||
2513                     /* Whispers and kibitzes */
2514                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515                     looking_at(buf, &i, "* kibitzes: ") ||
2516                     /* Channel tells */
2517                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2518
2519                   if (tkind == 1 && strchr(star_match[0], ':')) {
2520                       /* Avoid "tells you:" spoofs in channels */
2521                      tkind = 3;
2522                   }
2523                   if (star_match[0][0] == NULLCHAR ||
2524                       strchr(star_match[0], ' ') ||
2525                       (tkind == 3 && strchr(star_match[1], ' '))) {
2526                     /* Reject bogus matches */
2527                     i = oldi;
2528                   } else {
2529                     if (appData.colorize) {
2530                       if (oldi > next_out) {
2531                         SendToPlayer(&buf[next_out], oldi - next_out);
2532                         next_out = oldi;
2533                       }
2534                       switch (tkind) {
2535                       case 1:
2536                         Colorize(ColorTell, FALSE);
2537                         curColor = ColorTell;
2538                         break;
2539                       case 2:
2540                         Colorize(ColorKibitz, FALSE);
2541                         curColor = ColorKibitz;
2542                         break;
2543                       case 3:
2544                         p = strrchr(star_match[1], '(');
2545                         if (p == NULL) {
2546                           p = star_match[1];
2547                         } else {
2548                           p++;
2549                         }
2550                         if (atoi(p) == 1) {
2551                           Colorize(ColorChannel1, FALSE);
2552                           curColor = ColorChannel1;
2553                         } else {
2554                           Colorize(ColorChannel, FALSE);
2555                           curColor = ColorChannel;
2556                         }
2557                         break;
2558                       case 5:
2559                         curColor = ColorNormal;
2560                         break;
2561                       }
2562                     }
2563                     if (started == STARTED_NONE && appData.autoComment &&
2564                         (gameMode == IcsObserving ||
2565                          gameMode == IcsPlayingWhite ||
2566                          gameMode == IcsPlayingBlack)) {
2567                       parse_pos = i - oldi;
2568                       memcpy(parse, &buf[oldi], parse_pos);
2569                       parse[parse_pos] = NULLCHAR;
2570                       started = STARTED_COMMENT;
2571                       savingComment = TRUE;
2572                     } else {
2573                       started = STARTED_CHATTER;
2574                       savingComment = FALSE;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                   }
2579                 }
2580
2581                 if (looking_at(buf, &i, "* s-shouts: ") ||
2582                     looking_at(buf, &i, "* c-shouts: ")) {
2583                     if (appData.colorize) {
2584                         if (oldi > next_out) {
2585                             SendToPlayer(&buf[next_out], oldi - next_out);
2586                             next_out = oldi;
2587                         }
2588                         Colorize(ColorSShout, FALSE);
2589                         curColor = ColorSShout;
2590                     }
2591                     loggedOn = TRUE;
2592                     started = STARTED_CHATTER;
2593                     continue;
2594                 }
2595
2596                 if (looking_at(buf, &i, "--->")) {
2597                     loggedOn = TRUE;
2598                     continue;
2599                 }
2600
2601                 if (looking_at(buf, &i, "* shouts: ") ||
2602                     looking_at(buf, &i, "--> ")) {
2603                     if (appData.colorize) {
2604                         if (oldi > next_out) {
2605                             SendToPlayer(&buf[next_out], oldi - next_out);
2606                             next_out = oldi;
2607                         }
2608                         Colorize(ColorShout, FALSE);
2609                         curColor = ColorShout;
2610                     }
2611                     loggedOn = TRUE;
2612                     started = STARTED_CHATTER;
2613                     continue;
2614                 }
2615
2616                 if (looking_at( buf, &i, "Challenge:")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorChallenge, FALSE);
2623                         curColor = ColorChallenge;
2624                     }
2625                     loggedOn = TRUE;
2626                     continue;
2627                 }
2628
2629                 if (looking_at(buf, &i, "* offers you") ||
2630                     looking_at(buf, &i, "* offers to be") ||
2631                     looking_at(buf, &i, "* would like to") ||
2632                     looking_at(buf, &i, "* requests to") ||
2633                     looking_at(buf, &i, "Your opponent offers") ||
2634                     looking_at(buf, &i, "Your opponent requests")) {
2635
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorRequest, FALSE);
2642                         curColor = ColorRequest;
2643                     }
2644                     continue;
2645                 }
2646
2647                 if (looking_at(buf, &i, "* (*) seeking")) {
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorSeek, FALSE);
2654                         curColor = ColorSeek;
2655                     }
2656                     continue;
2657             }
2658
2659             if (looking_at(buf, &i, "\\   ")) {
2660                 if (prevColor != ColorNormal) {
2661                     if (oldi > next_out) {
2662                         SendToPlayer(&buf[next_out], oldi - next_out);
2663                         next_out = oldi;
2664                     }
2665                     Colorize(prevColor, TRUE);
2666                     curColor = prevColor;
2667                 }
2668                 if (savingComment) {
2669                     parse_pos = i - oldi;
2670                     memcpy(parse, &buf[oldi], parse_pos);
2671                     parse[parse_pos] = NULLCHAR;
2672                     started = STARTED_COMMENT;
2673                 } else {
2674                     started = STARTED_CHATTER;
2675                 }
2676                 continue;
2677             }
2678
2679             if (looking_at(buf, &i, "Black Strength :") ||
2680                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681                 looking_at(buf, &i, "<10>") ||
2682                 looking_at(buf, &i, "#@#")) {
2683                 /* Wrong board style */
2684                 loggedOn = TRUE;
2685                 SendToICS(ics_prefix);
2686                 SendToICS("set style 12\n");
2687                 SendToICS(ics_prefix);
2688                 SendToICS("refresh\n");
2689                 continue;
2690             }
2691             
2692             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2693                 ICSInitScript();
2694                 have_sent_ICS_logon = 1;
2695                 continue;
2696             }
2697               
2698             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2699                 (looking_at(buf, &i, "\n<12> ") ||
2700                  looking_at(buf, &i, "<12> "))) {
2701                 loggedOn = TRUE;
2702                 if (oldi > next_out) {
2703                     SendToPlayer(&buf[next_out], oldi - next_out);
2704                 }
2705                 next_out = i;
2706                 started = STARTED_BOARD;
2707                 parse_pos = 0;
2708                 continue;
2709             }
2710
2711             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712                 looking_at(buf, &i, "<b1> ")) {
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_HOLDINGS;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2723                 loggedOn = TRUE;
2724                 /* Header for a move list -- first line */
2725
2726                 switch (ics_getting_history) {
2727                   case H_FALSE:
2728                     switch (gameMode) {
2729                       case IcsIdle:
2730                       case BeginningOfGame:
2731                         /* User typed "moves" or "oldmoves" while we
2732                            were idle.  Pretend we asked for these
2733                            moves and soak them up so user can step
2734                            through them and/or save them.
2735                            */
2736                         Reset(FALSE, TRUE);
2737                         gameMode = IcsObserving;
2738                         ModeHighlight();
2739                         ics_gamenum = -1;
2740                         ics_getting_history = H_GOT_UNREQ_HEADER;
2741                         break;
2742                       case EditGame: /*?*/
2743                       case EditPosition: /*?*/
2744                         /* Should above feature work in these modes too? */
2745                         /* For now it doesn't */
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                       default:
2749                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2750                         break;
2751                     }
2752                     break;
2753                   case H_REQUESTED:
2754                     /* Is this the right one? */
2755                     if (gameInfo.white && gameInfo.black &&
2756                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2757                         strcmp(gameInfo.black, star_match[2]) == 0) {
2758                         /* All is well */
2759                         ics_getting_history = H_GOT_REQ_HEADER;
2760                     }
2761                     break;
2762                   case H_GOT_REQ_HEADER:
2763                   case H_GOT_UNREQ_HEADER:
2764                   case H_GOT_UNWANTED_HEADER:
2765                   case H_GETTING_MOVES:
2766                     /* Should not happen */
2767                     DisplayError(_("Error gathering move list: two headers"), 0);
2768                     ics_getting_history = H_FALSE;
2769                     break;
2770                 }
2771
2772                 /* Save player ratings into gameInfo if needed */
2773                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775                     (gameInfo.whiteRating == -1 ||
2776                      gameInfo.blackRating == -1)) {
2777
2778                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2779                     gameInfo.blackRating = string_to_rating(star_match[3]);
2780                     if (appData.debugMode)
2781                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2782                               gameInfo.whiteRating, gameInfo.blackRating);
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i,
2788               "* * match, initial time: * minute*, increment: * second")) {
2789                 /* Header for a move list -- second line */
2790                 /* Initial board will follow if this is a wild game */
2791                 if (gameInfo.event != NULL) free(gameInfo.event);
2792                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793                 gameInfo.event = StrSave(str);
2794                 /* [HGM] we switched variant. Translate boards if needed. */
2795                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i, "Move  ")) {
2800                 /* Beginning of a move list */
2801                 switch (ics_getting_history) {
2802                   case H_FALSE:
2803                     /* Normally should not happen */
2804                     /* Maybe user hit reset while we were parsing */
2805                     break;
2806                   case H_REQUESTED:
2807                     /* Happens if we are ignoring a move list that is not
2808                      * the one we just requested.  Common if the user
2809                      * tries to observe two games without turning off
2810                      * getMoveList */
2811                     break;
2812                   case H_GETTING_MOVES:
2813                     /* Should not happen */
2814                     DisplayError(_("Error gathering move list: nested"), 0);
2815                     ics_getting_history = H_FALSE;
2816                     break;
2817                   case H_GOT_REQ_HEADER:
2818                     ics_getting_history = H_GETTING_MOVES;
2819                     started = STARTED_MOVES;
2820                     parse_pos = 0;
2821                     if (oldi > next_out) {
2822                         SendToPlayer(&buf[next_out], oldi - next_out);
2823                     }
2824                     break;
2825                   case H_GOT_UNREQ_HEADER:
2826                     ics_getting_history = H_GETTING_MOVES;
2827                     started = STARTED_MOVES_NOHIDE;
2828                     parse_pos = 0;
2829                     break;
2830                   case H_GOT_UNWANTED_HEADER:
2831                     ics_getting_history = H_FALSE;
2832                     break;
2833                 }
2834                 continue;
2835             }                           
2836             
2837             if (looking_at(buf, &i, "% ") ||
2838                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840                 savingComment = FALSE;
2841                 switch (started) {
2842                   case STARTED_MOVES:
2843                   case STARTED_MOVES_NOHIDE:
2844                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845                     parse[parse_pos + i - oldi] = NULLCHAR;
2846                     ParseGameHistory(parse);
2847 #if ZIPPY
2848                     if (appData.zippyPlay && first.initDone) {
2849                         FeedMovesToProgram(&first, forwardMostMove);
2850                         if (gameMode == IcsPlayingWhite) {
2851                             if (WhiteOnMove(forwardMostMove)) {
2852                                 if (first.sendTime) {
2853                                   if (first.useColors) {
2854                                     SendToProgram("black\n", &first); 
2855                                   }
2856                                   SendTimeRemaining(&first, TRUE);
2857                                 }
2858                                 if (first.useColors) {
2859                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2860                                 }
2861                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862                                 first.maybeThinking = TRUE;
2863                             } else {
2864                                 if (first.usePlayother) {
2865                                   if (first.sendTime) {
2866                                     SendTimeRemaining(&first, TRUE);
2867                                   }
2868                                   SendToProgram("playother\n", &first);
2869                                   firstMove = FALSE;
2870                                 } else {
2871                                   firstMove = TRUE;
2872                                 }
2873                             }
2874                         } else if (gameMode == IcsPlayingBlack) {
2875                             if (!WhiteOnMove(forwardMostMove)) {
2876                                 if (first.sendTime) {
2877                                   if (first.useColors) {
2878                                     SendToProgram("white\n", &first);
2879                                   }
2880                                   SendTimeRemaining(&first, FALSE);
2881                                 }
2882                                 if (first.useColors) {
2883                                   SendToProgram("black\n", &first);
2884                                 }
2885                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886                                 first.maybeThinking = TRUE;
2887                             } else {
2888                                 if (first.usePlayother) {
2889                                   if (first.sendTime) {
2890                                     SendTimeRemaining(&first, FALSE);
2891                                   }
2892                                   SendToProgram("playother\n", &first);
2893                                   firstMove = FALSE;
2894                                 } else {
2895                                   firstMove = TRUE;
2896                                 }
2897                             }
2898                         }                       
2899                     }
2900 #endif
2901                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2902                         /* Moves came from oldmoves or moves command
2903                            while we weren't doing anything else.
2904                            */
2905                         currentMove = forwardMostMove;
2906                         ClearHighlights();/*!!could figure this out*/
2907                         flipView = appData.flipView;
2908                         DrawPosition(TRUE, boards[currentMove]);
2909                         DisplayBothClocks();
2910                         sprintf(str, "%s vs. %s",
2911                                 gameInfo.white, gameInfo.black);
2912                         DisplayTitle(str);
2913                         gameMode = IcsIdle;
2914                     } else {
2915                         /* Moves were history of an active game */
2916                         if (gameInfo.resultDetails != NULL) {
2917                             free(gameInfo.resultDetails);
2918                             gameInfo.resultDetails = NULL;
2919                         }
2920                     }
2921                     HistorySet(parseList, backwardMostMove,
2922                                forwardMostMove, currentMove-1);
2923                     DisplayMove(currentMove - 1);
2924                     if (started == STARTED_MOVES) next_out = i;
2925                     started = STARTED_NONE;
2926                     ics_getting_history = H_FALSE;
2927                     break;
2928
2929                   case STARTED_OBSERVE:
2930                     started = STARTED_NONE;
2931                     SendToICS(ics_prefix);
2932                     SendToICS("refresh\n");
2933                     break;
2934
2935                   default:
2936                     break;
2937                 }
2938                 if(bookHit) { // [HGM] book: simulate book reply
2939                     static char bookMove[MSG_SIZ]; // a bit generous?
2940
2941                     programStats.nodes = programStats.depth = programStats.time = 
2942                     programStats.score = programStats.got_only_move = 0;
2943                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2944
2945                     strcpy(bookMove, "move ");
2946                     strcat(bookMove, bookHit);
2947                     HandleMachineMove(bookMove, &first);
2948                 }
2949                 continue;
2950             }
2951             
2952             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953                  started == STARTED_HOLDINGS ||
2954                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955                 /* Accumulate characters in move list or board */
2956                 parse[parse_pos++] = buf[i];
2957             }
2958             
2959             /* Start of game messages.  Mostly we detect start of game
2960                when the first board image arrives.  On some versions
2961                of the ICS, though, we need to do a "refresh" after starting
2962                to observe in order to get the current board right away. */
2963             if (looking_at(buf, &i, "Adding game * to observation list")) {
2964                 started = STARTED_OBSERVE;
2965                 continue;
2966             }
2967
2968             /* Handle auto-observe */
2969             if (appData.autoObserve &&
2970                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2972                 char *player;
2973                 /* Choose the player that was highlighted, if any. */
2974                 if (star_match[0][0] == '\033' ||
2975                     star_match[1][0] != '\033') {
2976                     player = star_match[0];
2977                 } else {
2978                     player = star_match[2];
2979                 }
2980                 sprintf(str, "%sobserve %s\n",
2981                         ics_prefix, StripHighlightAndTitle(player));
2982                 SendToICS(str);
2983
2984                 /* Save ratings from notify string */
2985                 strcpy(player1Name, star_match[0]);
2986                 player1Rating = string_to_rating(star_match[1]);
2987                 strcpy(player2Name, star_match[2]);
2988                 player2Rating = string_to_rating(star_match[3]);
2989
2990                 if (appData.debugMode)
2991                   fprintf(debugFP, 
2992                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2993                           player1Name, player1Rating,
2994                           player2Name, player2Rating);
2995
2996                 continue;
2997             }
2998
2999             /* Deal with automatic examine mode after a game,
3000                and with IcsObserving -> IcsExamining transition */
3001             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002                 looking_at(buf, &i, "has made you an examiner of game *")) {
3003
3004                 int gamenum = atoi(star_match[0]);
3005                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006                     gamenum == ics_gamenum) {
3007                     /* We were already playing or observing this game;
3008                        no need to refetch history */
3009                     gameMode = IcsExamining;
3010                     if (pausing) {
3011                         pauseExamForwardMostMove = forwardMostMove;
3012                     } else if (currentMove < forwardMostMove) {
3013                         ForwardInner(forwardMostMove);
3014                     }
3015                 } else {
3016                     /* I don't think this case really can happen */
3017                     SendToICS(ics_prefix);
3018                     SendToICS("refresh\n");
3019                 }
3020                 continue;
3021             }    
3022             
3023             /* Error messages */
3024 //          if (ics_user_moved) {
3025             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026                 if (looking_at(buf, &i, "Illegal move") ||
3027                     looking_at(buf, &i, "Not a legal move") ||
3028                     looking_at(buf, &i, "Your king is in check") ||
3029                     looking_at(buf, &i, "It isn't your turn") ||
3030                     looking_at(buf, &i, "It is not your move")) {
3031                     /* Illegal move */
3032                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033                         currentMove = --forwardMostMove;
3034                         DisplayMove(currentMove - 1); /* before DMError */
3035                         DrawPosition(FALSE, boards[currentMove]);
3036                         SwitchClocks();
3037                         DisplayBothClocks();
3038                     }
3039                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3040                     ics_user_moved = 0;
3041                     continue;
3042                 }
3043             }
3044
3045             if (looking_at(buf, &i, "still have time") ||
3046                 looking_at(buf, &i, "not out of time") ||
3047                 looking_at(buf, &i, "either player is out of time") ||
3048                 looking_at(buf, &i, "has timeseal; checking")) {
3049                 /* We must have called his flag a little too soon */
3050                 whiteFlag = blackFlag = FALSE;
3051                 continue;
3052             }
3053
3054             if (looking_at(buf, &i, "added * seconds to") ||
3055                 looking_at(buf, &i, "seconds were added to")) {
3056                 /* Update the clocks */
3057                 SendToICS(ics_prefix);
3058                 SendToICS("refresh\n");
3059                 continue;
3060             }
3061
3062             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063                 ics_clock_paused = TRUE;
3064                 StopClocks();
3065                 continue;
3066             }
3067
3068             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069                 ics_clock_paused = FALSE;
3070                 StartClocks();
3071                 continue;
3072             }
3073
3074             /* Grab player ratings from the Creating: message.
3075                Note we have to check for the special case when
3076                the ICS inserts things like [white] or [black]. */
3077             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3079                 /* star_matches:
3080                    0    player 1 name (not necessarily white)
3081                    1    player 1 rating
3082                    2    empty, white, or black (IGNORED)
3083                    3    player 2 name (not necessarily black)
3084                    4    player 2 rating
3085                    
3086                    The names/ratings are sorted out when the game
3087                    actually starts (below).
3088                 */
3089                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090                 player1Rating = string_to_rating(star_match[1]);
3091                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092                 player2Rating = string_to_rating(star_match[4]);
3093
3094                 if (appData.debugMode)
3095                   fprintf(debugFP, 
3096                           "Ratings from 'Creating:' %s %d, %s %d\n",
3097                           player1Name, player1Rating,
3098                           player2Name, player2Rating);
3099
3100                 continue;
3101             }
3102             
3103             /* Improved generic start/end-of-game messages */
3104             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106                 /* If tkind == 0: */
3107                 /* star_match[0] is the game number */
3108                 /*           [1] is the white player's name */
3109                 /*           [2] is the black player's name */
3110                 /* For end-of-game: */
3111                 /*           [3] is the reason for the game end */
3112                 /*           [4] is a PGN end game-token, preceded by " " */
3113                 /* For start-of-game: */
3114                 /*           [3] begins with "Creating" or "Continuing" */
3115                 /*           [4] is " *" or empty (don't care). */
3116                 int gamenum = atoi(star_match[0]);
3117                 char *whitename, *blackname, *why, *endtoken;
3118                 ChessMove endtype = (ChessMove) 0;
3119
3120                 if (tkind == 0) {
3121                   whitename = star_match[1];
3122                   blackname = star_match[2];
3123                   why = star_match[3];
3124                   endtoken = star_match[4];
3125                 } else {
3126                   whitename = star_match[1];
3127                   blackname = star_match[3];
3128                   why = star_match[5];
3129                   endtoken = star_match[6];
3130                 }
3131
3132                 /* Game start messages */
3133                 if (strncmp(why, "Creating ", 9) == 0 ||
3134                     strncmp(why, "Continuing ", 11) == 0) {
3135                     gs_gamenum = gamenum;
3136                     strcpy(gs_kind, strchr(why, ' ') + 1);
3137                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3138 #if ZIPPY
3139                     if (appData.zippyPlay) {
3140                         ZippyGameStart(whitename, blackname);
3141                     }
3142 #endif /*ZIPPY*/
3143                     continue;
3144                 }
3145
3146                 /* Game end messages */
3147                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3148                     ics_gamenum != gamenum) {
3149                     continue;
3150                 }
3151                 while (endtoken[0] == ' ') endtoken++;
3152                 switch (endtoken[0]) {
3153                   case '*':
3154                   default:
3155                     endtype = GameUnfinished;
3156                     break;
3157                   case '0':
3158                     endtype = BlackWins;
3159                     break;
3160                   case '1':
3161                     if (endtoken[1] == '/')
3162                       endtype = GameIsDrawn;
3163                     else
3164                       endtype = WhiteWins;
3165                     break;
3166                 }
3167                 GameEnds(endtype, why, GE_ICS);
3168 #if ZIPPY
3169                 if (appData.zippyPlay && first.initDone) {
3170                     ZippyGameEnd(endtype, why);
3171                     if (first.pr == NULL) {
3172                       /* Start the next process early so that we'll
3173                          be ready for the next challenge */
3174                       StartChessProgram(&first);
3175                     }
3176                     /* Send "new" early, in case this command takes
3177                        a long time to finish, so that we'll be ready
3178                        for the next challenge. */
3179                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3180                     Reset(TRUE, TRUE);
3181                 }
3182 #endif /*ZIPPY*/
3183                 continue;
3184             }
3185
3186             if (looking_at(buf, &i, "Removing game * from observation") ||
3187                 looking_at(buf, &i, "no longer observing game *") ||
3188                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3189                 if (gameMode == IcsObserving &&
3190                     atoi(star_match[0]) == ics_gamenum)
3191                   {
3192                       /* icsEngineAnalyze */
3193                       if (appData.icsEngineAnalyze) {
3194                             ExitAnalyzeMode();
3195                             ModeHighlight();
3196                       }
3197                       StopClocks();
3198                       gameMode = IcsIdle;
3199                       ics_gamenum = -1;
3200                       ics_user_moved = FALSE;
3201                   }
3202                 continue;
3203             }
3204
3205             if (looking_at(buf, &i, "no longer examining game *")) {
3206                 if (gameMode == IcsExamining &&
3207                     atoi(star_match[0]) == ics_gamenum)
3208                   {
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             /* Advance leftover_start past any newlines we find,
3217                so only partial lines can get reparsed */
3218             if (looking_at(buf, &i, "\n")) {
3219                 prevColor = curColor;
3220                 if (curColor != ColorNormal) {
3221                     if (oldi > next_out) {
3222                         SendToPlayer(&buf[next_out], oldi - next_out);
3223                         next_out = oldi;
3224                     }
3225                     Colorize(ColorNormal, FALSE);
3226                     curColor = ColorNormal;
3227                 }
3228                 if (started == STARTED_BOARD) {
3229                     started = STARTED_NONE;
3230                     parse[parse_pos] = NULLCHAR;
3231                     ParseBoard12(parse);
3232                     ics_user_moved = 0;
3233
3234                     /* Send premove here */
3235                     if (appData.premove) {
3236                       char str[MSG_SIZ];
3237                       if (currentMove == 0 &&
3238                           gameMode == IcsPlayingWhite &&
3239                           appData.premoveWhite) {
3240                         sprintf(str, "%s\n", appData.premoveWhiteText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (currentMove == 1 &&
3245                                  gameMode == IcsPlayingBlack &&
3246                                  appData.premoveBlack) {
3247                         sprintf(str, "%s\n", appData.premoveBlackText);
3248                         if (appData.debugMode)
3249                           fprintf(debugFP, "Sending premove:\n");
3250                         SendToICS(str);
3251                       } else if (gotPremove) {
3252                         gotPremove = 0;
3253                         ClearPremoveHighlights();
3254                         if (appData.debugMode)
3255                           fprintf(debugFP, "Sending premove:\n");
3256                           UserMoveEvent(premoveFromX, premoveFromY, 
3257                                         premoveToX, premoveToY, 
3258                                         premovePromoChar);
3259                       }
3260                     }
3261
3262                     /* Usually suppress following prompt */
3263                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3264                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3265                         if (looking_at(buf, &i, "*% ")) {
3266                             savingComment = FALSE;
3267                         }
3268                     }
3269                     next_out = i;
3270                 } else if (started == STARTED_HOLDINGS) {
3271                     int gamenum;
3272                     char new_piece[MSG_SIZ];
3273                     started = STARTED_NONE;
3274                     parse[parse_pos] = NULLCHAR;
3275                     if (appData.debugMode)
3276                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3277                                                         parse, currentMove);
3278                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3279                         gamenum == ics_gamenum) {
3280                         if (gameInfo.variant == VariantNormal) {
3281                           /* [HGM] We seem to switch variant during a game!
3282                            * Presumably no holdings were displayed, so we have
3283                            * to move the position two files to the right to
3284                            * create room for them!
3285                            */
3286                           VariantClass newVariant;
3287                           switch(gameInfo.boardWidth) { // base guess on board width
3288                                 case 9:  newVariant = VariantShogi; break;
3289                                 case 10: newVariant = VariantGreat; break;
3290                                 default: newVariant = VariantCrazyhouse; break;
3291                           }
3292                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3293                           /* Get a move list just to see the header, which
3294                              will tell us whether this is really bug or zh */
3295                           if (ics_getting_history == H_FALSE) {
3296                             ics_getting_history = H_REQUESTED;
3297                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3298                             SendToICS(str);
3299                           }
3300                         }
3301                         new_piece[0] = NULLCHAR;
3302                         sscanf(parse, "game %d white [%s black [%s <- %s",
3303                                &gamenum, white_holding, black_holding,
3304                                new_piece);
3305                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3306                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3307                         /* [HGM] copy holdings to board holdings area */
3308                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3309                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3310                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3311 #if ZIPPY
3312                         if (appData.zippyPlay && first.initDone) {
3313                             ZippyHoldings(white_holding, black_holding,
3314                                           new_piece);
3315                         }
3316 #endif /*ZIPPY*/
3317                         if (tinyLayout || smallLayout) {
3318                             char wh[16], bh[16];
3319                             PackHolding(wh, white_holding);
3320                             PackHolding(bh, black_holding);
3321                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3322                                     gameInfo.white, gameInfo.black);
3323                         } else {
3324                             sprintf(str, "%s [%s] vs. %s [%s]",
3325                                     gameInfo.white, white_holding,
3326                                     gameInfo.black, black_holding);
3327                         }
3328
3329                         DrawPosition(FALSE, boards[currentMove]);
3330                         DisplayTitle(str);
3331                     }
3332                     /* Suppress following prompt */
3333                     if (looking_at(buf, &i, "*% ")) {
3334                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3335                         savingComment = FALSE;
3336                     }
3337                     next_out = i;
3338                 }
3339                 continue;
3340             }
3341
3342             i++;                /* skip unparsed character and loop back */
3343         }
3344         
3345         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3346             started != STARTED_HOLDINGS && i > next_out) {
3347             SendToPlayer(&buf[next_out], i - next_out);
3348             next_out = i;
3349         }
3350         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3351         
3352         leftover_len = buf_len - leftover_start;
3353         /* if buffer ends with something we couldn't parse,
3354            reparse it after appending the next read */
3355         
3356     } else if (count == 0) {
3357         RemoveInputSource(isr);
3358         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3359     } else {
3360         DisplayFatalError(_("Error reading from ICS"), error, 1);
3361     }
3362 }
3363
3364
3365 /* Board style 12 looks like this:
3366    
3367    <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
3368    
3369  * The "<12> " is stripped before it gets to this routine.  The two
3370  * trailing 0's (flip state and clock ticking) are later addition, and
3371  * some chess servers may not have them, or may have only the first.
3372  * Additional trailing fields may be added in the future.  
3373  */
3374
3375 #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"
3376
3377 #define RELATION_OBSERVING_PLAYED    0
3378 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3379 #define RELATION_PLAYING_MYMOVE      1
3380 #define RELATION_PLAYING_NOTMYMOVE  -1
3381 #define RELATION_EXAMINING           2
3382 #define RELATION_ISOLATED_BOARD     -3
3383 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3384
3385 void
3386 ParseBoard12(string)
3387      char *string;
3388
3389     GameMode newGameMode;
3390     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3391     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3392     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3393     char to_play, board_chars[200];
3394     char move_str[500], str[500], elapsed_time[500];
3395     char black[32], white[32];
3396     Board board;
3397     int prevMove = currentMove;
3398     int ticking = 2;
3399     ChessMove moveType;
3400     int fromX, fromY, toX, toY;
3401     char promoChar;
3402     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3403     char *bookHit = NULL; // [HGM] book
3404     Boolean weird = FALSE, reqFlag = FALSE;
3405
3406     fromX = fromY = toX = toY = -1;
3407     
3408     newGame = FALSE;
3409
3410     if (appData.debugMode)
3411       fprintf(debugFP, _("Parsing board: %s\n"), string);
3412
3413     move_str[0] = NULLCHAR;
3414     elapsed_time[0] = NULLCHAR;
3415     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3416         int  i = 0, j;
3417         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3418             if(string[i] == ' ') { ranks++; files = 0; }
3419             else files++;
3420             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3421             i++;
3422         }
3423         for(j = 0; j <i; j++) board_chars[j] = string[j];
3424         board_chars[i] = '\0';
3425         string += i + 1;
3426     }
3427     n = sscanf(string, PATTERN, &to_play, &double_push,
3428                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3429                &gamenum, white, black, &relation, &basetime, &increment,
3430                &white_stren, &black_stren, &white_time, &black_time,
3431                &moveNum, str, elapsed_time, move_str, &ics_flip,
3432                &ticking);
3433
3434     if (n < 21) {
3435         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3436         DisplayError(str, 0);
3437         return;
3438     }
3439
3440     /* Convert the move number to internal form */
3441     moveNum = (moveNum - 1) * 2;
3442     if (to_play == 'B') moveNum++;
3443     if (moveNum >= MAX_MOVES) {
3444       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3445                         0, 1);
3446       return;
3447     }
3448     
3449     switch (relation) {
3450       case RELATION_OBSERVING_PLAYED:
3451       case RELATION_OBSERVING_STATIC:
3452         if (gamenum == -1) {
3453             /* Old ICC buglet */
3454             relation = RELATION_OBSERVING_STATIC;
3455         }
3456         newGameMode = IcsObserving;
3457         break;
3458       case RELATION_PLAYING_MYMOVE:
3459       case RELATION_PLAYING_NOTMYMOVE:
3460         newGameMode =
3461           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3462             IcsPlayingWhite : IcsPlayingBlack;
3463         break;
3464       case RELATION_EXAMINING:
3465         newGameMode = IcsExamining;
3466         break;
3467       case RELATION_ISOLATED_BOARD:
3468       default:
3469         /* Just display this board.  If user was doing something else,
3470            we will forget about it until the next board comes. */ 
3471         newGameMode = IcsIdle;
3472         break;
3473       case RELATION_STARTING_POSITION:
3474         newGameMode = gameMode;
3475         break;
3476     }
3477     
3478     /* Modify behavior for initial board display on move listing
3479        of wild games.
3480        */
3481     switch (ics_getting_history) {
3482       case H_FALSE:
3483       case H_REQUESTED:
3484         break;
3485       case H_GOT_REQ_HEADER:
3486       case H_GOT_UNREQ_HEADER:
3487         /* This is the initial position of the current game */
3488         gamenum = ics_gamenum;
3489         moveNum = 0;            /* old ICS bug workaround */
3490         if (to_play == 'B') {
3491           startedFromSetupPosition = TRUE;
3492           blackPlaysFirst = TRUE;
3493           moveNum = 1;
3494           if (forwardMostMove == 0) forwardMostMove = 1;
3495           if (backwardMostMove == 0) backwardMostMove = 1;
3496           if (currentMove == 0) currentMove = 1;
3497         }
3498         newGameMode = gameMode;
3499         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3500         break;
3501       case H_GOT_UNWANTED_HEADER:
3502         /* This is an initial board that we don't want */
3503         return;
3504       case H_GETTING_MOVES:
3505         /* Should not happen */
3506         DisplayError(_("Error gathering move list: extra board"), 0);
3507         ics_getting_history = H_FALSE;
3508         return;
3509     }
3510
3511    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3512                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3513      /* [HGM] We seem to have switched variant unexpectedly
3514       * Try to guess new variant from board size
3515       */
3516           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3517           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3518           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3519           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3520           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3521           if(!weird) newVariant = VariantNormal;
3522           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3523           /* Get a move list just to see the header, which
3524              will tell us whether this is really bug or zh */
3525           if (ics_getting_history == H_FALSE) {
3526             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3527             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3528             SendToICS(str);
3529           }
3530     }
3531     
3532     /* Take action if this is the first board of a new game, or of a
3533        different game than is currently being displayed.  */
3534     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3535         relation == RELATION_ISOLATED_BOARD) {
3536         
3537         /* Forget the old game and get the history (if any) of the new one */
3538         if (gameMode != BeginningOfGame) {
3539           Reset(TRUE, TRUE);
3540         }
3541         newGame = TRUE;
3542         if (appData.autoRaiseBoard) BoardToTop();
3543         prevMove = -3;
3544         if (gamenum == -1) {
3545             newGameMode = IcsIdle;
3546         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3547                    appData.getMoveList && !reqFlag) {
3548             /* Need to get game history */
3549             ics_getting_history = H_REQUESTED;
3550             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3551             SendToICS(str);
3552         }
3553         
3554         /* Initially flip the board to have black on the bottom if playing
3555            black or if the ICS flip flag is set, but let the user change
3556            it with the Flip View button. */
3557         flipView = appData.autoFlipView ? 
3558           (newGameMode == IcsPlayingBlack) || ics_flip :
3559           appData.flipView;
3560         
3561         /* Done with values from previous mode; copy in new ones */
3562         gameMode = newGameMode;
3563         ModeHighlight();
3564         ics_gamenum = gamenum;
3565         if (gamenum == gs_gamenum) {
3566             int klen = strlen(gs_kind);
3567             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3568             sprintf(str, "ICS %s", gs_kind);
3569             gameInfo.event = StrSave(str);
3570         } else {
3571             gameInfo.event = StrSave("ICS game");
3572         }
3573         gameInfo.site = StrSave(appData.icsHost);
3574         gameInfo.date = PGNDate();
3575         gameInfo.round = StrSave("-");
3576         gameInfo.white = StrSave(white);
3577         gameInfo.black = StrSave(black);
3578         timeControl = basetime * 60 * 1000;
3579         timeControl_2 = 0;
3580         timeIncrement = increment * 1000;
3581         movesPerSession = 0;
3582         gameInfo.timeControl = TimeControlTagValue();
3583         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3584   if (appData.debugMode) {
3585     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3586     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3587     setbuf(debugFP, NULL);
3588   }
3589
3590         gameInfo.outOfBook = NULL;
3591         
3592         /* Do we have the ratings? */
3593         if (strcmp(player1Name, white) == 0 &&
3594             strcmp(player2Name, black) == 0) {
3595             if (appData.debugMode)
3596               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3597                       player1Rating, player2Rating);
3598             gameInfo.whiteRating = player1Rating;
3599             gameInfo.blackRating = player2Rating;
3600         } else if (strcmp(player2Name, white) == 0 &&
3601                    strcmp(player1Name, black) == 0) {
3602             if (appData.debugMode)
3603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3604                       player2Rating, player1Rating);
3605             gameInfo.whiteRating = player2Rating;
3606             gameInfo.blackRating = player1Rating;
3607         }
3608         player1Name[0] = player2Name[0] = NULLCHAR;
3609
3610         /* Silence shouts if requested */
3611         if (appData.quietPlay &&
3612             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3613             SendToICS(ics_prefix);
3614             SendToICS("set shout 0\n");
3615         }
3616     }
3617     
3618     /* Deal with midgame name changes */
3619     if (!newGame) {
3620         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3621             if (gameInfo.white) free(gameInfo.white);
3622             gameInfo.white = StrSave(white);
3623         }
3624         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3625             if (gameInfo.black) free(gameInfo.black);
3626             gameInfo.black = StrSave(black);
3627         }
3628     }
3629     
3630     /* Throw away game result if anything actually changes in examine mode */
3631     if (gameMode == IcsExamining && !newGame) {
3632         gameInfo.result = GameUnfinished;
3633         if (gameInfo.resultDetails != NULL) {
3634             free(gameInfo.resultDetails);
3635             gameInfo.resultDetails = NULL;
3636         }
3637     }
3638     
3639     /* In pausing && IcsExamining mode, we ignore boards coming
3640        in if they are in a different variation than we are. */
3641     if (pauseExamInvalid) return;
3642     if (pausing && gameMode == IcsExamining) {
3643         if (moveNum <= pauseExamForwardMostMove) {
3644             pauseExamInvalid = TRUE;
3645             forwardMostMove = pauseExamForwardMostMove;
3646             return;
3647         }
3648     }
3649     
3650   if (appData.debugMode) {
3651     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3652   }
3653     /* Parse the board */
3654     for (k = 0; k < ranks; k++) {
3655       for (j = 0; j < files; j++)
3656         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3657       if(gameInfo.holdingsWidth > 1) {
3658            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3659            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3660       }
3661     }
3662     CopyBoard(boards[moveNum], board);
3663     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3664     if (moveNum == 0) {
3665         startedFromSetupPosition =
3666           !CompareBoards(board, initialPosition);
3667         if(startedFromSetupPosition)
3668             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3669     }
3670
3671     /* [HGM] Set castling rights. Take the outermost Rooks,
3672        to make it also work for FRC opening positions. Note that board12
3673        is really defective for later FRC positions, as it has no way to
3674        indicate which Rook can castle if they are on the same side of King.
3675        For the initial position we grant rights to the outermost Rooks,
3676        and remember thos rights, and we then copy them on positions
3677        later in an FRC game. This means WB might not recognize castlings with
3678        Rooks that have moved back to their original position as illegal,
3679        but in ICS mode that is not its job anyway.
3680     */
3681     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3682     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3683
3684         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685             if(board[0][i] == WhiteRook) j = i;
3686         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688             if(board[0][i] == WhiteRook) j = i;
3689         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3693         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3694             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3695         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3696
3697         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3700         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3701             if(board[BOARD_HEIGHT-1][k] == bKing)
3702                 initialRights[5] = castlingRights[moveNum][5] = k;
3703     } else { int r;
3704         r = castlingRights[moveNum][0] = initialRights[0];
3705         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3706         r = castlingRights[moveNum][1] = initialRights[1];
3707         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3708         r = castlingRights[moveNum][3] = initialRights[3];
3709         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3710         r = castlingRights[moveNum][4] = initialRights[4];
3711         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3712         /* wildcastle kludge: always assume King has rights */
3713         r = castlingRights[moveNum][2] = initialRights[2];
3714         r = castlingRights[moveNum][5] = initialRights[5];
3715     }
3716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3717     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3718
3719     
3720     if (ics_getting_history == H_GOT_REQ_HEADER ||
3721         ics_getting_history == H_GOT_UNREQ_HEADER) {
3722         /* This was an initial position from a move list, not
3723            the current position */
3724         return;
3725     }
3726     
3727     /* Update currentMove and known move number limits */
3728     newMove = newGame || moveNum > forwardMostMove;
3729
3730     if (newGame) {
3731         forwardMostMove = backwardMostMove = currentMove = moveNum;
3732         if (gameMode == IcsExamining && moveNum == 0) {
3733           /* Workaround for ICS limitation: we are not told the wild
3734              type when starting to examine a game.  But if we ask for
3735              the move list, the move list header will tell us */
3736             ics_getting_history = H_REQUESTED;
3737             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3738             SendToICS(str);
3739         }
3740     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3741                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3742 #if ZIPPY
3743         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3744         /* [HGM] applied this also to an engine that is silently watching        */
3745         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3746             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3747             gameInfo.variant == currentlyInitializedVariant) {
3748           takeback = forwardMostMove - moveNum;
3749           for (i = 0; i < takeback; i++) {
3750             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3751             SendToProgram("undo\n", &first);
3752           }
3753         }
3754 #endif
3755
3756         forwardMostMove = moveNum;
3757         if (!pausing || currentMove > forwardMostMove)
3758           currentMove = forwardMostMove;
3759     } else {
3760         /* New part of history that is not contiguous with old part */ 
3761         if (pausing && gameMode == IcsExamining) {
3762             pauseExamInvalid = TRUE;
3763             forwardMostMove = pauseExamForwardMostMove;
3764             return;
3765         }
3766         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3767 #if ZIPPY
3768             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3769                 // [HGM] when we will receive the move list we now request, it will be
3770                 // fed to the engine from the first move on. So if the engine is not
3771                 // in the initial position now, bring it there.
3772                 InitChessProgram(&first, 0);
3773             }
3774 #endif
3775             ics_getting_history = H_REQUESTED;
3776             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777             SendToICS(str);
3778         }
3779         forwardMostMove = backwardMostMove = currentMove = moveNum;
3780     }
3781     
3782     /* Update the clocks */
3783     if (strchr(elapsed_time, '.')) {
3784       /* Time is in ms */
3785       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3786       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3787     } else {
3788       /* Time is in seconds */
3789       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3790       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3791     }
3792       
3793
3794 #if ZIPPY
3795     if (appData.zippyPlay && newGame &&
3796         gameMode != IcsObserving && gameMode != IcsIdle &&
3797         gameMode != IcsExamining)
3798       ZippyFirstBoard(moveNum, basetime, increment);
3799 #endif
3800     
3801     /* Put the move on the move list, first converting
3802        to canonical algebraic form. */
3803     if (moveNum > 0) {
3804   if (appData.debugMode) {
3805     if (appData.debugMode) { int f = forwardMostMove;
3806         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3807                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3808     }
3809     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3810     fprintf(debugFP, "moveNum = %d\n", moveNum);
3811     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3812     setbuf(debugFP, NULL);
3813   }
3814         if (moveNum <= backwardMostMove) {
3815             /* We don't know what the board looked like before
3816                this move.  Punt. */
3817             strcpy(parseList[moveNum - 1], move_str);
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             moveList[moveNum - 1][0] = NULLCHAR;
3821         } else if (strcmp(move_str, "none") == 0) {
3822             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3823             /* Again, we don't know what the board looked like;
3824                this is really the start of the game. */
3825             parseList[moveNum - 1][0] = NULLCHAR;
3826             moveList[moveNum - 1][0] = NULLCHAR;
3827             backwardMostMove = moveNum;
3828             startedFromSetupPosition = TRUE;
3829             fromX = fromY = toX = toY = -1;
3830         } else {
3831           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3832           //                 So we parse the long-algebraic move string in stead of the SAN move
3833           int valid; char buf[MSG_SIZ], *prom;
3834
3835           // str looks something like "Q/a1-a2"; kill the slash
3836           if(str[1] == '/') 
3837                 sprintf(buf, "%c%s", str[0], str+2);
3838           else  strcpy(buf, str); // might be castling
3839           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3840                 strcat(buf, prom); // long move lacks promo specification!
3841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3842                 if(appData.debugMode) 
3843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3844                 strcpy(move_str, buf);
3845           }
3846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar)
3848                || ParseOneMove(buf, moveNum - 1, &moveType,
3849                                 &fromX, &fromY, &toX, &toY, &promoChar);
3850           // end of long SAN patch
3851           if (valid) {
3852             (void) CoordsToAlgebraic(boards[moveNum - 1],
3853                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3854                                      fromY, fromX, toY, toX, promoChar,
3855                                      parseList[moveNum-1]);
3856             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3857                              castlingRights[moveNum]) ) {
3858               case MT_NONE:
3859               case MT_STALEMATE:
3860               default:
3861                 break;
3862               case MT_CHECK:
3863                 if(gameInfo.variant != VariantShogi)
3864                     strcat(parseList[moveNum - 1], "+");
3865                 break;
3866               case MT_CHECKMATE:
3867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3868                 strcat(parseList[moveNum - 1], "#");
3869                 break;
3870             }
3871             strcat(parseList[moveNum - 1], " ");
3872             strcat(parseList[moveNum - 1], elapsed_time);
3873             /* currentMoveString is set as a side-effect of ParseOneMove */
3874             strcpy(moveList[moveNum - 1], currentMoveString);
3875             strcat(moveList[moveNum - 1], "\n");
3876           } else {
3877             /* Move from ICS was illegal!?  Punt. */
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3880     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3881   }
3882             strcpy(parseList[moveNum - 1], move_str);
3883             strcat(parseList[moveNum - 1], " ");
3884             strcat(parseList[moveNum - 1], elapsed_time);
3885             moveList[moveNum - 1][0] = NULLCHAR;
3886             fromX = fromY = toX = toY = -1;
3887           }
3888         }
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3891     setbuf(debugFP, NULL);
3892   }
3893
3894 #if ZIPPY
3895         /* Send move to chess program (BEFORE animating it). */
3896         if (appData.zippyPlay && !newGame && newMove && 
3897            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3898
3899             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3900                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3901                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3902                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3903                             move_str);
3904                     DisplayError(str, 0);
3905                 } else {
3906                     if (first.sendTime) {
3907                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3908                     }
3909                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3910                     if (firstMove && !bookHit) {
3911                         firstMove = FALSE;
3912                         if (first.useColors) {
3913                           SendToProgram(gameMode == IcsPlayingWhite ?
3914                                         "white\ngo\n" :
3915                                         "black\ngo\n", &first);
3916                         } else {
3917                           SendToProgram("go\n", &first);
3918                         }
3919                         first.maybeThinking = TRUE;
3920                     }
3921                 }
3922             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3923               if (moveList[moveNum - 1][0] == NULLCHAR) {
3924                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3925                 DisplayError(str, 0);
3926               } else {
3927                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3928                 SendMoveToProgram(moveNum - 1, &first);
3929               }
3930             }
3931         }
3932 #endif
3933     }
3934
3935     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3936         /* If move comes from a remote source, animate it.  If it
3937            isn't remote, it will have already been animated. */
3938         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3939             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3940         }
3941         if (!pausing && appData.highlightLastMove) {
3942             SetHighlights(fromX, fromY, toX, toY);
3943         }
3944     }
3945     
3946     /* Start the clocks */
3947     whiteFlag = blackFlag = FALSE;
3948     appData.clockMode = !(basetime == 0 && increment == 0);
3949     if (ticking == 0) {
3950       ics_clock_paused = TRUE;
3951       StopClocks();
3952     } else if (ticking == 1) {
3953       ics_clock_paused = FALSE;
3954     }
3955     if (gameMode == IcsIdle ||
3956         relation == RELATION_OBSERVING_STATIC ||
3957         relation == RELATION_EXAMINING ||
3958         ics_clock_paused)
3959       DisplayBothClocks();
3960     else
3961       StartClocks();
3962     
3963     /* Display opponents and material strengths */
3964     if (gameInfo.variant != VariantBughouse &&
3965         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3966         if (tinyLayout || smallLayout) {
3967             if(gameInfo.variant == VariantNormal)
3968                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3969                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3970                     basetime, increment);
3971             else
3972                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment, (int) gameInfo.variant);
3975         } else {
3976             if(gameInfo.variant == VariantNormal)
3977                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3978                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3979                     basetime, increment);
3980             else
3981                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment, VariantName(gameInfo.variant));
3984         }
3985         DisplayTitle(str);
3986   if (appData.debugMode) {
3987     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3988   }
3989     }
3990
3991    
3992     /* Display the board */
3993     if (!pausing && !appData.noGUI) {
3994       
3995       if (appData.premove)
3996           if (!gotPremove || 
3997              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3998              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3999               ClearPremoveHighlights();
4000
4001       DrawPosition(FALSE, boards[currentMove]);
4002       DisplayMove(moveNum - 1);
4003       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4004             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4005               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4006         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4007       }
4008     }
4009
4010     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4011 #if ZIPPY
4012     if(bookHit) { // [HGM] book: simulate book reply
4013         static char bookMove[MSG_SIZ]; // a bit generous?
4014
4015         programStats.nodes = programStats.depth = programStats.time = 
4016         programStats.score = programStats.got_only_move = 0;
4017         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4018
4019         strcpy(bookMove, "move ");
4020         strcat(bookMove, bookHit);
4021         HandleMachineMove(bookMove, &first);
4022     }
4023 #endif
4024 }
4025
4026 void
4027 GetMoveListEvent()
4028 {
4029     char buf[MSG_SIZ];
4030     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4031         ics_getting_history = H_REQUESTED;
4032         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4033         SendToICS(buf);
4034     }
4035 }
4036
4037 void
4038 AnalysisPeriodicEvent(force)
4039      int force;
4040 {
4041     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4042          && !force) || !appData.periodicUpdates)
4043       return;
4044
4045     /* Send . command to Crafty to collect stats */
4046     SendToProgram(".\n", &first);
4047
4048     /* Don't send another until we get a response (this makes
4049        us stop sending to old Crafty's which don't understand
4050        the "." command (sending illegal cmds resets node count & time,
4051        which looks bad)) */
4052     programStats.ok_to_send = 0;
4053 }
4054
4055 void ics_update_width(new_width)
4056         int new_width;
4057 {
4058         ics_printf("set width %d\n", new_width);
4059 }
4060
4061 void
4062 SendMoveToProgram(moveNum, cps)
4063      int moveNum;
4064      ChessProgramState *cps;
4065 {
4066     char buf[MSG_SIZ];
4067
4068     if (cps->useUsermove) {
4069       SendToProgram("usermove ", cps);
4070     }
4071     if (cps->useSAN) {
4072       char *space;
4073       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4074         int len = space - parseList[moveNum];
4075         memcpy(buf, parseList[moveNum], len);
4076         buf[len++] = '\n';
4077         buf[len] = NULLCHAR;
4078       } else {
4079         sprintf(buf, "%s\n", parseList[moveNum]);
4080       }
4081       SendToProgram(buf, cps);
4082     } else {
4083       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4084         AlphaRank(moveList[moveNum], 4);
4085         SendToProgram(moveList[moveNum], cps);
4086         AlphaRank(moveList[moveNum], 4); // and back
4087       } else
4088       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4089        * the engine. It would be nice to have a better way to identify castle 
4090        * moves here. */
4091       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4092                                                                          && cps->useOOCastle) {
4093         int fromX = moveList[moveNum][0] - AAA; 
4094         int fromY = moveList[moveNum][1] - ONE;
4095         int toX = moveList[moveNum][2] - AAA; 
4096         int toY = moveList[moveNum][3] - ONE;
4097         if((boards[moveNum][fromY][fromX] == WhiteKing 
4098             && boards[moveNum][toY][toX] == WhiteRook)
4099            || (boards[moveNum][fromY][fromX] == BlackKing 
4100                && boards[moveNum][toY][toX] == BlackRook)) {
4101           if(toX > fromX) SendToProgram("O-O\n", cps);
4102           else SendToProgram("O-O-O\n", cps);
4103         }
4104         else SendToProgram(moveList[moveNum], cps);
4105       }
4106       else SendToProgram(moveList[moveNum], cps);
4107       /* End of additions by Tord */
4108     }
4109
4110     /* [HGM] setting up the opening has brought engine in force mode! */
4111     /*       Send 'go' if we are in a mode where machine should play. */
4112     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4113         (gameMode == TwoMachinesPlay   ||
4114 #ifdef ZIPPY
4115          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4116 #endif
4117          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4118         SendToProgram("go\n", cps);
4119   if (appData.debugMode) {
4120     fprintf(debugFP, "(extra)\n");
4121   }
4122     }
4123     setboardSpoiledMachineBlack = 0;
4124 }
4125
4126 void
4127 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4128      ChessMove moveType;
4129      int fromX, fromY, toX, toY;
4130 {
4131     char user_move[MSG_SIZ];
4132
4133     switch (moveType) {
4134       default:
4135         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4136                 (int)moveType, fromX, fromY, toX, toY);
4137         DisplayError(user_move + strlen("say "), 0);
4138         break;
4139       case WhiteKingSideCastle:
4140       case BlackKingSideCastle:
4141       case WhiteQueenSideCastleWild:
4142       case BlackQueenSideCastleWild:
4143       /* PUSH Fabien */
4144       case WhiteHSideCastleFR:
4145       case BlackHSideCastleFR:
4146       /* POP Fabien */
4147         sprintf(user_move, "o-o\n");
4148         break;
4149       case WhiteQueenSideCastle:
4150       case BlackQueenSideCastle:
4151       case WhiteKingSideCastleWild:
4152       case BlackKingSideCastleWild:
4153       /* PUSH Fabien */
4154       case WhiteASideCastleFR:
4155       case BlackASideCastleFR:
4156       /* POP Fabien */
4157         sprintf(user_move, "o-o-o\n");
4158         break;
4159       case WhitePromotionQueen:
4160       case BlackPromotionQueen:
4161       case WhitePromotionRook:
4162       case BlackPromotionRook:
4163       case WhitePromotionBishop:
4164       case BlackPromotionBishop:
4165       case WhitePromotionKnight:
4166       case BlackPromotionKnight:
4167       case WhitePromotionKing:
4168       case BlackPromotionKing:
4169       case WhitePromotionChancellor:
4170       case BlackPromotionChancellor:
4171       case WhitePromotionArchbishop:
4172       case BlackPromotionArchbishop:
4173         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4174             sprintf(user_move, "%c%c%c%c=%c\n",
4175                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4176                 PieceToChar(WhiteFerz));
4177         else if(gameInfo.variant == VariantGreat)
4178             sprintf(user_move, "%c%c%c%c=%c\n",
4179                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4180                 PieceToChar(WhiteMan));
4181         else
4182             sprintf(user_move, "%c%c%c%c=%c\n",
4183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4184                 PieceToChar(PromoPiece(moveType)));
4185         break;
4186       case WhiteDrop:
4187       case BlackDrop:
4188         sprintf(user_move, "%c@%c%c\n",
4189                 ToUpper(PieceToChar((ChessSquare) fromX)),
4190                 AAA + toX, ONE + toY);
4191         break;
4192       case NormalMove:
4193       case WhiteCapturesEnPassant:
4194       case BlackCapturesEnPassant:
4195       case IllegalMove:  /* could be a variant we don't quite understand */
4196         sprintf(user_move, "%c%c%c%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4198         break;
4199     }
4200     SendToICS(user_move);
4201     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4202         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4203 }
4204
4205 void
4206 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4207      int rf, ff, rt, ft;
4208      char promoChar;
4209      char move[7];
4210 {
4211     if (rf == DROP_RANK) {
4212         sprintf(move, "%c@%c%c\n",
4213                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4214     } else {
4215         if (promoChar == 'x' || promoChar == NULLCHAR) {
4216             sprintf(move, "%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4218         } else {
4219             sprintf(move, "%c%c%c%c%c\n",
4220                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4221         }
4222     }
4223 }
4224
4225 void
4226 ProcessICSInitScript(f)
4227      FILE *f;
4228 {
4229     char buf[MSG_SIZ];
4230
4231     while (fgets(buf, MSG_SIZ, f)) {
4232         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4233     }
4234
4235     fclose(f);
4236 }
4237
4238
4239 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4240 void
4241 AlphaRank(char *move, int n)
4242 {
4243 //    char *p = move, c; int x, y;
4244
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4247     }
4248
4249     if(move[1]=='*' && 
4250        move[2]>='0' && move[2]<='9' &&
4251        move[3]>='a' && move[3]<='x'    ) {
4252         move[1] = '@';
4253         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4254         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4255     } else
4256     if(move[0]>='0' && move[0]<='9' &&
4257        move[1]>='a' && move[1]<='x' &&
4258        move[2]>='0' && move[2]<='9' &&
4259        move[3]>='a' && move[3]<='x'    ) {
4260         /* input move, Shogi -> normal */
4261         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4262         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4263         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4264         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265     } else
4266     if(move[1]=='@' &&
4267        move[3]>='0' && move[3]<='9' &&
4268        move[2]>='a' && move[2]<='x'    ) {
4269         move[1] = '*';
4270         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4271         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4272     } else
4273     if(
4274        move[0]>='a' && move[0]<='x' &&
4275        move[3]>='0' && move[3]<='9' &&
4276        move[2]>='a' && move[2]<='x'    ) {
4277          /* output move, normal -> Shogi */
4278         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4279         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4280         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4281         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4282         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4283     }
4284     if (appData.debugMode) {
4285         fprintf(debugFP, "   out = '%s'\n", move);
4286     }
4287 }
4288
4289 /* Parser for moves from gnuchess, ICS, or user typein box */
4290 Boolean
4291 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4292      char *move;
4293      int moveNum;
4294      ChessMove *moveType;
4295      int *fromX, *fromY, *toX, *toY;
4296      char *promoChar;
4297 {       
4298     if (appData.debugMode) {
4299         fprintf(debugFP, "move to parse: %s\n", move);
4300     }
4301     *moveType = yylexstr(moveNum, move);
4302
4303     switch (*moveType) {
4304       case WhitePromotionChancellor:
4305       case BlackPromotionChancellor:
4306       case WhitePromotionArchbishop:
4307       case BlackPromotionArchbishop:
4308       case WhitePromotionQueen:
4309       case BlackPromotionQueen:
4310       case WhitePromotionRook:
4311       case BlackPromotionRook:
4312       case WhitePromotionBishop:
4313       case BlackPromotionBishop:
4314       case WhitePromotionKnight:
4315       case BlackPromotionKnight:
4316       case WhitePromotionKing:
4317       case BlackPromotionKing:
4318       case NormalMove:
4319       case WhiteCapturesEnPassant:
4320       case BlackCapturesEnPassant:
4321       case WhiteKingSideCastle:
4322       case WhiteQueenSideCastle:
4323       case BlackKingSideCastle:
4324       case BlackQueenSideCastle:
4325       case WhiteKingSideCastleWild:
4326       case WhiteQueenSideCastleWild:
4327       case BlackKingSideCastleWild:
4328       case BlackQueenSideCastleWild:
4329       /* Code added by Tord: */
4330       case WhiteHSideCastleFR:
4331       case WhiteASideCastleFR:
4332       case BlackHSideCastleFR:
4333       case BlackASideCastleFR:
4334       /* End of code added by Tord */
4335       case IllegalMove:         /* bug or odd chess variant */
4336         *fromX = currentMoveString[0] - AAA;
4337         *fromY = currentMoveString[1] - ONE;
4338         *toX = currentMoveString[2] - AAA;
4339         *toY = currentMoveString[3] - ONE;
4340         *promoChar = currentMoveString[4];
4341         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4342             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4343     if (appData.debugMode) {
4344         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4345     }
4346             *fromX = *fromY = *toX = *toY = 0;
4347             return FALSE;
4348         }
4349         if (appData.testLegality) {
4350           return (*moveType != IllegalMove);
4351         } else {
4352           return !(fromX == fromY && toX == toY);
4353         }
4354
4355       case WhiteDrop:
4356       case BlackDrop:
4357         *fromX = *moveType == WhiteDrop ?
4358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4359           (int) CharToPiece(ToLower(currentMoveString[0]));
4360         *fromY = DROP_RANK;
4361         *toX = currentMoveString[2] - AAA;
4362         *toY = currentMoveString[3] - ONE;
4363         *promoChar = NULLCHAR;
4364         return TRUE;
4365
4366       case AmbiguousMove:
4367       case ImpossibleMove:
4368       case (ChessMove) 0:       /* end of file */
4369       case ElapsedTime:
4370       case Comment:
4371       case PGNTag:
4372       case NAG:
4373       case WhiteWins:
4374       case BlackWins:
4375       case GameIsDrawn:
4376       default:
4377     if (appData.debugMode) {
4378         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4379     }
4380         /* bug? */
4381         *fromX = *fromY = *toX = *toY = 0;
4382         *promoChar = NULLCHAR;
4383         return FALSE;
4384     }
4385 }
4386
4387 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4388 // All positions will have equal probability, but the current method will not provide a unique
4389 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4390 #define DARK 1
4391 #define LITE 2
4392 #define ANY 3
4393
4394 int squaresLeft[4];
4395 int piecesLeft[(int)BlackPawn];
4396 int seed, nrOfShuffles;
4397
4398 void GetPositionNumber()
4399 {       // sets global variable seed
4400         int i;
4401
4402         seed = appData.defaultFrcPosition;
4403         if(seed < 0) { // randomize based on time for negative FRC position numbers
4404                 for(i=0; i<50; i++) seed += random();
4405                 seed = random() ^ random() >> 8 ^ random() << 8;
4406                 if(seed<0) seed = -seed;
4407         }
4408 }
4409
4410 int put(Board board, int pieceType, int rank, int n, int shade)
4411 // put the piece on the (n-1)-th empty squares of the given shade
4412 {
4413         int i;
4414
4415         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4416                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4417                         board[rank][i] = (ChessSquare) pieceType;
4418                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4419                         squaresLeft[ANY]--;
4420                         piecesLeft[pieceType]--; 
4421                         return i;
4422                 }
4423         }
4424         return -1;
4425 }
4426
4427
4428 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4429 // calculate where the next piece goes, (any empty square), and put it there
4430 {
4431         int i;
4432
4433         i = seed % squaresLeft[shade];
4434         nrOfShuffles *= squaresLeft[shade];
4435         seed /= squaresLeft[shade];
4436         put(board, pieceType, rank, i, shade);
4437 }
4438
4439 void AddTwoPieces(Board board, int pieceType, int rank)
4440 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4441 {
4442         int i, n=squaresLeft[ANY], j=n-1, k;
4443
4444         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4445         i = seed % k;  // pick one
4446         nrOfShuffles *= k;
4447         seed /= k;
4448         while(i >= j) i -= j--;
4449         j = n - 1 - j; i += j;
4450         put(board, pieceType, rank, j, ANY);
4451         put(board, pieceType, rank, i, ANY);
4452 }
4453
4454 void SetUpShuffle(Board board, int number)
4455 {
4456         int i, p, first=1;
4457
4458         GetPositionNumber(); nrOfShuffles = 1;
4459
4460         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4461         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4462         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4463
4464         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4465
4466         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4467             p = (int) board[0][i];
4468             if(p < (int) BlackPawn) piecesLeft[p] ++;
4469             board[0][i] = EmptySquare;
4470         }
4471
4472         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4473             // shuffles restricted to allow normal castling put KRR first
4474             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4475                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4476             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4477                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4478             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4479                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4480             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4481                 put(board, WhiteRook, 0, 0, ANY);
4482             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4483         }
4484
4485         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4486             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4487             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4488                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4489                 while(piecesLeft[p] >= 2) {
4490                     AddOnePiece(board, p, 0, LITE);
4491                     AddOnePiece(board, p, 0, DARK);
4492                 }
4493                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4494             }
4495
4496         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4497             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4498             // but we leave King and Rooks for last, to possibly obey FRC restriction
4499             if(p == (int)WhiteRook) continue;
4500             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4501             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4502         }
4503
4504         // now everything is placed, except perhaps King (Unicorn) and Rooks
4505
4506         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4507             // Last King gets castling rights
4508             while(piecesLeft[(int)WhiteUnicorn]) {
4509                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4511             }
4512
4513             while(piecesLeft[(int)WhiteKing]) {
4514                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4515                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4516             }
4517
4518
4519         } else {
4520             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4521             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4522         }
4523
4524         // Only Rooks can be left; simply place them all
4525         while(piecesLeft[(int)WhiteRook]) {
4526                 i = put(board, WhiteRook, 0, 0, ANY);
4527                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4528                         if(first) {
4529                                 first=0;
4530                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4531                         }
4532                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4533                 }
4534         }
4535         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4536             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4537         }
4538
4539         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4540 }
4541
4542 int SetCharTable( char *table, const char * map )
4543 /* [HGM] moved here from winboard.c because of its general usefulness */
4544 /*       Basically a safe strcpy that uses the last character as King */
4545 {
4546     int result = FALSE; int NrPieces;
4547
4548     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4549                     && NrPieces >= 12 && !(NrPieces&1)) {
4550         int i; /* [HGM] Accept even length from 12 to 34 */
4551
4552         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4553         for( i=0; i<NrPieces/2-1; i++ ) {
4554             table[i] = map[i];
4555             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4556         }
4557         table[(int) WhiteKing]  = map[NrPieces/2-1];
4558         table[(int) BlackKing]  = map[NrPieces-1];
4559
4560         result = TRUE;
4561     }
4562
4563     return result;
4564 }
4565
4566 void Prelude(Board board)
4567 {       // [HGM] superchess: random selection of exo-pieces
4568         int i, j, k; ChessSquare p; 
4569         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4570
4571         GetPositionNumber(); // use FRC position number
4572
4573         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4574             SetCharTable(pieceToChar, appData.pieceToCharTable);
4575             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4576                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4577         }
4578
4579         j = seed%4;                 seed /= 4; 
4580         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4581         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4582         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4583         j = seed%3 + (seed%3 >= j); seed /= 3; 
4584         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4585         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4586         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4587         j = seed%3;                 seed /= 3; 
4588         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4589         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4590         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4591         j = seed%2 + (seed%2 >= j); seed /= 2; 
4592         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4593         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4594         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4595         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4596         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4597         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4598         put(board, exoPieces[0],    0, 0, ANY);
4599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4600 }
4601
4602 void
4603 InitPosition(redraw)
4604      int redraw;
4605 {
4606     ChessSquare (* pieces)[BOARD_SIZE];
4607     int i, j, pawnRow, overrule,
4608     oldx = gameInfo.boardWidth,
4609     oldy = gameInfo.boardHeight,
4610     oldh = gameInfo.holdingsWidth,
4611     oldv = gameInfo.variant;
4612
4613     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4614
4615     /* [AS] Initialize pv info list [HGM] and game status */
4616     {
4617         for( i=0; i<MAX_MOVES; i++ ) {
4618             pvInfoList[i].depth = 0;
4619             epStatus[i]=EP_NONE;
4620             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4621         }
4622
4623         initialRulePlies = 0; /* 50-move counter start */
4624
4625         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4626         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4627     }
4628
4629     
4630     /* [HGM] logic here is completely changed. In stead of full positions */
4631     /* the initialized data only consist of the two backranks. The switch */
4632     /* selects which one we will use, which is than copied to the Board   */
4633     /* initialPosition, which for the rest is initialized by Pawns and    */
4634     /* empty squares. This initial position is then copied to boards[0],  */
4635     /* possibly after shuffling, so that it remains available.            */
4636
4637     gameInfo.holdingsWidth = 0; /* default board sizes */
4638     gameInfo.boardWidth    = 8;
4639     gameInfo.boardHeight   = 8;
4640     gameInfo.holdingsSize  = 0;
4641     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4642     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4643     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4644
4645     switch (gameInfo.variant) {
4646     case VariantFischeRandom:
4647       shuffleOpenings = TRUE;
4648     default:
4649       pieces = FIDEArray;
4650       break;
4651     case VariantShatranj:
4652       pieces = ShatranjArray;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4655       break;
4656     case VariantTwoKings:
4657       pieces = twoKingsArray;
4658       break;
4659     case VariantCapaRandom:
4660       shuffleOpenings = TRUE;
4661     case VariantCapablanca:
4662       pieces = CapablancaArray;
4663       gameInfo.boardWidth = 10;
4664       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4665       break;
4666     case VariantGothic:
4667       pieces = GothicArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4670       break;
4671     case VariantJanus:
4672       pieces = JanusArray;
4673       gameInfo.boardWidth = 10;
4674       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4675       nrCastlingRights = 6;
4676         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4677         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4678         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4679         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4680         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4681         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4682       break;
4683     case VariantFalcon:
4684       pieces = FalconArray;
4685       gameInfo.boardWidth = 10;
4686       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4687       break;
4688     case VariantXiangqi:
4689       pieces = XiangqiArray;
4690       gameInfo.boardWidth  = 9;
4691       gameInfo.boardHeight = 10;
4692       nrCastlingRights = 0;
4693       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4694       break;
4695     case VariantShogi:
4696       pieces = ShogiArray;
4697       gameInfo.boardWidth  = 9;
4698       gameInfo.boardHeight = 9;
4699       gameInfo.holdingsSize = 7;
4700       nrCastlingRights = 0;
4701       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4702       break;
4703     case VariantCourier:
4704       pieces = CourierArray;
4705       gameInfo.boardWidth  = 12;
4706       nrCastlingRights = 0;
4707       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4708       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4709       break;
4710     case VariantKnightmate:
4711       pieces = KnightmateArray;
4712       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4713       break;
4714     case VariantFairy:
4715       pieces = fairyArray;
4716       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4717       break;
4718     case VariantGreat:
4719       pieces = GreatArray;
4720       gameInfo.boardWidth = 10;
4721       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4722       gameInfo.holdingsSize = 8;
4723       break;
4724     case VariantSuper:
4725       pieces = FIDEArray;
4726       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4727       gameInfo.holdingsSize = 8;
4728       startedFromSetupPosition = TRUE;
4729       break;
4730     case VariantCrazyhouse:
4731     case VariantBughouse:
4732       pieces = FIDEArray;
4733       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4734       gameInfo.holdingsSize = 5;
4735       break;
4736     case VariantWildCastle:
4737       pieces = FIDEArray;
4738       /* !!?shuffle with kings guaranteed to be on d or e file */
4739       shuffleOpenings = 1;
4740       break;
4741     case VariantNoCastle:
4742       pieces = FIDEArray;
4743       nrCastlingRights = 0;
4744       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4745       /* !!?unconstrained back-rank shuffle */
4746       shuffleOpenings = 1;
4747       break;
4748     }
4749
4750     overrule = 0;
4751     if(appData.NrFiles >= 0) {
4752         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4753         gameInfo.boardWidth = appData.NrFiles;
4754     }
4755     if(appData.NrRanks >= 0) {
4756         gameInfo.boardHeight = appData.NrRanks;
4757     }
4758     if(appData.holdingsSize >= 0) {
4759         i = appData.holdingsSize;
4760         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4761         gameInfo.holdingsSize = i;
4762     }
4763     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4764     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4765         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4766
4767     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4768     if(pawnRow < 1) pawnRow = 1;
4769
4770     /* User pieceToChar list overrules defaults */
4771     if(appData.pieceToCharTable != NULL)
4772         SetCharTable(pieceToChar, appData.pieceToCharTable);
4773
4774     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4775
4776         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4777             s = (ChessSquare) 0; /* account holding counts in guard band */
4778         for( i=0; i<BOARD_HEIGHT; i++ )
4779             initialPosition[i][j] = s;
4780
4781         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4782         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4783         initialPosition[pawnRow][j] = WhitePawn;
4784         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4785         if(gameInfo.variant == VariantXiangqi) {
4786             if(j&1) {
4787                 initialPosition[pawnRow][j] = 
4788                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4789                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4790                    initialPosition[2][j] = WhiteCannon;
4791                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4792                 }
4793             }
4794         }
4795         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4796     }
4797     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4798
4799             j=BOARD_LEFT+1;
4800             initialPosition[1][j] = WhiteBishop;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4802             j=BOARD_RGHT-2;
4803             initialPosition[1][j] = WhiteRook;
4804             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4805     }
4806
4807     if( nrCastlingRights == -1) {
4808         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4809         /*       This sets default castling rights from none to normal corners   */
4810         /* Variants with other castling rights must set them themselves above    */
4811         nrCastlingRights = 6;
4812        
4813         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4814         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4815         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4816         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4817         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4818         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4819      }
4820
4821      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4822      if(gameInfo.variant == VariantGreat) { // promotion commoners
4823         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4824         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4825         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4826         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4827      }
4828   if (appData.debugMode) {
4829     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4830   }
4831     if(shuffleOpenings) {
4832         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4833         startedFromSetupPosition = TRUE;
4834     }
4835     if(startedFromPositionFile) {
4836       /* [HGM] loadPos: use PositionFile for every new game */
4837       CopyBoard(initialPosition, filePosition);
4838       for(i=0; i<nrCastlingRights; i++)
4839           castlingRights[0][i] = initialRights[i] = fileRights[i];
4840       startedFromSetupPosition = TRUE;
4841     }
4842
4843     CopyBoard(boards[0], initialPosition);
4844
4845     if(oldx != gameInfo.boardWidth ||
4846        oldy != gameInfo.boardHeight ||
4847        oldh != gameInfo.holdingsWidth
4848 #ifdef GOTHIC
4849        || oldv == VariantGothic ||        // For licensing popups
4850        gameInfo.variant == VariantGothic
4851 #endif
4852 #ifdef FALCON
4853        || oldv == VariantFalcon ||
4854        gameInfo.variant == VariantFalcon
4855 #endif
4856                                          )
4857             InitDrawingSizes(-2 ,0);
4858
4859     if (redraw)
4860       DrawPosition(TRUE, boards[currentMove]);
4861 }
4862
4863 void
4864 SendBoard(cps, moveNum)
4865      ChessProgramState *cps;
4866      int moveNum;
4867 {
4868     char message[MSG_SIZ];
4869     
4870     if (cps->useSetboard) {
4871       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4872       sprintf(message, "setboard %s\n", fen);
4873       SendToProgram(message, cps);
4874       free(fen);
4875
4876     } else {
4877       ChessSquare *bp;
4878       int i, j;
4879       /* Kludge to set black to move, avoiding the troublesome and now
4880        * deprecated "black" command.
4881        */
4882       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4883
4884       SendToProgram("edit\n", cps);
4885       SendToProgram("#\n", cps);
4886       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4887         bp = &boards[moveNum][i][BOARD_LEFT];
4888         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4889           if ((int) *bp < (int) BlackPawn) {
4890             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4891                     AAA + j, ONE + i);
4892             if(message[0] == '+' || message[0] == '~') {
4893                 sprintf(message, "%c%c%c+\n",
4894                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4895                         AAA + j, ONE + i);
4896             }
4897             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4898                 message[1] = BOARD_RGHT   - 1 - j + '1';
4899                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4900             }
4901             SendToProgram(message, cps);
4902           }
4903         }
4904       }
4905     
4906       SendToProgram("c\n", cps);
4907       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4908         bp = &boards[moveNum][i][BOARD_LEFT];
4909         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4910           if (((int) *bp != (int) EmptySquare)
4911               && ((int) *bp >= (int) BlackPawn)) {
4912             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4913                     AAA + j, ONE + i);
4914             if(message[0] == '+' || message[0] == '~') {
4915                 sprintf(message, "%c%c%c+\n",
4916                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4917                         AAA + j, ONE + i);
4918             }
4919             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4920                 message[1] = BOARD_RGHT   - 1 - j + '1';
4921                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4922             }
4923             SendToProgram(message, cps);
4924           }
4925         }
4926       }
4927     
4928       SendToProgram(".\n", cps);
4929     }
4930     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4931 }
4932
4933 int
4934 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4935 {
4936     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4937     /* [HGM] add Shogi promotions */
4938     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4939     ChessSquare piece;
4940     ChessMove moveType;
4941     Boolean premove;
4942
4943     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4944     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4945
4946     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4947       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4948         return FALSE;
4949
4950     piece = boards[currentMove][fromY][fromX];
4951     if(gameInfo.variant == VariantShogi) {
4952         promotionZoneSize = 3;
4953         highestPromotingPiece = (int)WhiteFerz;
4954     }
4955
4956     // next weed out all moves that do not touch the promotion zone at all
4957     if((int)piece >= BlackPawn) {
4958         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4959              return FALSE;
4960         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4961     } else {
4962         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4963            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4964     }
4965
4966     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4967
4968     // weed out mandatory Shogi promotions
4969     if(gameInfo.variant == VariantShogi) {
4970         if(piece >= BlackPawn) {
4971             if(toY == 0 && piece == BlackPawn ||
4972                toY == 0 && piece == BlackQueen ||
4973                toY <= 1 && piece == BlackKnight) {
4974                 *promoChoice = '+';
4975                 return FALSE;
4976             }
4977         } else {
4978             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4979                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4980                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4981                 *promoChoice = '+';
4982                 return FALSE;
4983             }
4984         }
4985     }
4986
4987     // weed out obviously illegal Pawn moves
4988     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4989         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4990         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4991         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4992         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4993         // note we are not allowed to test for valid (non-)capture, due to premove
4994     }
4995
4996     // we either have a choice what to promote to, or (in Shogi) whether to promote
4997     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4998         *promoChoice = PieceToChar(BlackFerz);  // no choice
4999         return FALSE;
5000     }
5001     if(appData.alwaysPromoteToQueen) { // predetermined
5002         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5003              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5004         else *promoChoice = PieceToChar(BlackQueen);
5005         return FALSE;
5006     }
5007
5008     // suppress promotion popup on illegal moves that are not premoves
5009     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5010               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5011     if(appData.testLegality && !premove) {
5012         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5013                         epStatus[currentMove], castlingRights[currentMove],
5014                         fromY, fromX, toY, toX, NULLCHAR);
5015         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5016            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5017             return FALSE;
5018     }
5019
5020     return TRUE;
5021 }
5022
5023 int
5024 InPalace(row, column)
5025      int row, column;
5026 {   /* [HGM] for Xiangqi */
5027     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5028          column < (BOARD_WIDTH + 4)/2 &&
5029          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5030     return FALSE;
5031 }
5032
5033 int
5034 PieceForSquare (x, y)
5035      int x;
5036      int y;
5037 {
5038   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5039      return -1;
5040   else
5041      return boards[currentMove][y][x];
5042 }
5043
5044 int
5045 OKToStartUserMove(x, y)
5046      int x, y;
5047 {
5048     ChessSquare from_piece;
5049     int white_piece;
5050
5051     if (matchMode) return FALSE;
5052     if (gameMode == EditPosition) return TRUE;
5053
5054     if (x >= 0 && y >= 0)
5055       from_piece = boards[currentMove][y][x];
5056     else
5057       from_piece = EmptySquare;
5058
5059     if (from_piece == EmptySquare) return FALSE;
5060
5061     white_piece = (int)from_piece >= (int)WhitePawn &&
5062       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5063
5064     switch (gameMode) {
5065       case PlayFromGameFile:
5066       case AnalyzeFile:
5067       case TwoMachinesPlay:
5068       case EndOfGame:
5069         return FALSE;
5070
5071       case IcsObserving:
5072       case IcsIdle:
5073         return FALSE;
5074
5075       case MachinePlaysWhite:
5076       case IcsPlayingBlack:
5077         if (appData.zippyPlay) return FALSE;
5078         if (white_piece) {
5079             DisplayMoveError(_("You are playing Black"));
5080             return FALSE;
5081         }
5082         break;
5083
5084       case MachinePlaysBlack:
5085       case IcsPlayingWhite:
5086         if (appData.zippyPlay) return FALSE;
5087         if (!white_piece) {
5088             DisplayMoveError(_("You are playing White"));
5089             return FALSE;
5090         }
5091         break;
5092
5093       case EditGame:
5094         if (!white_piece && WhiteOnMove(currentMove)) {
5095             DisplayMoveError(_("It is White's turn"));
5096             return FALSE;
5097         }           
5098         if (white_piece && !WhiteOnMove(currentMove)) {
5099             DisplayMoveError(_("It is Black's turn"));
5100             return FALSE;
5101         }           
5102         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5103             /* Editing correspondence game history */
5104             /* Could disallow this or prompt for confirmation */
5105             cmailOldMove = -1;
5106         }
5107         if (currentMove < forwardMostMove) {
5108             /* Discarding moves */
5109             /* Could prompt for confirmation here,
5110                but I don't think that's such a good idea */
5111             forwardMostMove = currentMove;
5112         }
5113         break;
5114
5115       case BeginningOfGame:
5116         if (appData.icsActive) return FALSE;
5117         if (!appData.noChessProgram) {
5118             if (!white_piece) {
5119                 DisplayMoveError(_("You are playing White"));
5120                 return FALSE;
5121             }
5122         }
5123         break;
5124         
5125       case Training:
5126         if (!white_piece && WhiteOnMove(currentMove)) {
5127             DisplayMoveError(_("It is White's turn"));
5128             return FALSE;
5129         }           
5130         if (white_piece && !WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is Black's turn"));
5132             return FALSE;
5133         }           
5134         break;
5135
5136       default:
5137       case IcsExamining:
5138         break;
5139     }
5140     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5141         && gameMode != AnalyzeFile && gameMode != Training) {
5142         DisplayMoveError(_("Displayed position is not current"));
5143         return FALSE;
5144     }
5145     return TRUE;
5146 }
5147
5148 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5149 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5150 int lastLoadGameUseList = FALSE;
5151 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5152 ChessMove lastLoadGameStart = (ChessMove) 0;
5153
5154 ChessMove
5155 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5156      int fromX, fromY, toX, toY;
5157      int promoChar;
5158      Boolean captureOwn;
5159 {
5160     ChessMove moveType;
5161     ChessSquare pdown, pup;
5162
5163     /* Check if the user is playing in turn.  This is complicated because we
5164        let the user "pick up" a piece before it is his turn.  So the piece he
5165        tried to pick up may have been captured by the time he puts it down!
5166        Therefore we use the color the user is supposed to be playing in this
5167        test, not the color of the piece that is currently on the starting
5168        square---except in EditGame mode, where the user is playing both
5169        sides; fortunately there the capture race can't happen.  (It can
5170        now happen in IcsExamining mode, but that's just too bad.  The user
5171        will get a somewhat confusing message in that case.)
5172        */
5173
5174     switch (gameMode) {
5175       case PlayFromGameFile:
5176       case AnalyzeFile:
5177       case TwoMachinesPlay:
5178       case EndOfGame:
5179       case IcsObserving:
5180       case IcsIdle:
5181         /* We switched into a game mode where moves are not accepted,
5182            perhaps while the mouse button was down. */
5183         return ImpossibleMove;
5184
5185       case MachinePlaysWhite:
5186         /* User is moving for Black */
5187         if (WhiteOnMove(currentMove)) {
5188             DisplayMoveError(_("It is White's turn"));
5189             return ImpossibleMove;
5190         }
5191         break;
5192
5193       case MachinePlaysBlack:
5194         /* User is moving for White */
5195         if (!WhiteOnMove(currentMove)) {
5196             DisplayMoveError(_("It is Black's turn"));
5197             return ImpossibleMove;
5198         }
5199         break;
5200
5201       case EditGame:
5202       case IcsExamining:
5203       case BeginningOfGame:
5204       case AnalyzeMode:
5205       case Training:
5206         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5207             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5208             /* User is moving for Black */
5209             if (WhiteOnMove(currentMove)) {
5210                 DisplayMoveError(_("It is White's turn"));
5211                 return ImpossibleMove;
5212             }
5213         } else {
5214             /* User is moving for White */
5215             if (!WhiteOnMove(currentMove)) {
5216                 DisplayMoveError(_("It is Black's turn"));
5217                 return ImpossibleMove;
5218             }
5219         }
5220         break;
5221
5222       case IcsPlayingBlack:
5223         /* User is moving for Black */
5224         if (WhiteOnMove(currentMove)) {
5225             if (!appData.premove) {
5226                 DisplayMoveError(_("It is White's turn"));
5227             } else if (toX >= 0 && toY >= 0) {
5228                 premoveToX = toX;
5229                 premoveToY = toY;
5230                 premoveFromX = fromX;
5231                 premoveFromY = fromY;
5232                 premovePromoChar = promoChar;
5233                 gotPremove = 1;
5234                 if (appData.debugMode) 
5235                     fprintf(debugFP, "Got premove: fromX %d,"
5236                             "fromY %d, toX %d, toY %d\n",
5237                             fromX, fromY, toX, toY);
5238             }
5239             return ImpossibleMove;
5240         }
5241         break;
5242
5243       case IcsPlayingWhite:
5244         /* User is moving for White */
5245         if (!WhiteOnMove(currentMove)) {
5246             if (!appData.premove) {
5247                 DisplayMoveError(_("It is Black's turn"));
5248             } else if (toX >= 0 && toY >= 0) {
5249                 premoveToX = toX;
5250                 premoveToY = toY;
5251                 premoveFromX = fromX;
5252                 premoveFromY = fromY;
5253                 premovePromoChar = promoChar;
5254                 gotPremove = 1;
5255                 if (appData.debugMode) 
5256                     fprintf(debugFP, "Got premove: fromX %d,"
5257                             "fromY %d, toX %d, toY %d\n",
5258                             fromX, fromY, toX, toY);
5259             }
5260             return ImpossibleMove;
5261         }
5262         break;
5263
5264       default:
5265         break;
5266
5267       case EditPosition:
5268         /* EditPosition, empty square, or different color piece;
5269            click-click move is possible */
5270         if (toX == -2 || toY == -2) {
5271             boards[0][fromY][fromX] = EmptySquare;
5272             return AmbiguousMove;
5273         } else if (toX >= 0 && toY >= 0) {
5274             boards[0][toY][toX] = boards[0][fromY][fromX];
5275             boards[0][fromY][fromX] = EmptySquare;
5276             return AmbiguousMove;
5277         }
5278         return ImpossibleMove;
5279     }
5280
5281     if(toX < 0 || toY < 0) return ImpossibleMove;
5282     pdown = boards[currentMove][fromY][fromX];
5283     pup = boards[currentMove][toY][toX];
5284
5285     /* [HGM] If move started in holdings, it means a drop */
5286     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5287          if( pup != EmptySquare ) return ImpossibleMove;
5288          if(appData.testLegality) {
5289              /* it would be more logical if LegalityTest() also figured out
5290               * which drops are legal. For now we forbid pawns on back rank.
5291               * Shogi is on its own here...
5292               */
5293              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5294                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5295                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5296          }
5297          return WhiteDrop; /* Not needed to specify white or black yet */
5298     }
5299
5300     userOfferedDraw = FALSE;
5301         
5302     /* [HGM] always test for legality, to get promotion info */
5303     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5304                           epStatus[currentMove], castlingRights[currentMove],
5305                                          fromY, fromX, toY, toX, promoChar);
5306     /* [HGM] but possibly ignore an IllegalMove result */
5307     if (appData.testLegality) {
5308         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5309             DisplayMoveError(_("Illegal move"));
5310             return ImpossibleMove;
5311         }
5312     }
5313
5314     return moveType;
5315     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5316        function is made into one that returns an OK move type if FinishMove
5317        should be called. This to give the calling driver routine the
5318        opportunity to finish the userMove input with a promotion popup,
5319        without bothering the user with this for invalid or illegal moves */
5320
5321 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5322 }
5323
5324 /* Common tail of UserMoveEvent and DropMenuEvent */
5325 int
5326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5327      ChessMove moveType;
5328      int fromX, fromY, toX, toY;
5329      /*char*/int promoChar;
5330 {
5331     char *bookHit = 0;
5332
5333     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5334         // [HGM] superchess: suppress promotions to non-available piece
5335         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5336         if(WhiteOnMove(currentMove)) {
5337             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5338         } else {
5339             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5340         }
5341     }
5342
5343     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5344        move type in caller when we know the move is a legal promotion */
5345     if(moveType == NormalMove && promoChar)
5346         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5347
5348     /* [HGM] convert drag-and-drop piece drops to standard form */
5349     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5350          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5351            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5352                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5353            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5354            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5355            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5356            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5357          fromY = DROP_RANK;
5358     }
5359
5360     /* [HGM] <popupFix> The following if has been moved here from
5361        UserMoveEvent(). Because it seemed to belong here (why not allow
5362        piece drops in training games?), and because it can only be
5363        performed after it is known to what we promote. */
5364     if (gameMode == Training) {
5365       /* compare the move played on the board to the next move in the
5366        * game. If they match, display the move and the opponent's response. 
5367        * If they don't match, display an error message.
5368        */
5369       int saveAnimate;
5370       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5371       CopyBoard(testBoard, boards[currentMove]);
5372       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5373
5374       if (CompareBoards(testBoard, boards[currentMove+1])) {
5375         ForwardInner(currentMove+1);
5376
5377         /* Autoplay the opponent's response.
5378          * if appData.animate was TRUE when Training mode was entered,
5379          * the response will be animated.
5380          */
5381         saveAnimate = appData.animate;
5382         appData.animate = animateTraining;
5383         ForwardInner(currentMove+1);
5384         appData.animate = saveAnimate;
5385
5386         /* check for the end of the game */
5387         if (currentMove >= forwardMostMove) {
5388           gameMode = PlayFromGameFile;
5389           ModeHighlight();
5390           SetTrainingModeOff();
5391           DisplayInformation(_("End of game"));
5392         }
5393       } else {
5394         DisplayError(_("Incorrect move"), 0);
5395       }
5396       return 1;
5397     }
5398
5399   /* Ok, now we know that the move is good, so we can kill
5400      the previous line in Analysis Mode */
5401   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5402     forwardMostMove = currentMove;
5403   }
5404
5405   /* If we need the chess program but it's dead, restart it */
5406   ResurrectChessProgram();
5407
5408   /* A user move restarts a paused game*/
5409   if (pausing)
5410     PauseEvent();
5411
5412   thinkOutput[0] = NULLCHAR;
5413
5414   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5415
5416   if (gameMode == BeginningOfGame) {
5417     if (appData.noChessProgram) {
5418       gameMode = EditGame;
5419       SetGameInfo();
5420     } else {
5421       char buf[MSG_SIZ];
5422       gameMode = MachinePlaysBlack;
5423       StartClocks();
5424       SetGameInfo();
5425       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5426       DisplayTitle(buf);
5427       if (first.sendName) {
5428         sprintf(buf, "name %s\n", gameInfo.white);
5429         SendToProgram(buf, &first);
5430       }
5431       StartClocks();
5432     }
5433     ModeHighlight();
5434   }
5435
5436   /* Relay move to ICS or chess engine */
5437   if (appData.icsActive) {
5438     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5439         gameMode == IcsExamining) {
5440       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5441       ics_user_moved = 1;
5442     }
5443   } else {
5444     if (first.sendTime && (gameMode == BeginningOfGame ||
5445                            gameMode == MachinePlaysWhite ||
5446                            gameMode == MachinePlaysBlack)) {
5447       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5448     }
5449     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5450          // [HGM] book: if program might be playing, let it use book
5451         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5452         first.maybeThinking = TRUE;
5453     } else SendMoveToProgram(forwardMostMove-1, &first);
5454     if (currentMove == cmailOldMove + 1) {
5455       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5456     }
5457   }
5458
5459   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5460
5461   switch (gameMode) {
5462   case EditGame:
5463     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5464                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5465     case MT_NONE:
5466     case MT_CHECK:
5467       break;
5468     case MT_CHECKMATE:
5469     case MT_STAINMATE:
5470       if (WhiteOnMove(currentMove)) {
5471         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5472       } else {
5473         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5474       }
5475       break;
5476     case MT_STALEMATE:
5477       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5478       break;
5479     }
5480     break;
5481     
5482   case MachinePlaysBlack:
5483   case MachinePlaysWhite:
5484     /* disable certain menu options while machine is thinking */
5485     SetMachineThinkingEnables();
5486     break;
5487
5488   default:
5489     break;
5490   }
5491
5492   if(bookHit) { // [HGM] book: simulate book reply
5493         static char bookMove[MSG_SIZ]; // a bit generous?
5494
5495         programStats.nodes = programStats.depth = programStats.time = 
5496         programStats.score = programStats.got_only_move = 0;
5497         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5498
5499         strcpy(bookMove, "move ");
5500         strcat(bookMove, bookHit);
5501         HandleMachineMove(bookMove, &first);
5502   }
5503   return 1;
5504 }
5505
5506 void
5507 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5508      int fromX, fromY, toX, toY;
5509      int promoChar;
5510 {
5511     /* [HGM] This routine was added to allow calling of its two logical
5512        parts from other modules in the old way. Before, UserMoveEvent()
5513        automatically called FinishMove() if the move was OK, and returned
5514        otherwise. I separated the two, in order to make it possible to
5515        slip a promotion popup in between. But that it always needs two
5516        calls, to the first part, (now called UserMoveTest() ), and to
5517        FinishMove if the first part succeeded. Calls that do not need
5518        to do anything in between, can call this routine the old way. 
5519     */
5520     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5521 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5522     if(moveType == AmbiguousMove)
5523         DrawPosition(FALSE, boards[currentMove]);
5524     else if(moveType != ImpossibleMove && moveType != Comment)
5525         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5526 }
5527
5528 void LeftClick(ClickType clickType, int xPix, int yPix)
5529 {
5530     int x, y;
5531     Boolean saveAnimate;
5532     static int second = 0, promotionChoice = 0;
5533     char promoChoice = NULLCHAR;
5534
5535     if (clickType == Press) ErrorPopDown();
5536
5537     x = EventToSquare(xPix, BOARD_WIDTH);
5538     y = EventToSquare(yPix, BOARD_HEIGHT);
5539     if (!flipView && y >= 0) {
5540         y = BOARD_HEIGHT - 1 - y;
5541     }
5542     if (flipView && x >= 0) {
5543         x = BOARD_WIDTH - 1 - x;
5544     }
5545
5546     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5547         if(clickType == Release) return; // ignore upclick of click-click destination
5548         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5549         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5550         if(gameInfo.holdingsWidth && 
5551                 (WhiteOnMove(currentMove) 
5552                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5553                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5554             // click in right holdings, for determining promotion piece
5555             ChessSquare p = boards[currentMove][y][x];
5556             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5557             if(p != EmptySquare) {
5558                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5559                 fromX = fromY = -1;
5560                 return;
5561             }
5562         }
5563         DrawPosition(FALSE, boards[currentMove]);
5564         return;
5565     }
5566
5567     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5568     if(clickType == Press
5569             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5570               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5571               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5572         return;
5573
5574     if (fromX == -1) {
5575         if (clickType == Press) {
5576             /* First square */
5577             if (OKToStartUserMove(x, y)) {
5578                 fromX = x;
5579                 fromY = y;
5580                 second = 0;
5581                 DragPieceBegin(xPix, yPix);
5582                 if (appData.highlightDragging) {
5583                     SetHighlights(x, y, -1, -1);
5584                 }
5585             }
5586         }
5587         return;
5588     }
5589
5590     /* fromX != -1 */
5591     if (clickType == Press && gameMode != EditPosition) {
5592         ChessSquare fromP;
5593         ChessSquare toP;
5594         int frc;
5595
5596         // ignore off-board to clicks
5597         if(y < 0 || x < 0) return;
5598
5599         /* Check if clicking again on the same color piece */
5600         fromP = boards[currentMove][fromY][fromX];
5601         toP = boards[currentMove][y][x];
5602         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5603         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5604              WhitePawn <= toP && toP <= WhiteKing &&
5605              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5606              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5607             (BlackPawn <= fromP && fromP <= BlackKing && 
5608              BlackPawn <= toP && toP <= BlackKing &&
5609              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5610              !(fromP == BlackKing && toP == BlackRook && frc))) {
5611             /* Clicked again on same color piece -- changed his mind */
5612             second = (x == fromX && y == fromY);
5613             if (appData.highlightDragging) {
5614                 SetHighlights(x, y, -1, -1);
5615             } else {
5616                 ClearHighlights();
5617             }
5618             if (OKToStartUserMove(x, y)) {
5619                 fromX = x;
5620                 fromY = y;
5621                 DragPieceBegin(xPix, yPix);
5622             }
5623             return;
5624         }
5625         // ignore clicks on holdings
5626         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5627     }
5628
5629     if (clickType == Release && x == fromX && y == fromY) {
5630         DragPieceEnd(xPix, yPix);
5631         if (appData.animateDragging) {
5632             /* Undo animation damage if any */
5633             DrawPosition(FALSE, NULL);
5634         }
5635         if (second) {
5636             /* Second up/down in same square; just abort move */
5637             second = 0;
5638             fromX = fromY = -1;
5639             ClearHighlights();
5640             gotPremove = 0;
5641             ClearPremoveHighlights();
5642         } else {
5643             /* First upclick in same square; start click-click mode */
5644             SetHighlights(x, y, -1, -1);
5645         }
5646         return;
5647     }
5648
5649     /* we now have a different from- and (possibly off-board) to-square */
5650     /* Completed move */
5651     toX = x;
5652     toY = y;
5653     saveAnimate = appData.animate;
5654     if (clickType == Press) {
5655         /* Finish clickclick move */
5656         if (appData.animate || appData.highlightLastMove) {
5657             SetHighlights(fromX, fromY, toX, toY);
5658         } else {
5659             ClearHighlights();
5660         }
5661     } else {
5662         /* Finish drag move */
5663         if (appData.highlightLastMove) {
5664             SetHighlights(fromX, fromY, toX, toY);
5665         } else {
5666             ClearHighlights();
5667         }
5668         DragPieceEnd(xPix, yPix);
5669         /* Don't animate move and drag both */
5670         appData.animate = FALSE;
5671     }
5672
5673     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5674     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5675         ClearHighlights();
5676         fromX = fromY = -1;
5677         DrawPosition(TRUE, NULL);
5678         return;
5679     }
5680
5681     // off-board moves should not be highlighted
5682     if(x < 0 || x < 0) ClearHighlights();
5683
5684     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5685         SetHighlights(fromX, fromY, toX, toY);
5686         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5687             // [HGM] super: promotion to captured piece selected from holdings
5688             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5689             promotionChoice = TRUE;
5690             // kludge follows to temporarily execute move on display, without promoting yet
5691             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5692             boards[currentMove][toY][toX] = p;
5693             DrawPosition(FALSE, boards[currentMove]);
5694             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5695             boards[currentMove][toY][toX] = q;
5696             DisplayMessage("Click in holdings to choose piece", "");
5697             return;
5698         }
5699         PromotionPopUp();
5700     } else {
5701         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5702         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5703         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5704         fromX = fromY = -1;
5705     }
5706     appData.animate = saveAnimate;
5707     if (appData.animate || appData.animateDragging) {
5708         /* Undo animation damage if needed */
5709         DrawPosition(FALSE, NULL);
5710     }
5711 }
5712
5713 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5714 {
5715 //    char * hint = lastHint;
5716     FrontEndProgramStats stats;
5717
5718     stats.which = cps == &first ? 0 : 1;
5719     stats.depth = cpstats->depth;
5720     stats.nodes = cpstats->nodes;
5721     stats.score = cpstats->score;
5722     stats.time = cpstats->time;
5723     stats.pv = cpstats->movelist;
5724     stats.hint = lastHint;
5725     stats.an_move_index = 0;
5726     stats.an_move_count = 0;
5727
5728     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5729         stats.hint = cpstats->move_name;
5730         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5731         stats.an_move_count = cpstats->nr_moves;
5732     }
5733
5734     SetProgramStats( &stats );
5735 }
5736
5737 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5738 {   // [HGM] book: this routine intercepts moves to simulate book replies
5739     char *bookHit = NULL;
5740
5741     //first determine if the incoming move brings opponent into his book
5742     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5743         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5744     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5745     if(bookHit != NULL && !cps->bookSuspend) {
5746         // make sure opponent is not going to reply after receiving move to book position
5747         SendToProgram("force\n", cps);
5748         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5749     }
5750     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5751     // now arrange restart after book miss
5752     if(bookHit) {
5753         // after a book hit we never send 'go', and the code after the call to this routine
5754         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5755         char buf[MSG_SIZ];
5756         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5757         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5758         SendToProgram(buf, cps);
5759         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5760     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5761         SendToProgram("go\n", cps);
5762         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5763     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5764         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5765             SendToProgram("go\n", cps); 
5766         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5767     }
5768     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5769 }
5770
5771 char *savedMessage;
5772 ChessProgramState *savedState;
5773 void DeferredBookMove(void)
5774 {
5775         if(savedState->lastPing != savedState->lastPong)
5776                     ScheduleDelayedEvent(DeferredBookMove, 10);
5777         else
5778         HandleMachineMove(savedMessage, savedState);
5779 }
5780
5781 void
5782 HandleMachineMove(message, cps)
5783      char *message;
5784      ChessProgramState *cps;
5785 {
5786     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5787     char realname[MSG_SIZ];
5788     int fromX, fromY, toX, toY;
5789     ChessMove moveType;
5790     char promoChar;
5791     char *p;
5792     int machineWhite;
5793     char *bookHit;
5794
5795 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5796     /*
5797      * Kludge to ignore BEL characters
5798      */
5799     while (*message == '\007') message++;
5800
5801     /*
5802      * [HGM] engine debug message: ignore lines starting with '#' character
5803      */
5804     if(cps->debug && *message == '#') return;
5805
5806     /*
5807      * Look for book output
5808      */
5809     if (cps == &first && bookRequested) {
5810         if (message[0] == '\t' || message[0] == ' ') {
5811             /* Part of the book output is here; append it */
5812             strcat(bookOutput, message);
5813             strcat(bookOutput, "  \n");
5814             return;
5815         } else if (bookOutput[0] != NULLCHAR) {
5816             /* All of book output has arrived; display it */
5817             char *p = bookOutput;
5818             while (*p != NULLCHAR) {
5819                 if (*p == '\t') *p = ' ';
5820                 p++;
5821             }
5822             DisplayInformation(bookOutput);
5823             bookRequested = FALSE;
5824             /* Fall through to parse the current output */
5825         }
5826     }
5827
5828     /*
5829      * Look for machine move.
5830      */
5831     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5832         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5833     {
5834         /* This method is only useful on engines that support ping */
5835         if (cps->lastPing != cps->lastPong) {
5836           if (gameMode == BeginningOfGame) {
5837             /* Extra move from before last new; ignore */
5838             if (appData.debugMode) {
5839                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5840             }
5841           } else {
5842             if (appData.debugMode) {
5843                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5844                         cps->which, gameMode);
5845             }
5846
5847             SendToProgram("undo\n", cps);
5848           }
5849           return;
5850         }
5851
5852         switch (gameMode) {
5853           case BeginningOfGame:
5854             /* Extra move from before last reset; ignore */
5855             if (appData.debugMode) {
5856                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5857             }
5858             return;
5859
5860           case EndOfGame:
5861           case IcsIdle:
5862           default:
5863             /* Extra move after we tried to stop.  The mode test is
5864                not a reliable way of detecting this problem, but it's
5865                the best we can do on engines that don't support ping.
5866             */
5867             if (appData.debugMode) {
5868                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5869                         cps->which, gameMode);
5870             }
5871             SendToProgram("undo\n", cps);
5872             return;
5873
5874           case MachinePlaysWhite:
5875           case IcsPlayingWhite:
5876             machineWhite = TRUE;
5877             break;
5878
5879           case MachinePlaysBlack:
5880           case IcsPlayingBlack:
5881             machineWhite = FALSE;
5882             break;
5883
5884           case TwoMachinesPlay:
5885             machineWhite = (cps->twoMachinesColor[0] == 'w');
5886             break;
5887         }
5888         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5889             if (appData.debugMode) {
5890                 fprintf(debugFP,
5891                         "Ignoring move out of turn by %s, gameMode %d"
5892                         ", forwardMost %d\n",
5893                         cps->which, gameMode, forwardMostMove);
5894             }
5895             return;
5896         }
5897
5898     if (appData.debugMode) { int f = forwardMostMove;
5899         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5900                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5901     }
5902         if(cps->alphaRank) AlphaRank(machineMove, 4);
5903         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5904                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5905             /* Machine move could not be parsed; ignore it. */
5906             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5907                     machineMove, cps->which);
5908             DisplayError(buf1, 0);
5909             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5910                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5911             if (gameMode == TwoMachinesPlay) {
5912               GameEnds(machineWhite ? BlackWins : WhiteWins,
5913                        buf1, GE_XBOARD);
5914             }
5915             return;
5916         }
5917
5918         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5919         /* So we have to redo legality test with true e.p. status here,  */
5920         /* to make sure an illegal e.p. capture does not slip through,   */
5921         /* to cause a forfeit on a justified illegal-move complaint      */
5922         /* of the opponent.                                              */
5923         if( gameMode==TwoMachinesPlay && appData.testLegality
5924             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5925                                                               ) {
5926            ChessMove moveType;
5927            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5928                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5929                              fromY, fromX, toY, toX, promoChar);
5930             if (appData.debugMode) {
5931                 int i;
5932                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5933                     castlingRights[forwardMostMove][i], castlingRank[i]);
5934                 fprintf(debugFP, "castling rights\n");
5935             }
5936             if(moveType == IllegalMove) {
5937                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5938                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5939                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5940                            buf1, GE_XBOARD);
5941                 return;
5942            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5943            /* [HGM] Kludge to handle engines that send FRC-style castling
5944               when they shouldn't (like TSCP-Gothic) */
5945            switch(moveType) {
5946              case WhiteASideCastleFR:
5947              case BlackASideCastleFR:
5948                toX+=2;
5949                currentMoveString[2]++;
5950                break;
5951              case WhiteHSideCastleFR:
5952              case BlackHSideCastleFR:
5953                toX--;
5954                currentMoveString[2]--;
5955                break;
5956              default: ; // nothing to do, but suppresses warning of pedantic compilers
5957            }
5958         }
5959         hintRequested = FALSE;
5960         lastHint[0] = NULLCHAR;
5961         bookRequested = FALSE;
5962         /* Program may be pondering now */
5963         cps->maybeThinking = TRUE;
5964         if (cps->sendTime == 2) cps->sendTime = 1;
5965         if (cps->offeredDraw) cps->offeredDraw--;
5966
5967 #if ZIPPY
5968         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5969             first.initDone) {
5970           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5971           ics_user_moved = 1;
5972           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5973                 char buf[3*MSG_SIZ];
5974
5975                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5976                         programStats.score / 100.,
5977                         programStats.depth,
5978                         programStats.time / 100.,
5979                         (unsigned int)programStats.nodes,
5980                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5981                         programStats.movelist);
5982                 SendToICS(buf);
5983 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5984           }
5985         }
5986 #endif
5987         /* currentMoveString is set as a side-effect of ParseOneMove */
5988         strcpy(machineMove, currentMoveString);
5989         strcat(machineMove, "\n");
5990         strcpy(moveList[forwardMostMove], machineMove);
5991
5992         /* [AS] Save move info and clear stats for next move */
5993         pvInfoList[ forwardMostMove ].score = programStats.score;
5994         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5995         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5996         ClearProgramStats();
5997         thinkOutput[0] = NULLCHAR;
5998         hiddenThinkOutputState = 0;
5999
6000         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6001
6002         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6003         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6004             int count = 0;
6005
6006             while( count < adjudicateLossPlies ) {
6007                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6008
6009                 if( count & 1 ) {
6010                     score = -score; /* Flip score for winning side */
6011                 }
6012
6013                 if( score > adjudicateLossThreshold ) {
6014                     break;
6015                 }
6016
6017                 count++;
6018             }
6019
6020             if( count >= adjudicateLossPlies ) {
6021                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6022
6023                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6024                     "Xboard adjudication", 
6025                     GE_XBOARD );
6026
6027                 return;
6028             }
6029         }
6030
6031         if( gameMode == TwoMachinesPlay ) {
6032           // [HGM] some adjudications useful with buggy engines
6033             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6034           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6035
6036
6037             if( appData.testLegality )
6038             {   /* [HGM] Some more adjudications for obstinate engines */
6039                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6040                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6041                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6042                 static int moveCount = 6;
6043                 ChessMove result;
6044                 char *reason = NULL;
6045
6046                 /* Count what is on board. */
6047                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6048                 {   ChessSquare p = boards[forwardMostMove][i][j];
6049                     int m=i;
6050
6051                     switch((int) p)
6052                     {   /* count B,N,R and other of each side */
6053                         case WhiteKing:
6054                         case BlackKing:
6055                              NrK++; break; // [HGM] atomic: count Kings
6056                         case WhiteKnight:
6057                              NrWN++; break;
6058                         case WhiteBishop:
6059                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6060                              bishopsColor |= 1 << ((i^j)&1);
6061                              NrWB++; break;
6062                         case BlackKnight:
6063                              NrBN++; break;
6064                         case BlackBishop:
6065                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6066                              bishopsColor |= 1 << ((i^j)&1);
6067                              NrBB++; break;
6068                         case WhiteRook:
6069                              NrWR++; break;
6070                         case BlackRook:
6071                              NrBR++; break;
6072                         case WhiteQueen:
6073                              NrWQ++; break;
6074                         case BlackQueen:
6075                              NrBQ++; break;
6076                         case EmptySquare: 
6077                              break;
6078                         case BlackPawn:
6079                              m = 7-i;
6080                         case WhitePawn:
6081                              PawnAdvance += m; NrPawns++;
6082                     }
6083                     NrPieces += (p != EmptySquare);
6084                     NrW += ((int)p < (int)BlackPawn);
6085                     if(gameInfo.variant == VariantXiangqi && 
6086                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6087                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6088                         NrW -= ((int)p < (int)BlackPawn);
6089                     }
6090                 }
6091
6092                 /* Some material-based adjudications that have to be made before stalemate test */
6093                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6094                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6095                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6096                      if(appData.checkMates) {
6097                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6098                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6100                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6101                          return;
6102                      }
6103                 }
6104
6105                 /* Bare King in Shatranj (loses) or Losers (wins) */
6106                 if( NrW == 1 || NrPieces - NrW == 1) {
6107                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6108                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6109                      if(appData.checkMates) {
6110                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6111                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6113                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6114                          return;
6115                      }
6116                   } else
6117                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6118                   {    /* bare King */
6119                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6120                         if(appData.checkMates) {
6121                             /* but only adjudicate if adjudication enabled */
6122                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6123                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6124                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6125                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6126                             return;
6127                         }
6128                   }
6129                 } else bare = 1;
6130
6131
6132             // don't wait for engine to announce game end if we can judge ourselves
6133             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6134                                        castlingRights[forwardMostMove]) ) {
6135               case MT_CHECK:
6136                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6137                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6138                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6139                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6140                             checkCnt++;
6141                         if(checkCnt >= 2) {
6142                             reason = "Xboard adjudication: 3rd check";
6143                             epStatus[forwardMostMove] = EP_CHECKMATE;
6144                             break;
6145                         }
6146                     }
6147                 }
6148               case MT_NONE:
6149               default:
6150                 break;
6151               case MT_STALEMATE:
6152               case MT_STAINMATE:
6153                 reason = "Xboard adjudication: Stalemate";
6154                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6155                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6156                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6157                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6158                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6159                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6160                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6161                                                                         EP_CHECKMATE : EP_WINS);
6162                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6163                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6164                 }
6165                 break;
6166               case MT_CHECKMATE:
6167                 reason = "Xboard adjudication: Checkmate";
6168                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6169                 break;
6170             }
6171
6172                 switch(i = epStatus[forwardMostMove]) {
6173                     case EP_STALEMATE:
6174                         result = GameIsDrawn; break;
6175                     case EP_CHECKMATE:
6176                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6177                     case EP_WINS:
6178                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6179                     default:
6180                         result = (ChessMove) 0;
6181                 }
6182                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6183                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6184                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6185                     GameEnds( result, reason, GE_XBOARD );
6186                     return;
6187                 }
6188
6189                 /* Next absolutely insufficient mating material. */
6190                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6191                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6192                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6193                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6194                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6195
6196                      /* always flag draws, for judging claims */
6197                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6198
6199                      if(appData.materialDraws) {
6200                          /* but only adjudicate them if adjudication enabled */
6201                          SendToProgram("force\n", cps->other); // suppress reply
6202                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6203                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6204                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6205                          return;
6206                      }
6207                 }
6208
6209                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6210                 if(NrPieces == 4 && 
6211                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6212                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6213                    || NrWN==2 || NrBN==2     /* KNNK */
6214                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6215                   ) ) {
6216                      if(--moveCount < 0 && appData.trivialDraws)
6217                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6218                           SendToProgram("force\n", cps->other); // suppress reply
6219                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6220                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6221                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6222                           return;
6223                      }
6224                 } else moveCount = 6;
6225             }
6226           }
6227           
6228           if (appData.debugMode) { int i;
6229             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6230                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6231                     appData.drawRepeats);
6232             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6233               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6234             
6235           }
6236
6237                 /* Check for rep-draws */
6238                 count = 0;
6239                 for(k = forwardMostMove-2;
6240                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6241                         epStatus[k] < EP_UNKNOWN &&
6242                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6243                     k-=2)
6244                 {   int rights=0;
6245                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6246                         /* compare castling rights */
6247                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6248                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6249                                 rights++; /* King lost rights, while rook still had them */
6250                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6251                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6252                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6253                                    rights++; /* but at least one rook lost them */
6254                         }
6255                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6256                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6257                                 rights++; 
6258                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6259                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6260                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6261                                    rights++;
6262                         }
6263                         if( rights == 0 && ++count > appData.drawRepeats-2
6264                             && appData.drawRepeats > 1) {
6265                              /* adjudicate after user-specified nr of repeats */
6266                              SendToProgram("force\n", cps->other); // suppress reply
6267                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6268                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6269                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6270                                 // [HGM] xiangqi: check for forbidden perpetuals
6271                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6272                                 for(m=forwardMostMove; m>k; m-=2) {
6273                                     if(MateTest(boards[m], PosFlags(m), 
6274                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6275                                         ourPerpetual = 0; // the current mover did not always check
6276                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6277                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6278                                         hisPerpetual = 0; // the opponent did not always check
6279                                 }
6280                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6281                                                                         ourPerpetual, hisPerpetual);
6282                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6283                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6284                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6285                                     return;
6286                                 }
6287                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6288                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6289                                 // Now check for perpetual chases
6290                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6291                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6292                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6293                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6294                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6295                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6296                                         return;
6297                                     }
6298                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6299                                         break; // Abort repetition-checking loop.
6300                                 }
6301                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6302                              }
6303                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6304                              return;
6305                         }
6306                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6307                              epStatus[forwardMostMove] = EP_REP_DRAW;
6308                     }
6309                 }
6310
6311                 /* Now we test for 50-move draws. Determine ply count */
6312                 count = forwardMostMove;
6313                 /* look for last irreversble move */
6314                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6315                     count--;
6316                 /* if we hit starting position, add initial plies */
6317                 if( count == backwardMostMove )
6318                     count -= initialRulePlies;
6319                 count = forwardMostMove - count; 
6320                 if( count >= 100)
6321                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6322                          /* this is used to judge if draw claims are legal */
6323                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6324                          SendToProgram("force\n", cps->other); // suppress reply
6325                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6326                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6327                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6328                          return;
6329                 }
6330
6331                 /* if draw offer is pending, treat it as a draw claim
6332                  * when draw condition present, to allow engines a way to
6333                  * claim draws before making their move to avoid a race
6334                  * condition occurring after their move
6335                  */
6336                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6337                          char *p = NULL;
6338                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6339                              p = "Draw claim: 50-move rule";
6340                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6341                              p = "Draw claim: 3-fold repetition";
6342                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6343                              p = "Draw claim: insufficient mating material";
6344                          if( p != NULL ) {
6345                              SendToProgram("force\n", cps->other); // suppress reply
6346                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6347                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6348                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6349                              return;
6350                          }
6351                 }
6352
6353
6354                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6355                     SendToProgram("force\n", cps->other); // suppress reply
6356                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6357                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6358
6359                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6360
6361                     return;
6362                 }
6363         }
6364
6365         bookHit = NULL;
6366         if (gameMode == TwoMachinesPlay) {
6367             /* [HGM] relaying draw offers moved to after reception of move */
6368             /* and interpreting offer as claim if it brings draw condition */
6369             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6370                 SendToProgram("draw\n", cps->other);
6371             }
6372             if (cps->other->sendTime) {
6373                 SendTimeRemaining(cps->other,
6374                                   cps->other->twoMachinesColor[0] == 'w');
6375             }
6376             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6377             if (firstMove && !bookHit) {
6378                 firstMove = FALSE;
6379                 if (cps->other->useColors) {
6380                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6381                 }
6382                 SendToProgram("go\n", cps->other);
6383             }
6384             cps->other->maybeThinking = TRUE;
6385         }
6386
6387         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388         
6389         if (!pausing && appData.ringBellAfterMoves) {
6390             RingBell();
6391         }
6392
6393         /* 
6394          * Reenable menu items that were disabled while
6395          * machine was thinking
6396          */
6397         if (gameMode != TwoMachinesPlay)
6398             SetUserThinkingEnables();
6399
6400         // [HGM] book: after book hit opponent has received move and is now in force mode
6401         // force the book reply into it, and then fake that it outputted this move by jumping
6402         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6403         if(bookHit) {
6404                 static char bookMove[MSG_SIZ]; // a bit generous?
6405
6406                 strcpy(bookMove, "move ");
6407                 strcat(bookMove, bookHit);
6408                 message = bookMove;
6409                 cps = cps->other;
6410                 programStats.nodes = programStats.depth = programStats.time = 
6411                 programStats.score = programStats.got_only_move = 0;
6412                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6413
6414                 if(cps->lastPing != cps->lastPong) {
6415                     savedMessage = message; // args for deferred call
6416                     savedState = cps;
6417                     ScheduleDelayedEvent(DeferredBookMove, 10);
6418                     return;
6419                 }
6420                 goto FakeBookMove;
6421         }
6422
6423         return;
6424     }
6425
6426     /* Set special modes for chess engines.  Later something general
6427      *  could be added here; for now there is just one kludge feature,
6428      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6429      *  when "xboard" is given as an interactive command.
6430      */
6431     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6432         cps->useSigint = FALSE;
6433         cps->useSigterm = FALSE;
6434     }
6435     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6436       ParseFeatures(message+8, cps);
6437       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6438     }
6439
6440     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6441      * want this, I was asked to put it in, and obliged.
6442      */
6443     if (!strncmp(message, "setboard ", 9)) {
6444         Board initial_position; int i;
6445
6446         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6447
6448         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6449             DisplayError(_("Bad FEN received from engine"), 0);
6450             return ;
6451         } else {
6452            Reset(TRUE, FALSE);
6453            CopyBoard(boards[0], initial_position);
6454            initialRulePlies = FENrulePlies;
6455            epStatus[0] = FENepStatus;
6456            for( i=0; i<nrCastlingRights; i++ )
6457                 castlingRights[0][i] = FENcastlingRights[i];
6458            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6459            else gameMode = MachinePlaysBlack;                 
6460            DrawPosition(FALSE, boards[currentMove]);
6461         }
6462         return;
6463     }
6464
6465     /*
6466      * Look for communication commands
6467      */
6468     if (!strncmp(message, "telluser ", 9)) {
6469         DisplayNote(message + 9);
6470         return;
6471     }
6472     if (!strncmp(message, "tellusererror ", 14)) {
6473         DisplayError(message + 14, 0);
6474         return;
6475     }
6476     if (!strncmp(message, "tellopponent ", 13)) {
6477       if (appData.icsActive) {
6478         if (loggedOn) {
6479           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6480           SendToICS(buf1);
6481         }
6482       } else {
6483         DisplayNote(message + 13);
6484       }
6485       return;
6486     }
6487     if (!strncmp(message, "tellothers ", 11)) {
6488       if (appData.icsActive) {
6489         if (loggedOn) {
6490           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6491           SendToICS(buf1);
6492         }
6493       }
6494       return;
6495     }
6496     if (!strncmp(message, "tellall ", 8)) {
6497       if (appData.icsActive) {
6498         if (loggedOn) {
6499           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6500           SendToICS(buf1);
6501         }
6502       } else {
6503         DisplayNote(message + 8);
6504       }
6505       return;
6506     }
6507     if (strncmp(message, "warning", 7) == 0) {
6508         /* Undocumented feature, use tellusererror in new code */
6509         DisplayError(message, 0);
6510         return;
6511     }
6512     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6513         strcpy(realname, cps->tidy);
6514         strcat(realname, " query");
6515         AskQuestion(realname, buf2, buf1, cps->pr);
6516         return;
6517     }
6518     /* Commands from the engine directly to ICS.  We don't allow these to be 
6519      *  sent until we are logged on. Crafty kibitzes have been known to 
6520      *  interfere with the login process.
6521      */
6522     if (loggedOn) {
6523         if (!strncmp(message, "tellics ", 8)) {
6524             SendToICS(message + 8);
6525             SendToICS("\n");
6526             return;
6527         }
6528         if (!strncmp(message, "tellicsnoalias ", 15)) {
6529             SendToICS(ics_prefix);
6530             SendToICS(message + 15);
6531             SendToICS("\n");
6532             return;
6533         }
6534         /* The following are for backward compatibility only */
6535         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6536             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6537             SendToICS(ics_prefix);
6538             SendToICS(message);
6539             SendToICS("\n");
6540             return;
6541         }
6542     }
6543     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6544         return;
6545     }
6546     /*
6547      * If the move is illegal, cancel it and redraw the board.
6548      * Also deal with other error cases.  Matching is rather loose
6549      * here to accommodate engines written before the spec.
6550      */
6551     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6552         strncmp(message, "Error", 5) == 0) {
6553         if (StrStr(message, "name") || 
6554             StrStr(message, "rating") || StrStr(message, "?") ||
6555             StrStr(message, "result") || StrStr(message, "board") ||
6556             StrStr(message, "bk") || StrStr(message, "computer") ||
6557             StrStr(message, "variant") || StrStr(message, "hint") ||
6558             StrStr(message, "random") || StrStr(message, "depth") ||
6559             StrStr(message, "accepted")) {
6560             return;
6561         }
6562         if (StrStr(message, "protover")) {
6563           /* Program is responding to input, so it's apparently done
6564              initializing, and this error message indicates it is
6565              protocol version 1.  So we don't need to wait any longer
6566              for it to initialize and send feature commands. */
6567           FeatureDone(cps, 1);
6568           cps->protocolVersion = 1;
6569           return;
6570         }
6571         cps->maybeThinking = FALSE;
6572
6573         if (StrStr(message, "draw")) {
6574             /* Program doesn't have "draw" command */
6575             cps->sendDrawOffers = 0;
6576             return;
6577         }
6578         if (cps->sendTime != 1 &&
6579             (StrStr(message, "time") || StrStr(message, "otim"))) {
6580           /* Program apparently doesn't have "time" or "otim" command */
6581           cps->sendTime = 0;
6582           return;
6583         }
6584         if (StrStr(message, "analyze")) {
6585             cps->analysisSupport = FALSE;
6586             cps->analyzing = FALSE;
6587             Reset(FALSE, TRUE);
6588             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6589             DisplayError(buf2, 0);
6590             return;
6591         }
6592         if (StrStr(message, "(no matching move)st")) {
6593           /* Special kludge for GNU Chess 4 only */
6594           cps->stKludge = TRUE;
6595           SendTimeControl(cps, movesPerSession, timeControl,
6596                           timeIncrement, appData.searchDepth,
6597                           searchTime);
6598           return;
6599         }
6600         if (StrStr(message, "(no matching move)sd")) {
6601           /* Special kludge for GNU Chess 4 only */
6602           cps->sdKludge = TRUE;
6603           SendTimeControl(cps, movesPerSession, timeControl,
6604                           timeIncrement, appData.searchDepth,
6605                           searchTime);
6606           return;
6607         }
6608         if (!StrStr(message, "llegal")) {
6609             return;
6610         }
6611         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6612             gameMode == IcsIdle) return;
6613         if (forwardMostMove <= backwardMostMove) return;
6614         if (pausing) PauseEvent();
6615       if(appData.forceIllegal) {
6616             // [HGM] illegal: machine refused move; force position after move into it
6617           SendToProgram("force\n", cps);
6618           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6619                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6620                 // when black is to move, while there might be nothing on a2 or black
6621                 // might already have the move. So send the board as if white has the move.
6622                 // But first we must change the stm of the engine, as it refused the last move
6623                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6624                 if(WhiteOnMove(forwardMostMove)) {
6625                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6626                     SendBoard(cps, forwardMostMove); // kludgeless board
6627                 } else {
6628                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6629                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6630                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6631                 }
6632           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6633             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6634                  gameMode == TwoMachinesPlay)
6635               SendToProgram("go\n", cps);
6636             return;
6637       } else
6638         if (gameMode == PlayFromGameFile) {
6639             /* Stop reading this game file */
6640             gameMode = EditGame;
6641             ModeHighlight();
6642         }
6643         currentMove = --forwardMostMove;
6644         DisplayMove(currentMove-1); /* before DisplayMoveError */
6645         SwitchClocks();
6646         DisplayBothClocks();
6647         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6648                 parseList[currentMove], cps->which);
6649         DisplayMoveError(buf1);
6650         DrawPosition(FALSE, boards[currentMove]);
6651
6652         /* [HGM] illegal-move claim should forfeit game when Xboard */
6653         /* only passes fully legal moves                            */
6654         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6655             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6656                                 "False illegal-move claim", GE_XBOARD );
6657         }
6658         return;
6659     }
6660     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6661         /* Program has a broken "time" command that
6662            outputs a string not ending in newline.
6663            Don't use it. */
6664         cps->sendTime = 0;
6665     }
6666     
6667     /*
6668      * If chess program startup fails, exit with an error message.
6669      * Attempts to recover here are futile.
6670      */
6671     if ((StrStr(message, "unknown host") != NULL)
6672         || (StrStr(message, "No remote directory") != NULL)
6673         || (StrStr(message, "not found") != NULL)
6674         || (StrStr(message, "No such file") != NULL)
6675         || (StrStr(message, "can't alloc") != NULL)
6676         || (StrStr(message, "Permission denied") != NULL)) {
6677
6678         cps->maybeThinking = FALSE;
6679         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6680                 cps->which, cps->program, cps->host, message);
6681         RemoveInputSource(cps->isr);
6682         DisplayFatalError(buf1, 0, 1);
6683         return;
6684     }
6685     
6686     /* 
6687      * Look for hint output
6688      */
6689     if (sscanf(message, "Hint: %s", buf1) == 1) {
6690         if (cps == &first && hintRequested) {
6691             hintRequested = FALSE;
6692             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6693                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6694                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6695                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6696                                     fromY, fromX, toY, toX, promoChar, buf1);
6697                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6698                 DisplayInformation(buf2);
6699             } else {
6700                 /* Hint move could not be parsed!? */
6701               snprintf(buf2, sizeof(buf2),
6702                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6703                         buf1, cps->which);
6704                 DisplayError(buf2, 0);
6705             }
6706         } else {
6707             strcpy(lastHint, buf1);
6708         }
6709         return;
6710     }
6711
6712     /*
6713      * Ignore other messages if game is not in progress
6714      */
6715     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6716         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6717
6718     /*
6719      * look for win, lose, draw, or draw offer
6720      */
6721     if (strncmp(message, "1-0", 3) == 0) {
6722         char *p, *q, *r = "";
6723         p = strchr(message, '{');
6724         if (p) {
6725             q = strchr(p, '}');
6726             if (q) {
6727                 *q = NULLCHAR;
6728                 r = p + 1;
6729             }
6730         }
6731         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6732         return;
6733     } else if (strncmp(message, "0-1", 3) == 0) {
6734         char *p, *q, *r = "";
6735         p = strchr(message, '{');
6736         if (p) {
6737             q = strchr(p, '}');
6738             if (q) {
6739                 *q = NULLCHAR;
6740                 r = p + 1;
6741             }
6742         }
6743         /* Kludge for Arasan 4.1 bug */
6744         if (strcmp(r, "Black resigns") == 0) {
6745             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6746             return;
6747         }
6748         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6749         return;
6750     } else if (strncmp(message, "1/2", 3) == 0) {
6751         char *p, *q, *r = "";
6752         p = strchr(message, '{');
6753         if (p) {
6754             q = strchr(p, '}');
6755             if (q) {
6756                 *q = NULLCHAR;
6757                 r = p + 1;
6758             }
6759         }
6760             
6761         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6762         return;
6763
6764     } else if (strncmp(message, "White resign", 12) == 0) {
6765         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6766         return;
6767     } else if (strncmp(message, "Black resign", 12) == 0) {
6768         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6769         return;
6770     } else if (strncmp(message, "White matches", 13) == 0 ||
6771                strncmp(message, "Black matches", 13) == 0   ) {
6772         /* [HGM] ignore GNUShogi noises */
6773         return;
6774     } else if (strncmp(message, "White", 5) == 0 &&
6775                message[5] != '(' &&
6776                StrStr(message, "Black") == NULL) {
6777         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6778         return;
6779     } else if (strncmp(message, "Black", 5) == 0 &&
6780                message[5] != '(') {
6781         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6782         return;
6783     } else if (strcmp(message, "resign") == 0 ||
6784                strcmp(message, "computer resigns") == 0) {
6785         switch (gameMode) {
6786           case MachinePlaysBlack:
6787           case IcsPlayingBlack:
6788             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6789             break;
6790           case MachinePlaysWhite:
6791           case IcsPlayingWhite:
6792             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6793             break;
6794           case TwoMachinesPlay:
6795             if (cps->twoMachinesColor[0] == 'w')
6796               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6797             else
6798               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6799             break;
6800           default:
6801             /* can't happen */
6802             break;
6803         }
6804         return;
6805     } else if (strncmp(message, "opponent mates", 14) == 0) {
6806         switch (gameMode) {
6807           case MachinePlaysBlack:
6808           case IcsPlayingBlack:
6809             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6810             break;
6811           case MachinePlaysWhite:
6812           case IcsPlayingWhite:
6813             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6814             break;
6815           case TwoMachinesPlay:
6816             if (cps->twoMachinesColor[0] == 'w')
6817               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6818             else
6819               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6820             break;
6821           default:
6822             /* can't happen */
6823             break;
6824         }
6825         return;
6826     } else if (strncmp(message, "computer mates", 14) == 0) {
6827         switch (gameMode) {
6828           case MachinePlaysBlack:
6829           case IcsPlayingBlack:
6830             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6831             break;
6832           case MachinePlaysWhite:
6833           case IcsPlayingWhite:
6834             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6835             break;
6836           case TwoMachinesPlay:
6837             if (cps->twoMachinesColor[0] == 'w')
6838               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6839             else
6840               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6841             break;
6842           default:
6843             /* can't happen */
6844             break;
6845         }
6846         return;
6847     } else if (strncmp(message, "checkmate", 9) == 0) {
6848         if (WhiteOnMove(forwardMostMove)) {
6849             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6850         } else {
6851             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6852         }
6853         return;
6854     } else if (strstr(message, "Draw") != NULL ||
6855                strstr(message, "game is a draw") != NULL) {
6856         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6857         return;
6858     } else if (strstr(message, "offer") != NULL &&
6859                strstr(message, "draw") != NULL) {
6860 #if ZIPPY
6861         if (appData.zippyPlay && first.initDone) {
6862             /* Relay offer to ICS */
6863             SendToICS(ics_prefix);
6864             SendToICS("draw\n");
6865         }
6866 #endif
6867         cps->offeredDraw = 2; /* valid until this engine moves twice */
6868         if (gameMode == TwoMachinesPlay) {
6869             if (cps->other->offeredDraw) {
6870                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6871             /* [HGM] in two-machine mode we delay relaying draw offer      */
6872             /* until after we also have move, to see if it is really claim */
6873             }
6874         } else if (gameMode == MachinePlaysWhite ||
6875                    gameMode == MachinePlaysBlack) {
6876           if (userOfferedDraw) {
6877             DisplayInformation(_("Machine accepts your draw offer"));
6878             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6879           } else {
6880             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6881           }
6882         }
6883     }
6884
6885     
6886     /*
6887      * Look for thinking output
6888      */
6889     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6890           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6891                                 ) {
6892         int plylev, mvleft, mvtot, curscore, time;
6893         char mvname[MOVE_LEN];
6894         u64 nodes; // [DM]
6895         char plyext;
6896         int ignore = FALSE;
6897         int prefixHint = FALSE;
6898         mvname[0] = NULLCHAR;
6899
6900         switch (gameMode) {
6901           case MachinePlaysBlack:
6902           case IcsPlayingBlack:
6903             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6904             break;
6905           case MachinePlaysWhite:
6906           case IcsPlayingWhite:
6907             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6908             break;
6909           case AnalyzeMode:
6910           case AnalyzeFile:
6911             break;
6912           case IcsObserving: /* [DM] icsEngineAnalyze */
6913             if (!appData.icsEngineAnalyze) ignore = TRUE;
6914             break;
6915           case TwoMachinesPlay:
6916             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6917                 ignore = TRUE;
6918             }
6919             break;
6920           default:
6921             ignore = TRUE;
6922             break;
6923         }
6924
6925         if (!ignore) {
6926             buf1[0] = NULLCHAR;
6927             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6928                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6929
6930                 if (plyext != ' ' && plyext != '\t') {
6931                     time *= 100;
6932                 }
6933
6934                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6935                 if( cps->scoreIsAbsolute && 
6936                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6937                 {
6938                     curscore = -curscore;
6939                 }
6940
6941
6942                 programStats.depth = plylev;
6943                 programStats.nodes = nodes;
6944                 programStats.time = time;
6945                 programStats.score = curscore;
6946                 programStats.got_only_move = 0;
6947
6948                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6949                         int ticklen;
6950
6951                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6952                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6953                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6954                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6955                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6956                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6957                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6958                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6959                 }
6960
6961                 /* Buffer overflow protection */
6962                 if (buf1[0] != NULLCHAR) {
6963                     if (strlen(buf1) >= sizeof(programStats.movelist)
6964                         && appData.debugMode) {
6965                         fprintf(debugFP,
6966                                 "PV is too long; using the first %u bytes.\n",
6967                                 (unsigned) sizeof(programStats.movelist) - 1);
6968                     }
6969
6970                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6971                 } else {
6972                     sprintf(programStats.movelist, " no PV\n");
6973                 }
6974
6975                 if (programStats.seen_stat) {
6976                     programStats.ok_to_send = 1;
6977                 }
6978
6979                 if (strchr(programStats.movelist, '(') != NULL) {
6980                     programStats.line_is_book = 1;
6981                     programStats.nr_moves = 0;
6982                     programStats.moves_left = 0;
6983                 } else {
6984                     programStats.line_is_book = 0;
6985                 }
6986
6987                 SendProgramStatsToFrontend( cps, &programStats );
6988
6989                 /* 
6990                     [AS] Protect the thinkOutput buffer from overflow... this
6991                     is only useful if buf1 hasn't overflowed first!
6992                 */
6993                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6994                         plylev, 
6995                         (gameMode == TwoMachinesPlay ?
6996                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6997                         ((double) curscore) / 100.0,
6998                         prefixHint ? lastHint : "",
6999                         prefixHint ? " " : "" );
7000
7001                 if( buf1[0] != NULLCHAR ) {
7002                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7003
7004                     if( strlen(buf1) > max_len ) {
7005                         if( appData.debugMode) {
7006                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7007                         }
7008                         buf1[max_len+1] = '\0';
7009                     }
7010
7011                     strcat( thinkOutput, buf1 );
7012                 }
7013
7014                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7015                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7016                     DisplayMove(currentMove - 1);
7017                 }
7018                 return;
7019
7020             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7021                 /* crafty (9.25+) says "(only move) <move>"
7022                  * if there is only 1 legal move
7023                  */
7024                 sscanf(p, "(only move) %s", buf1);
7025                 sprintf(thinkOutput, "%s (only move)", buf1);
7026                 sprintf(programStats.movelist, "%s (only move)", buf1);
7027                 programStats.depth = 1;
7028                 programStats.nr_moves = 1;
7029                 programStats.moves_left = 1;
7030                 programStats.nodes = 1;
7031                 programStats.time = 1;
7032                 programStats.got_only_move = 1;
7033
7034                 /* Not really, but we also use this member to
7035                    mean "line isn't going to change" (Crafty
7036                    isn't searching, so stats won't change) */
7037                 programStats.line_is_book = 1;
7038
7039                 SendProgramStatsToFrontend( cps, &programStats );
7040                 
7041                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7042                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7043                     DisplayMove(currentMove - 1);
7044                 }
7045                 return;
7046             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7047                               &time, &nodes, &plylev, &mvleft,
7048                               &mvtot, mvname) >= 5) {
7049                 /* The stat01: line is from Crafty (9.29+) in response
7050                    to the "." command */
7051                 programStats.seen_stat = 1;
7052                 cps->maybeThinking = TRUE;
7053
7054                 if (programStats.got_only_move || !appData.periodicUpdates)
7055                   return;
7056
7057                 programStats.depth = plylev;
7058                 programStats.time = time;
7059                 programStats.nodes = nodes;
7060                 programStats.moves_left = mvleft;
7061                 programStats.nr_moves = mvtot;
7062                 strcpy(programStats.move_name, mvname);
7063                 programStats.ok_to_send = 1;
7064                 programStats.movelist[0] = '\0';
7065
7066                 SendProgramStatsToFrontend( cps, &programStats );
7067
7068                 return;
7069
7070             } else if (strncmp(message,"++",2) == 0) {
7071                 /* Crafty 9.29+ outputs this */
7072                 programStats.got_fail = 2;
7073                 return;
7074
7075             } else if (strncmp(message,"--",2) == 0) {
7076                 /* Crafty 9.29+ outputs this */
7077                 programStats.got_fail = 1;
7078                 return;
7079
7080             } else if (thinkOutput[0] != NULLCHAR &&
7081                        strncmp(message, "    ", 4) == 0) {
7082                 unsigned message_len;
7083
7084                 p = message;
7085                 while (*p && *p == ' ') p++;
7086
7087                 message_len = strlen( p );
7088
7089                 /* [AS] Avoid buffer overflow */
7090                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7091                     strcat(thinkOutput, " ");
7092                     strcat(thinkOutput, p);
7093                 }
7094
7095                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7096                     strcat(programStats.movelist, " ");
7097                     strcat(programStats.movelist, p);
7098                 }
7099
7100                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7101                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7102                     DisplayMove(currentMove - 1);
7103                 }
7104                 return;
7105             }
7106         }
7107         else {
7108             buf1[0] = NULLCHAR;
7109
7110             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7111                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7112             {
7113                 ChessProgramStats cpstats;
7114
7115                 if (plyext != ' ' && plyext != '\t') {
7116                     time *= 100;
7117                 }
7118
7119                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7120                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7121                     curscore = -curscore;
7122                 }
7123
7124                 cpstats.depth = plylev;
7125                 cpstats.nodes = nodes;
7126                 cpstats.time = time;
7127                 cpstats.score = curscore;
7128                 cpstats.got_only_move = 0;
7129                 cpstats.movelist[0] = '\0';
7130
7131                 if (buf1[0] != NULLCHAR) {
7132                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7133                 }
7134
7135                 cpstats.ok_to_send = 0;
7136                 cpstats.line_is_book = 0;
7137                 cpstats.nr_moves = 0;
7138                 cpstats.moves_left = 0;
7139
7140                 SendProgramStatsToFrontend( cps, &cpstats );
7141             }
7142         }
7143     }
7144 }
7145
7146
7147 /* Parse a game score from the character string "game", and
7148    record it as the history of the current game.  The game
7149    score is NOT assumed to start from the standard position. 
7150    The display is not updated in any way.
7151    */
7152 void
7153 ParseGameHistory(game)
7154      char *game;
7155 {
7156     ChessMove moveType;
7157     int fromX, fromY, toX, toY, boardIndex;
7158     char promoChar;
7159     char *p, *q;
7160     char buf[MSG_SIZ];
7161
7162     if (appData.debugMode)
7163       fprintf(debugFP, "Parsing game history: %s\n", game);
7164
7165     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7166     gameInfo.site = StrSave(appData.icsHost);
7167     gameInfo.date = PGNDate();
7168     gameInfo.round = StrSave("-");
7169
7170     /* Parse out names of players */
7171     while (*game == ' ') game++;
7172     p = buf;
7173     while (*game != ' ') *p++ = *game++;
7174     *p = NULLCHAR;
7175     gameInfo.white = StrSave(buf);
7176     while (*game == ' ') game++;
7177     p = buf;
7178     while (*game != ' ' && *game != '\n') *p++ = *game++;
7179     *p = NULLCHAR;
7180     gameInfo.black = StrSave(buf);
7181
7182     /* Parse moves */
7183     boardIndex = blackPlaysFirst ? 1 : 0;
7184     yynewstr(game);
7185     for (;;) {
7186         yyboardindex = boardIndex;
7187         moveType = (ChessMove) yylex();
7188         switch (moveType) {
7189           case IllegalMove:             /* maybe suicide chess, etc. */
7190   if (appData.debugMode) {
7191     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7192     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7193     setbuf(debugFP, NULL);
7194   }
7195           case WhitePromotionChancellor:
7196           case BlackPromotionChancellor:
7197           case WhitePromotionArchbishop:
7198           case BlackPromotionArchbishop:
7199           case WhitePromotionQueen:
7200           case BlackPromotionQueen:
7201           case WhitePromotionRook:
7202           case BlackPromotionRook:
7203           case WhitePromotionBishop:
7204           case BlackPromotionBishop:
7205           case WhitePromotionKnight:
7206           case BlackPromotionKnight:
7207           case WhitePromotionKing:
7208           case BlackPromotionKing:
7209           case NormalMove:
7210           case WhiteCapturesEnPassant:
7211           case BlackCapturesEnPassant:
7212           case WhiteKingSideCastle:
7213           case WhiteQueenSideCastle:
7214           case BlackKingSideCastle:
7215           case BlackQueenSideCastle:
7216           case WhiteKingSideCastleWild:
7217           case WhiteQueenSideCastleWild:
7218           case BlackKingSideCastleWild:
7219           case BlackQueenSideCastleWild:
7220           /* PUSH Fabien */
7221           case WhiteHSideCastleFR:
7222           case WhiteASideCastleFR:
7223           case BlackHSideCastleFR:
7224           case BlackASideCastleFR:
7225           /* POP Fabien */
7226             fromX = currentMoveString[0] - AAA;
7227             fromY = currentMoveString[1] - ONE;
7228             toX = currentMoveString[2] - AAA;
7229             toY = currentMoveString[3] - ONE;
7230             promoChar = currentMoveString[4];
7231             break;
7232           case WhiteDrop:
7233           case BlackDrop:
7234             fromX = moveType == WhiteDrop ?
7235               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7236             (int) CharToPiece(ToLower(currentMoveString[0]));
7237             fromY = DROP_RANK;
7238             toX = currentMoveString[2] - AAA;
7239             toY = currentMoveString[3] - ONE;
7240             promoChar = NULLCHAR;
7241             break;
7242           case AmbiguousMove:
7243             /* bug? */
7244             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7245   if (appData.debugMode) {
7246     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7247     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7248     setbuf(debugFP, NULL);
7249   }
7250             DisplayError(buf, 0);
7251             return;
7252           case ImpossibleMove:
7253             /* bug? */
7254             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7255   if (appData.debugMode) {
7256     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7257     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7258     setbuf(debugFP, NULL);
7259   }
7260             DisplayError(buf, 0);
7261             return;
7262           case (ChessMove) 0:   /* end of file */
7263             if (boardIndex < backwardMostMove) {
7264                 /* Oops, gap.  How did that happen? */
7265                 DisplayError(_("Gap in move list"), 0);
7266                 return;
7267             }
7268             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7269             if (boardIndex > forwardMostMove) {
7270                 forwardMostMove = boardIndex;
7271             }
7272             return;
7273           case ElapsedTime:
7274             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7275                 strcat(parseList[boardIndex-1], " ");
7276                 strcat(parseList[boardIndex-1], yy_text);
7277             }
7278             continue;
7279           case Comment:
7280           case PGNTag:
7281           case NAG:
7282           default:
7283             /* ignore */
7284             continue;
7285           case WhiteWins:
7286           case BlackWins:
7287           case GameIsDrawn:
7288           case GameUnfinished:
7289             if (gameMode == IcsExamining) {
7290                 if (boardIndex < backwardMostMove) {
7291                     /* Oops, gap.  How did that happen? */
7292                     return;
7293                 }
7294                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7295                 return;
7296             }
7297             gameInfo.result = moveType;
7298             p = strchr(yy_text, '{');
7299             if (p == NULL) p = strchr(yy_text, '(');
7300             if (p == NULL) {
7301                 p = yy_text;
7302                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7303             } else {
7304                 q = strchr(p, *p == '{' ? '}' : ')');
7305                 if (q != NULL) *q = NULLCHAR;
7306                 p++;
7307             }
7308             gameInfo.resultDetails = StrSave(p);
7309             continue;
7310         }
7311         if (boardIndex >= forwardMostMove &&
7312             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7313             backwardMostMove = blackPlaysFirst ? 1 : 0;
7314             return;
7315         }
7316         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7317                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7318                                  parseList[boardIndex]);
7319         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7320         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7321         /* currentMoveString is set as a side-effect of yylex */
7322         strcpy(moveList[boardIndex], currentMoveString);
7323         strcat(moveList[boardIndex], "\n");
7324         boardIndex++;
7325         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7326                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7327         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7328                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7329           case MT_NONE:
7330           case MT_STALEMATE:
7331           default:
7332             break;
7333           case MT_CHECK:
7334             if(gameInfo.variant != VariantShogi)
7335                 strcat(parseList[boardIndex - 1], "+");
7336             break;
7337           case MT_CHECKMATE:
7338           case MT_STAINMATE:
7339             strcat(parseList[boardIndex - 1], "#");
7340             break;
7341         }
7342     }
7343 }
7344
7345
7346 /* Apply a move to the given board  */
7347 void
7348 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7349      int fromX, fromY, toX, toY;
7350      int promoChar;
7351      Board board;
7352      char *castling;
7353      char *ep;
7354 {
7355   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7356
7357     /* [HGM] compute & store e.p. status and castling rights for new position */
7358     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7359     { int i;
7360
7361       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7362       oldEP = *ep;
7363       *ep = EP_NONE;
7364
7365       if( board[toY][toX] != EmptySquare ) 
7366            *ep = EP_CAPTURE;  
7367
7368       if( board[fromY][fromX] == WhitePawn ) {
7369            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7370                *ep = EP_PAWN_MOVE;
7371            if( toY-fromY==2) {
7372                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7373                         gameInfo.variant != VariantBerolina || toX < fromX)
7374                       *ep = toX | berolina;
7375                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7376                         gameInfo.variant != VariantBerolina || toX > fromX) 
7377                       *ep = toX;
7378            }
7379       } else 
7380       if( board[fromY][fromX] == BlackPawn ) {
7381            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7382                *ep = EP_PAWN_MOVE; 
7383            if( toY-fromY== -2) {
7384                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7385                         gameInfo.variant != VariantBerolina || toX < fromX)
7386                       *ep = toX | berolina;
7387                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7388                         gameInfo.variant != VariantBerolina || toX > fromX) 
7389                       *ep = toX;
7390            }
7391        }
7392
7393        for(i=0; i<nrCastlingRights; i++) {
7394            if(castling[i] == fromX && castlingRank[i] == fromY ||
7395               castling[i] == toX   && castlingRank[i] == toY   
7396              ) castling[i] = -1; // revoke for moved or captured piece
7397        }
7398
7399     }
7400
7401   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7402   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7403        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7404          
7405   if (fromX == toX && fromY == toY) return;
7406
7407   if (fromY == DROP_RANK) {
7408         /* must be first */
7409         piece = board[toY][toX] = (ChessSquare) fromX;
7410   } else {
7411      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7412      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7413      if(gameInfo.variant == VariantKnightmate)
7414          king += (int) WhiteUnicorn - (int) WhiteKing;
7415
7416     /* Code added by Tord: */
7417     /* FRC castling assumed when king captures friendly rook. */
7418     if (board[fromY][fromX] == WhiteKing &&
7419              board[toY][toX] == WhiteRook) {
7420       board[fromY][fromX] = EmptySquare;
7421       board[toY][toX] = EmptySquare;
7422       if(toX > fromX) {
7423         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7424       } else {
7425         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7426       }
7427     } else if (board[fromY][fromX] == BlackKing &&
7428                board[toY][toX] == BlackRook) {
7429       board[fromY][fromX] = EmptySquare;
7430       board[toY][toX] = EmptySquare;
7431       if(toX > fromX) {
7432         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7433       } else {
7434         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7435       }
7436     /* End of code added by Tord */
7437
7438     } else if (board[fromY][fromX] == king
7439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7440         && toY == fromY && toX > fromX+1) {
7441         board[fromY][fromX] = EmptySquare;
7442         board[toY][toX] = king;
7443         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7444         board[fromY][BOARD_RGHT-1] = EmptySquare;
7445     } else if (board[fromY][fromX] == king
7446         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7447                && toY == fromY && toX < fromX-1) {
7448         board[fromY][fromX] = EmptySquare;
7449         board[toY][toX] = king;
7450         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7451         board[fromY][BOARD_LEFT] = EmptySquare;
7452     } else if (board[fromY][fromX] == WhitePawn
7453                && toY == BOARD_HEIGHT-1
7454                && gameInfo.variant != VariantXiangqi
7455                ) {
7456         /* white pawn promotion */
7457         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7458         if (board[toY][toX] == EmptySquare) {
7459             board[toY][toX] = WhiteQueen;
7460         }
7461         if(gameInfo.variant==VariantBughouse ||
7462            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7463             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7464         board[fromY][fromX] = EmptySquare;
7465     } else if ((fromY == BOARD_HEIGHT-4)
7466                && (toX != fromX)
7467                && gameInfo.variant != VariantXiangqi
7468                && gameInfo.variant != VariantBerolina
7469                && (board[fromY][fromX] == WhitePawn)
7470                && (board[toY][toX] == EmptySquare)) {
7471         board[fromY][fromX] = EmptySquare;
7472         board[toY][toX] = WhitePawn;
7473         captured = board[toY - 1][toX];
7474         board[toY - 1][toX] = EmptySquare;
7475     } else if ((fromY == BOARD_HEIGHT-4)
7476                && (toX == fromX)
7477                && gameInfo.variant == VariantBerolina
7478                && (board[fromY][fromX] == WhitePawn)
7479                && (board[toY][toX] == EmptySquare)) {
7480         board[fromY][fromX] = EmptySquare;
7481         board[toY][toX] = WhitePawn;
7482         if(oldEP & EP_BEROLIN_A) {
7483                 captured = board[fromY][fromX-1];
7484                 board[fromY][fromX-1] = EmptySquare;
7485         }else{  captured = board[fromY][fromX+1];
7486                 board[fromY][fromX+1] = EmptySquare;
7487         }
7488     } else if (board[fromY][fromX] == king
7489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490                && toY == fromY && toX > fromX+1) {
7491         board[fromY][fromX] = EmptySquare;
7492         board[toY][toX] = king;
7493         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7494         board[fromY][BOARD_RGHT-1] = EmptySquare;
7495     } else if (board[fromY][fromX] == king
7496         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7497                && toY == fromY && toX < fromX-1) {
7498         board[fromY][fromX] = EmptySquare;
7499         board[toY][toX] = king;
7500         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7501         board[fromY][BOARD_LEFT] = EmptySquare;
7502     } else if (fromY == 7 && fromX == 3
7503                && board[fromY][fromX] == BlackKing
7504                && toY == 7 && toX == 5) {
7505         board[fromY][fromX] = EmptySquare;
7506         board[toY][toX] = BlackKing;
7507         board[fromY][7] = EmptySquare;
7508         board[toY][4] = BlackRook;
7509     } else if (fromY == 7 && fromX == 3
7510                && board[fromY][fromX] == BlackKing
7511                && toY == 7 && toX == 1) {
7512         board[fromY][fromX] = EmptySquare;
7513         board[toY][toX] = BlackKing;
7514         board[fromY][0] = EmptySquare;
7515         board[toY][2] = BlackRook;
7516     } else if (board[fromY][fromX] == BlackPawn
7517                && toY == 0
7518                && gameInfo.variant != VariantXiangqi
7519                ) {
7520         /* black pawn promotion */
7521         board[0][toX] = CharToPiece(ToLower(promoChar));
7522         if (board[0][toX] == EmptySquare) {
7523             board[0][toX] = BlackQueen;
7524         }
7525         if(gameInfo.variant==VariantBughouse ||
7526            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7527             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7528         board[fromY][fromX] = EmptySquare;
7529     } else if ((fromY == 3)
7530                && (toX != fromX)
7531                && gameInfo.variant != VariantXiangqi
7532                && gameInfo.variant != VariantBerolina
7533                && (board[fromY][fromX] == BlackPawn)
7534                && (board[toY][toX] == EmptySquare)) {
7535         board[fromY][fromX] = EmptySquare;
7536         board[toY][toX] = BlackPawn;
7537         captured = board[toY + 1][toX];
7538         board[toY + 1][toX] = EmptySquare;
7539     } else if ((fromY == 3)
7540                && (toX == fromX)
7541                && gameInfo.variant == VariantBerolina
7542                && (board[fromY][fromX] == BlackPawn)
7543                && (board[toY][toX] == EmptySquare)) {
7544         board[fromY][fromX] = EmptySquare;
7545         board[toY][toX] = BlackPawn;
7546         if(oldEP & EP_BEROLIN_A) {
7547                 captured = board[fromY][fromX-1];
7548                 board[fromY][fromX-1] = EmptySquare;
7549         }else{  captured = board[fromY][fromX+1];
7550                 board[fromY][fromX+1] = EmptySquare;
7551         }
7552     } else {
7553         board[toY][toX] = board[fromY][fromX];
7554         board[fromY][fromX] = EmptySquare;
7555     }
7556
7557     /* [HGM] now we promote for Shogi, if needed */
7558     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7559         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7560   }
7561
7562     if (gameInfo.holdingsWidth != 0) {
7563
7564       /* !!A lot more code needs to be written to support holdings  */
7565       /* [HGM] OK, so I have written it. Holdings are stored in the */
7566       /* penultimate board files, so they are automaticlly stored   */
7567       /* in the game history.                                       */
7568       if (fromY == DROP_RANK) {
7569         /* Delete from holdings, by decreasing count */
7570         /* and erasing image if necessary            */
7571         p = (int) fromX;
7572         if(p < (int) BlackPawn) { /* white drop */
7573              p -= (int)WhitePawn;
7574                  p = PieceToNumber((ChessSquare)p);
7575              if(p >= gameInfo.holdingsSize) p = 0;
7576              if(--board[p][BOARD_WIDTH-2] <= 0)
7577                   board[p][BOARD_WIDTH-1] = EmptySquare;
7578              if((int)board[p][BOARD_WIDTH-2] < 0)
7579                         board[p][BOARD_WIDTH-2] = 0;
7580         } else {                  /* black drop */
7581              p -= (int)BlackPawn;
7582                  p = PieceToNumber((ChessSquare)p);
7583              if(p >= gameInfo.holdingsSize) p = 0;
7584              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7585                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7586              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7587                         board[BOARD_HEIGHT-1-p][1] = 0;
7588         }
7589       }
7590       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7591           && gameInfo.variant != VariantBughouse        ) {
7592         /* [HGM] holdings: Add to holdings, if holdings exist */
7593         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7594                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7595                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7596         }
7597         p = (int) captured;
7598         if (p >= (int) BlackPawn) {
7599           p -= (int)BlackPawn;
7600           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7601                   /* in Shogi restore piece to its original  first */
7602                   captured = (ChessSquare) (DEMOTED captured);
7603                   p = DEMOTED p;
7604           }
7605           p = PieceToNumber((ChessSquare)p);
7606           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7607           board[p][BOARD_WIDTH-2]++;
7608           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7609         } else {
7610           p -= (int)WhitePawn;
7611           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7612                   captured = (ChessSquare) (DEMOTED captured);
7613                   p = DEMOTED p;
7614           }
7615           p = PieceToNumber((ChessSquare)p);
7616           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7617           board[BOARD_HEIGHT-1-p][1]++;
7618           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7619         }
7620       }
7621     } else if (gameInfo.variant == VariantAtomic) {
7622       if (captured != EmptySquare) {
7623         int y, x;
7624         for (y = toY-1; y <= toY+1; y++) {
7625           for (x = toX-1; x <= toX+1; x++) {
7626             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7627                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7628               board[y][x] = EmptySquare;
7629             }
7630           }
7631         }
7632         board[toY][toX] = EmptySquare;
7633       }
7634     }
7635     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7636         /* [HGM] Shogi promotions */
7637         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7638     }
7639
7640     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7641                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7642         // [HGM] superchess: take promotion piece out of holdings
7643         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7644         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7645             if(!--board[k][BOARD_WIDTH-2])
7646                 board[k][BOARD_WIDTH-1] = EmptySquare;
7647         } else {
7648             if(!--board[BOARD_HEIGHT-1-k][1])
7649                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7650         }
7651     }
7652
7653 }
7654
7655 /* Updates forwardMostMove */
7656 void
7657 MakeMove(fromX, fromY, toX, toY, promoChar)
7658      int fromX, fromY, toX, toY;
7659      int promoChar;
7660 {
7661 //    forwardMostMove++; // [HGM] bare: moved downstream
7662
7663     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7664         int timeLeft; static int lastLoadFlag=0; int king, piece;
7665         piece = boards[forwardMostMove][fromY][fromX];
7666         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7667         if(gameInfo.variant == VariantKnightmate)
7668             king += (int) WhiteUnicorn - (int) WhiteKing;
7669         if(forwardMostMove == 0) {
7670             if(blackPlaysFirst) 
7671                 fprintf(serverMoves, "%s;", second.tidy);
7672             fprintf(serverMoves, "%s;", first.tidy);
7673             if(!blackPlaysFirst) 
7674                 fprintf(serverMoves, "%s;", second.tidy);
7675         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7676         lastLoadFlag = loadFlag;
7677         // print base move
7678         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7679         // print castling suffix
7680         if( toY == fromY && piece == king ) {
7681             if(toX-fromX > 1)
7682                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7683             if(fromX-toX >1)
7684                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7685         }
7686         // e.p. suffix
7687         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7688              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7689              boards[forwardMostMove][toY][toX] == EmptySquare
7690              && fromX != toX )
7691                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7692         // promotion suffix
7693         if(promoChar != NULLCHAR)
7694                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7695         if(!loadFlag) {
7696             fprintf(serverMoves, "/%d/%d",
7697                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7698             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7699             else                      timeLeft = blackTimeRemaining/1000;
7700             fprintf(serverMoves, "/%d", timeLeft);
7701         }
7702         fflush(serverMoves);
7703     }
7704
7705     if (forwardMostMove+1 >= MAX_MOVES) {
7706       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7707                         0, 1);
7708       return;
7709     }
7710     if (commentList[forwardMostMove+1] != NULL) {
7711         free(commentList[forwardMostMove+1]);
7712         commentList[forwardMostMove+1] = NULL;
7713     }
7714     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7715     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7716     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7717                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7718     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7719     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7720     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7721     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7722     gameInfo.result = GameUnfinished;
7723     if (gameInfo.resultDetails != NULL) {
7724         free(gameInfo.resultDetails);
7725         gameInfo.resultDetails = NULL;
7726     }
7727     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7728                               moveList[forwardMostMove - 1]);
7729     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7730                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7731                              fromY, fromX, toY, toX, promoChar,
7732                              parseList[forwardMostMove - 1]);
7733     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7734                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7735                             castlingRights[forwardMostMove]) ) {
7736       case MT_NONE:
7737       case MT_STALEMATE:
7738       default:
7739         break;
7740       case MT_CHECK:
7741         if(gameInfo.variant != VariantShogi)
7742             strcat(parseList[forwardMostMove - 1], "+");
7743         break;
7744       case MT_CHECKMATE:
7745       case MT_STAINMATE:
7746         strcat(parseList[forwardMostMove - 1], "#");
7747         break;
7748     }
7749     if (appData.debugMode) {
7750         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7751     }
7752
7753 }
7754
7755 /* Updates currentMove if not pausing */
7756 void
7757 ShowMove(fromX, fromY, toX, toY)
7758 {
7759     int instant = (gameMode == PlayFromGameFile) ?
7760         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7761     if(appData.noGUI) return;
7762     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7763         if (!instant) {
7764             if (forwardMostMove == currentMove + 1) {
7765                 AnimateMove(boards[forwardMostMove - 1],
7766                             fromX, fromY, toX, toY);
7767             }
7768             if (appData.highlightLastMove) {
7769                 SetHighlights(fromX, fromY, toX, toY);
7770             }
7771         }
7772         currentMove = forwardMostMove;
7773     }
7774
7775     if (instant) return;
7776
7777     DisplayMove(currentMove - 1);
7778     DrawPosition(FALSE, boards[currentMove]);
7779     DisplayBothClocks();
7780     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7781 }
7782
7783 void SendEgtPath(ChessProgramState *cps)
7784 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7785         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7786
7787         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7788
7789         while(*p) {
7790             char c, *q = name+1, *r, *s;
7791
7792             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7793             while(*p && *p != ',') *q++ = *p++;
7794             *q++ = ':'; *q = 0;
7795             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7796                 strcmp(name, ",nalimov:") == 0 ) {
7797                 // take nalimov path from the menu-changeable option first, if it is defined
7798                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7799                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7800             } else
7801             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7802                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7803                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7804                 s = r = StrStr(s, ":") + 1; // beginning of path info
7805                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7806                 c = *r; *r = 0;             // temporarily null-terminate path info
7807                     *--q = 0;               // strip of trailig ':' from name
7808                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7809                 *r = c;
7810                 SendToProgram(buf,cps);     // send egtbpath command for this format
7811             }
7812             if(*p == ',') p++; // read away comma to position for next format name
7813         }
7814 }
7815
7816 void
7817 InitChessProgram(cps, setup)
7818      ChessProgramState *cps;
7819      int setup; /* [HGM] needed to setup FRC opening position */
7820 {
7821     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7822     if (appData.noChessProgram) return;
7823     hintRequested = FALSE;
7824     bookRequested = FALSE;
7825
7826     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7827     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7828     if(cps->memSize) { /* [HGM] memory */
7829         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7830         SendToProgram(buf, cps);
7831     }
7832     SendEgtPath(cps); /* [HGM] EGT */
7833     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7834         sprintf(buf, "cores %d\n", appData.smpCores);
7835         SendToProgram(buf, cps);
7836     }
7837
7838     SendToProgram(cps->initString, cps);
7839     if (gameInfo.variant != VariantNormal &&
7840         gameInfo.variant != VariantLoadable
7841         /* [HGM] also send variant if board size non-standard */
7842         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7843                                             ) {
7844       char *v = VariantName(gameInfo.variant);
7845       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7846         /* [HGM] in protocol 1 we have to assume all variants valid */
7847         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7848         DisplayFatalError(buf, 0, 1);
7849         return;
7850       }
7851
7852       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7853       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7854       if( gameInfo.variant == VariantXiangqi )
7855            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7856       if( gameInfo.variant == VariantShogi )
7857            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7858       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7859            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7860       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7861                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7862            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863       if( gameInfo.variant == VariantCourier )
7864            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7865       if( gameInfo.variant == VariantSuper )
7866            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7867       if( gameInfo.variant == VariantGreat )
7868            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7869
7870       if(overruled) {
7871            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7872                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7873            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7874            if(StrStr(cps->variants, b) == NULL) { 
7875                // specific sized variant not known, check if general sizing allowed
7876                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7877                    if(StrStr(cps->variants, "boardsize") == NULL) {
7878                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7879                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7880                        DisplayFatalError(buf, 0, 1);
7881                        return;
7882                    }
7883                    /* [HGM] here we really should compare with the maximum supported board size */
7884                }
7885            }
7886       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7887       sprintf(buf, "variant %s\n", b);
7888       SendToProgram(buf, cps);
7889     }
7890     currentlyInitializedVariant = gameInfo.variant;
7891
7892     /* [HGM] send opening position in FRC to first engine */
7893     if(setup) {
7894           SendToProgram("force\n", cps);
7895           SendBoard(cps, 0);
7896           /* engine is now in force mode! Set flag to wake it up after first move. */
7897           setboardSpoiledMachineBlack = 1;
7898     }
7899
7900     if (cps->sendICS) {
7901       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7902       SendToProgram(buf, cps);
7903     }
7904     cps->maybeThinking = FALSE;
7905     cps->offeredDraw = 0;
7906     if (!appData.icsActive) {
7907         SendTimeControl(cps, movesPerSession, timeControl,
7908                         timeIncrement, appData.searchDepth,
7909                         searchTime);
7910     }
7911     if (appData.showThinking 
7912         // [HGM] thinking: four options require thinking output to be sent
7913         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7914                                 ) {
7915         SendToProgram("post\n", cps);
7916     }
7917     SendToProgram("hard\n", cps);
7918     if (!appData.ponderNextMove) {
7919         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7920            it without being sure what state we are in first.  "hard"
7921            is not a toggle, so that one is OK.
7922          */
7923         SendToProgram("easy\n", cps);
7924     }
7925     if (cps->usePing) {
7926       sprintf(buf, "ping %d\n", ++cps->lastPing);
7927       SendToProgram(buf, cps);
7928     }
7929     cps->initDone = TRUE;
7930 }   
7931
7932
7933 void
7934 StartChessProgram(cps)
7935      ChessProgramState *cps;
7936 {
7937     char buf[MSG_SIZ];
7938     int err;
7939
7940     if (appData.noChessProgram) return;
7941     cps->initDone = FALSE;
7942
7943     if (strcmp(cps->host, "localhost") == 0) {
7944         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7945     } else if (*appData.remoteShell == NULLCHAR) {
7946         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7947     } else {
7948         if (*appData.remoteUser == NULLCHAR) {
7949           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7950                     cps->program);
7951         } else {
7952           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7953                     cps->host, appData.remoteUser, cps->program);
7954         }
7955         err = StartChildProcess(buf, "", &cps->pr);
7956     }
7957     
7958     if (err != 0) {
7959         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7960         DisplayFatalError(buf, err, 1);
7961         cps->pr = NoProc;
7962         cps->isr = NULL;
7963         return;
7964     }
7965     
7966     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7967     if (cps->protocolVersion > 1) {
7968       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7969       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7970       cps->comboCnt = 0;  //                and values of combo boxes
7971       SendToProgram(buf, cps);
7972     } else {
7973       SendToProgram("xboard\n", cps);
7974     }
7975 }
7976
7977
7978 void
7979 TwoMachinesEventIfReady P((void))
7980 {
7981   if (first.lastPing != first.lastPong) {
7982     DisplayMessage("", _("Waiting for first chess program"));
7983     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7984     return;
7985   }
7986   if (second.lastPing != second.lastPong) {
7987     DisplayMessage("", _("Waiting for second chess program"));
7988     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7989     return;
7990   }
7991   ThawUI();
7992   TwoMachinesEvent();
7993 }
7994
7995 void
7996 NextMatchGame P((void))
7997 {
7998     int index; /* [HGM] autoinc: step load index during match */
7999     Reset(FALSE, TRUE);
8000     if (*appData.loadGameFile != NULLCHAR) {
8001         index = appData.loadGameIndex;
8002         if(index < 0) { // [HGM] autoinc
8003             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8004             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8005         } 
8006         LoadGameFromFile(appData.loadGameFile,
8007                          index,
8008                          appData.loadGameFile, FALSE);
8009     } else if (*appData.loadPositionFile != NULLCHAR) {
8010         index = appData.loadPositionIndex;
8011         if(index < 0) { // [HGM] autoinc
8012             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8013             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8014         } 
8015         LoadPositionFromFile(appData.loadPositionFile,
8016                              index,
8017                              appData.loadPositionFile);
8018     }
8019     TwoMachinesEventIfReady();
8020 }
8021
8022 void UserAdjudicationEvent( int result )
8023 {
8024     ChessMove gameResult = GameIsDrawn;
8025
8026     if( result > 0 ) {
8027         gameResult = WhiteWins;
8028     }
8029     else if( result < 0 ) {
8030         gameResult = BlackWins;
8031     }
8032
8033     if( gameMode == TwoMachinesPlay ) {
8034         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8035     }
8036 }
8037
8038
8039 // [HGM] save: calculate checksum of game to make games easily identifiable
8040 int StringCheckSum(char *s)
8041 {
8042         int i = 0;
8043         if(s==NULL) return 0;
8044         while(*s) i = i*259 + *s++;
8045         return i;
8046 }
8047
8048 int GameCheckSum()
8049 {
8050         int i, sum=0;
8051         for(i=backwardMostMove; i<forwardMostMove; i++) {
8052                 sum += pvInfoList[i].depth;
8053                 sum += StringCheckSum(parseList[i]);
8054                 sum += StringCheckSum(commentList[i]);
8055                 sum *= 261;
8056         }
8057         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8058         return sum + StringCheckSum(commentList[i]);
8059 } // end of save patch
8060
8061 void
8062 GameEnds(result, resultDetails, whosays)
8063      ChessMove result;
8064      char *resultDetails;
8065      int whosays;
8066 {
8067     GameMode nextGameMode;
8068     int isIcsGame;
8069     char buf[MSG_SIZ];
8070
8071     if(endingGame) return; /* [HGM] crash: forbid recursion */
8072     endingGame = 1;
8073
8074     if (appData.debugMode) {
8075       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8076               result, resultDetails ? resultDetails : "(null)", whosays);
8077     }
8078
8079     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8080         /* If we are playing on ICS, the server decides when the
8081            game is over, but the engine can offer to draw, claim 
8082            a draw, or resign. 
8083          */
8084 #if ZIPPY
8085         if (appData.zippyPlay && first.initDone) {
8086             if (result == GameIsDrawn) {
8087                 /* In case draw still needs to be claimed */
8088                 SendToICS(ics_prefix);
8089                 SendToICS("draw\n");
8090             } else if (StrCaseStr(resultDetails, "resign")) {
8091                 SendToICS(ics_prefix);
8092                 SendToICS("resign\n");
8093             }
8094         }
8095 #endif
8096         endingGame = 0; /* [HGM] crash */
8097         return;
8098     }
8099
8100     /* If we're loading the game from a file, stop */
8101     if (whosays == GE_FILE) {
8102       (void) StopLoadGameTimer();
8103       gameFileFP = NULL;
8104     }
8105
8106     /* Cancel draw offers */
8107     first.offeredDraw = second.offeredDraw = 0;
8108
8109     /* If this is an ICS game, only ICS can really say it's done;
8110        if not, anyone can. */
8111     isIcsGame = (gameMode == IcsPlayingWhite || 
8112                  gameMode == IcsPlayingBlack || 
8113                  gameMode == IcsObserving    || 
8114                  gameMode == IcsExamining);
8115
8116     if (!isIcsGame || whosays == GE_ICS) {
8117         /* OK -- not an ICS game, or ICS said it was done */
8118         StopClocks();
8119         if (!isIcsGame && !appData.noChessProgram) 
8120           SetUserThinkingEnables();
8121     
8122         /* [HGM] if a machine claims the game end we verify this claim */
8123         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8124             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8125                 char claimer;
8126                 ChessMove trueResult = (ChessMove) -1;
8127
8128                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8129                                             first.twoMachinesColor[0] :
8130                                             second.twoMachinesColor[0] ;
8131
8132                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8133                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8134                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8135                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8136                 } else
8137                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8138                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8139                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8140                 } else
8141                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8142                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8143                 }
8144
8145                 // now verify win claims, but not in drop games, as we don't understand those yet
8146                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8147                                                  || gameInfo.variant == VariantGreat) &&
8148                     (result == WhiteWins && claimer == 'w' ||
8149                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8150                       if (appData.debugMode) {
8151                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8152                                 result, epStatus[forwardMostMove], forwardMostMove);
8153                       }
8154                       if(result != trueResult) {
8155                               sprintf(buf, "False win claim: '%s'", resultDetails);
8156                               result = claimer == 'w' ? BlackWins : WhiteWins;
8157                               resultDetails = buf;
8158                       }
8159                 } else
8160                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8161                     && (forwardMostMove <= backwardMostMove ||
8162                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8163                         (claimer=='b')==(forwardMostMove&1))
8164                                                                                   ) {
8165                       /* [HGM] verify: draws that were not flagged are false claims */
8166                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8167                       result = claimer == 'w' ? BlackWins : WhiteWins;
8168                       resultDetails = buf;
8169                 }
8170                 /* (Claiming a loss is accepted no questions asked!) */
8171             }
8172             /* [HGM] bare: don't allow bare King to win */
8173             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8174                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8175                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8176                && result != GameIsDrawn)
8177             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8178                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8179                         int p = (int)boards[forwardMostMove][i][j] - color;
8180                         if(p >= 0 && p <= (int)WhiteKing) k++;
8181                 }
8182                 if (appData.debugMode) {
8183                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8184                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8185                 }
8186                 if(k <= 1) {
8187                         result = GameIsDrawn;
8188                         sprintf(buf, "%s but bare king", resultDetails);
8189                         resultDetails = buf;
8190                 }
8191             }
8192         }
8193
8194
8195         if(serverMoves != NULL && !loadFlag) { char c = '=';
8196             if(result==WhiteWins) c = '+';
8197             if(result==BlackWins) c = '-';
8198             if(resultDetails != NULL)
8199                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8200         }
8201         if (resultDetails != NULL) {
8202             gameInfo.result = result;
8203             gameInfo.resultDetails = StrSave(resultDetails);
8204
8205             /* display last move only if game was not loaded from file */
8206             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8207                 DisplayMove(currentMove - 1);
8208     
8209             if (forwardMostMove != 0) {
8210                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8211                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8212                                                                 ) {
8213                     if (*appData.saveGameFile != NULLCHAR) {
8214                         SaveGameToFile(appData.saveGameFile, TRUE);
8215                     } else if (appData.autoSaveGames) {
8216                         AutoSaveGame();
8217                     }
8218                     if (*appData.savePositionFile != NULLCHAR) {
8219                         SavePositionToFile(appData.savePositionFile);
8220                     }
8221                 }
8222             }
8223
8224             /* Tell program how game ended in case it is learning */
8225             /* [HGM] Moved this to after saving the PGN, just in case */
8226             /* engine died and we got here through time loss. In that */
8227             /* case we will get a fatal error writing the pipe, which */
8228             /* would otherwise lose us the PGN.                       */
8229             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8230             /* output during GameEnds should never be fatal anymore   */
8231             if (gameMode == MachinePlaysWhite ||
8232                 gameMode == MachinePlaysBlack ||
8233                 gameMode == TwoMachinesPlay ||
8234                 gameMode == IcsPlayingWhite ||
8235                 gameMode == IcsPlayingBlack ||
8236                 gameMode == BeginningOfGame) {
8237                 char buf[MSG_SIZ];
8238                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8239                         resultDetails);
8240                 if (first.pr != NoProc) {
8241                     SendToProgram(buf, &first);
8242                 }
8243                 if (second.pr != NoProc &&
8244                     gameMode == TwoMachinesPlay) {
8245                     SendToProgram(buf, &second);
8246                 }
8247             }
8248         }
8249
8250         if (appData.icsActive) {
8251             if (appData.quietPlay &&
8252                 (gameMode == IcsPlayingWhite ||
8253                  gameMode == IcsPlayingBlack)) {
8254                 SendToICS(ics_prefix);
8255                 SendToICS("set shout 1\n");
8256             }
8257             nextGameMode = IcsIdle;
8258             ics_user_moved = FALSE;
8259             /* clean up premove.  It's ugly when the game has ended and the
8260              * premove highlights are still on the board.
8261              */
8262             if (gotPremove) {
8263               gotPremove = FALSE;
8264               ClearPremoveHighlights();
8265               DrawPosition(FALSE, boards[currentMove]);
8266             }
8267             if (whosays == GE_ICS) {
8268                 switch (result) {
8269                 case WhiteWins:
8270                     if (gameMode == IcsPlayingWhite)
8271                         PlayIcsWinSound();
8272                     else if(gameMode == IcsPlayingBlack)
8273                         PlayIcsLossSound();
8274                     break;
8275                 case BlackWins:
8276                     if (gameMode == IcsPlayingBlack)
8277                         PlayIcsWinSound();
8278                     else if(gameMode == IcsPlayingWhite)
8279                         PlayIcsLossSound();
8280                     break;
8281                 case GameIsDrawn:
8282                     PlayIcsDrawSound();
8283                     break;
8284                 default:
8285                     PlayIcsUnfinishedSound();
8286                 }
8287             }
8288         } else if (gameMode == EditGame ||
8289                    gameMode == PlayFromGameFile || 
8290                    gameMode == AnalyzeMode || 
8291                    gameMode == AnalyzeFile) {
8292             nextGameMode = gameMode;
8293         } else {
8294             nextGameMode = EndOfGame;
8295         }
8296         pausing = FALSE;
8297         ModeHighlight();
8298     } else {
8299         nextGameMode = gameMode;
8300     }
8301
8302     if (appData.noChessProgram) {
8303         gameMode = nextGameMode;
8304         ModeHighlight();
8305         endingGame = 0; /* [HGM] crash */
8306         return;
8307     }
8308
8309     if (first.reuse) {
8310         /* Put first chess program into idle state */
8311         if (first.pr != NoProc &&
8312             (gameMode == MachinePlaysWhite ||
8313              gameMode == MachinePlaysBlack ||
8314              gameMode == TwoMachinesPlay ||
8315              gameMode == IcsPlayingWhite ||
8316              gameMode == IcsPlayingBlack ||
8317              gameMode == BeginningOfGame)) {
8318             SendToProgram("force\n", &first);
8319             if (first.usePing) {
8320               char buf[MSG_SIZ];
8321               sprintf(buf, "ping %d\n", ++first.lastPing);
8322               SendToProgram(buf, &first);
8323             }
8324         }
8325     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8326         /* Kill off first chess program */
8327         if (first.isr != NULL)
8328           RemoveInputSource(first.isr);
8329         first.isr = NULL;
8330     
8331         if (first.pr != NoProc) {
8332             ExitAnalyzeMode();
8333             DoSleep( appData.delayBeforeQuit );
8334             SendToProgram("quit\n", &first);
8335             DoSleep( appData.delayAfterQuit );
8336             DestroyChildProcess(first.pr, first.useSigterm);
8337         }
8338         first.pr = NoProc;
8339     }
8340     if (second.reuse) {
8341         /* Put second chess program into idle state */
8342         if (second.pr != NoProc &&
8343             gameMode == TwoMachinesPlay) {
8344             SendToProgram("force\n", &second);
8345             if (second.usePing) {
8346               char buf[MSG_SIZ];
8347               sprintf(buf, "ping %d\n", ++second.lastPing);
8348               SendToProgram(buf, &second);
8349             }
8350         }
8351     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8352         /* Kill off second chess program */
8353         if (second.isr != NULL)
8354           RemoveInputSource(second.isr);
8355         second.isr = NULL;
8356     
8357         if (second.pr != NoProc) {
8358             DoSleep( appData.delayBeforeQuit );
8359             SendToProgram("quit\n", &second);
8360             DoSleep( appData.delayAfterQuit );
8361             DestroyChildProcess(second.pr, second.useSigterm);
8362         }
8363         second.pr = NoProc;
8364     }
8365
8366     if (matchMode && gameMode == TwoMachinesPlay) {
8367         switch (result) {
8368         case WhiteWins:
8369           if (first.twoMachinesColor[0] == 'w') {
8370             first.matchWins++;
8371           } else {
8372             second.matchWins++;
8373           }
8374           break;
8375         case BlackWins:
8376           if (first.twoMachinesColor[0] == 'b') {
8377             first.matchWins++;
8378           } else {
8379             second.matchWins++;
8380           }
8381           break;
8382         default:
8383           break;
8384         }
8385         if (matchGame < appData.matchGames) {
8386             char *tmp;
8387             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8388                 tmp = first.twoMachinesColor;
8389                 first.twoMachinesColor = second.twoMachinesColor;
8390                 second.twoMachinesColor = tmp;
8391             }
8392             gameMode = nextGameMode;
8393             matchGame++;
8394             if(appData.matchPause>10000 || appData.matchPause<10)
8395                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8396             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8397             endingGame = 0; /* [HGM] crash */
8398             return;
8399         } else {
8400             char buf[MSG_SIZ];
8401             gameMode = nextGameMode;
8402             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8403                     first.tidy, second.tidy,
8404                     first.matchWins, second.matchWins,
8405                     appData.matchGames - (first.matchWins + second.matchWins));
8406             DisplayFatalError(buf, 0, 0);
8407         }
8408     }
8409     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8410         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8411       ExitAnalyzeMode();
8412     gameMode = nextGameMode;
8413     ModeHighlight();
8414     endingGame = 0;  /* [HGM] crash */
8415 }
8416
8417 /* Assumes program was just initialized (initString sent).
8418    Leaves program in force mode. */
8419 void
8420 FeedMovesToProgram(cps, upto) 
8421      ChessProgramState *cps;
8422      int upto;
8423 {
8424     int i;
8425     
8426     if (appData.debugMode)
8427       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8428               startedFromSetupPosition ? "position and " : "",
8429               backwardMostMove, upto, cps->which);
8430     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8431         // [HGM] variantswitch: make engine aware of new variant
8432         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8433                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8434         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8435         SendToProgram(buf, cps);
8436         currentlyInitializedVariant = gameInfo.variant;
8437     }
8438     SendToProgram("force\n", cps);
8439     if (startedFromSetupPosition) {
8440         SendBoard(cps, backwardMostMove);
8441     if (appData.debugMode) {
8442         fprintf(debugFP, "feedMoves\n");
8443     }
8444     }
8445     for (i = backwardMostMove; i < upto; i++) {
8446         SendMoveToProgram(i, cps);
8447     }
8448 }
8449
8450
8451 void
8452 ResurrectChessProgram()
8453 {
8454      /* The chess program may have exited.
8455         If so, restart it and feed it all the moves made so far. */
8456
8457     if (appData.noChessProgram || first.pr != NoProc) return;
8458     
8459     StartChessProgram(&first);
8460     InitChessProgram(&first, FALSE);
8461     FeedMovesToProgram(&first, currentMove);
8462
8463     if (!first.sendTime) {
8464         /* can't tell gnuchess what its clock should read,
8465            so we bow to its notion. */
8466         ResetClocks();
8467         timeRemaining[0][currentMove] = whiteTimeRemaining;
8468         timeRemaining[1][currentMove] = blackTimeRemaining;
8469     }
8470
8471     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8472                 appData.icsEngineAnalyze) && first.analysisSupport) {
8473       SendToProgram("analyze\n", &first);
8474       first.analyzing = TRUE;
8475     }
8476 }
8477
8478 /*
8479  * Button procedures
8480  */
8481 void
8482 Reset(redraw, init)
8483      int redraw, init;
8484 {
8485     int i;
8486
8487     if (appData.debugMode) {
8488         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8489                 redraw, init, gameMode);
8490     }
8491     pausing = pauseExamInvalid = FALSE;
8492     startedFromSetupPosition = blackPlaysFirst = FALSE;
8493     firstMove = TRUE;
8494     whiteFlag = blackFlag = FALSE;
8495     userOfferedDraw = FALSE;
8496     hintRequested = bookRequested = FALSE;
8497     first.maybeThinking = FALSE;
8498     second.maybeThinking = FALSE;
8499     first.bookSuspend = FALSE; // [HGM] book
8500     second.bookSuspend = FALSE;
8501     thinkOutput[0] = NULLCHAR;
8502     lastHint[0] = NULLCHAR;
8503     ClearGameInfo(&gameInfo);
8504     gameInfo.variant = StringToVariant(appData.variant);
8505     ics_user_moved = ics_clock_paused = FALSE;
8506     ics_getting_history = H_FALSE;
8507     ics_gamenum = -1;
8508     white_holding[0] = black_holding[0] = NULLCHAR;
8509     ClearProgramStats();
8510     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8511     
8512     ResetFrontEnd();
8513     ClearHighlights();
8514     flipView = appData.flipView;
8515     ClearPremoveHighlights();
8516     gotPremove = FALSE;
8517     alarmSounded = FALSE;
8518
8519     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8520     if(appData.serverMovesName != NULL) {
8521         /* [HGM] prepare to make moves file for broadcasting */
8522         clock_t t = clock();
8523         if(serverMoves != NULL) fclose(serverMoves);
8524         serverMoves = fopen(appData.serverMovesName, "r");
8525         if(serverMoves != NULL) {
8526             fclose(serverMoves);
8527             /* delay 15 sec before overwriting, so all clients can see end */
8528             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8529         }
8530         serverMoves = fopen(appData.serverMovesName, "w");
8531     }
8532
8533     ExitAnalyzeMode();
8534     gameMode = BeginningOfGame;
8535     ModeHighlight();
8536     if(appData.icsActive) gameInfo.variant = VariantNormal;
8537     currentMove = forwardMostMove = backwardMostMove = 0;
8538     InitPosition(redraw);
8539     for (i = 0; i < MAX_MOVES; i++) {
8540         if (commentList[i] != NULL) {
8541             free(commentList[i]);
8542             commentList[i] = NULL;
8543         }
8544     }
8545     ResetClocks();
8546     timeRemaining[0][0] = whiteTimeRemaining;
8547     timeRemaining[1][0] = blackTimeRemaining;
8548     if (first.pr == NULL) {
8549         StartChessProgram(&first);
8550     }
8551     if (init) {
8552             InitChessProgram(&first, startedFromSetupPosition);
8553     }
8554     DisplayTitle("");
8555     DisplayMessage("", "");
8556     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8557     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8558 }
8559
8560 void
8561 AutoPlayGameLoop()
8562 {
8563     for (;;) {
8564         if (!AutoPlayOneMove())
8565           return;
8566         if (matchMode || appData.timeDelay == 0)
8567           continue;
8568         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8569           return;
8570         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8571         break;
8572     }
8573 }
8574
8575
8576 int
8577 AutoPlayOneMove()
8578 {
8579     int fromX, fromY, toX, toY;
8580
8581     if (appData.debugMode) {
8582       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8583     }
8584
8585     if (gameMode != PlayFromGameFile)
8586       return FALSE;
8587
8588     if (currentMove >= forwardMostMove) {
8589       gameMode = EditGame;
8590       ModeHighlight();
8591
8592       /* [AS] Clear current move marker at the end of a game */
8593       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8594
8595       return FALSE;
8596     }
8597     
8598     toX = moveList[currentMove][2] - AAA;
8599     toY = moveList[currentMove][3] - ONE;
8600
8601     if (moveList[currentMove][1] == '@') {
8602         if (appData.highlightLastMove) {
8603             SetHighlights(-1, -1, toX, toY);
8604         }
8605     } else {
8606         fromX = moveList[currentMove][0] - AAA;
8607         fromY = moveList[currentMove][1] - ONE;
8608
8609         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8610
8611         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8612
8613         if (appData.highlightLastMove) {
8614             SetHighlights(fromX, fromY, toX, toY);
8615         }
8616     }
8617     DisplayMove(currentMove);
8618     SendMoveToProgram(currentMove++, &first);
8619     DisplayBothClocks();
8620     DrawPosition(FALSE, boards[currentMove]);
8621     // [HGM] PV info: always display, routine tests if empty
8622     DisplayComment(currentMove - 1, commentList[currentMove]);
8623     return TRUE;
8624 }
8625
8626
8627 int
8628 LoadGameOneMove(readAhead)
8629      ChessMove readAhead;
8630 {
8631     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8632     char promoChar = NULLCHAR;
8633     ChessMove moveType;
8634     char move[MSG_SIZ];
8635     char *p, *q;
8636     
8637     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8638         gameMode != AnalyzeMode && gameMode != Training) {
8639         gameFileFP = NULL;
8640         return FALSE;
8641     }
8642     
8643     yyboardindex = forwardMostMove;
8644     if (readAhead != (ChessMove)0) {
8645       moveType = readAhead;
8646     } else {
8647       if (gameFileFP == NULL)
8648           return FALSE;
8649       moveType = (ChessMove) yylex();
8650     }
8651     
8652     done = FALSE;
8653     switch (moveType) {
8654       case Comment:
8655         if (appData.debugMode) 
8656           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8657         p = yy_text;
8658         if (*p == '{' || *p == '[' || *p == '(') {
8659             p[strlen(p) - 1] = NULLCHAR;
8660             p++;
8661         }
8662
8663         /* append the comment but don't display it */
8664         while (*p == '\n') p++;
8665         AppendComment(currentMove, p);
8666         return TRUE;
8667
8668       case WhiteCapturesEnPassant:
8669       case BlackCapturesEnPassant:
8670       case WhitePromotionChancellor:
8671       case BlackPromotionChancellor:
8672       case WhitePromotionArchbishop:
8673       case BlackPromotionArchbishop:
8674       case WhitePromotionCentaur:
8675       case BlackPromotionCentaur:
8676       case WhitePromotionQueen:
8677       case BlackPromotionQueen:
8678       case WhitePromotionRook:
8679       case BlackPromotionRook:
8680       case WhitePromotionBishop:
8681       case BlackPromotionBishop:
8682       case WhitePromotionKnight:
8683       case BlackPromotionKnight:
8684       case WhitePromotionKing:
8685       case BlackPromotionKing:
8686       case NormalMove:
8687       case WhiteKingSideCastle:
8688       case WhiteQueenSideCastle:
8689       case BlackKingSideCastle:
8690       case BlackQueenSideCastle:
8691       case WhiteKingSideCastleWild:
8692       case WhiteQueenSideCastleWild:
8693       case BlackKingSideCastleWild:
8694       case BlackQueenSideCastleWild:
8695       /* PUSH Fabien */
8696       case WhiteHSideCastleFR:
8697       case WhiteASideCastleFR:
8698       case BlackHSideCastleFR:
8699       case BlackASideCastleFR:
8700       /* POP Fabien */
8701         if (appData.debugMode)
8702           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8703         fromX = currentMoveString[0] - AAA;
8704         fromY = currentMoveString[1] - ONE;
8705         toX = currentMoveString[2] - AAA;
8706         toY = currentMoveString[3] - ONE;
8707         promoChar = currentMoveString[4];
8708         break;
8709
8710       case WhiteDrop:
8711       case BlackDrop:
8712         if (appData.debugMode)
8713           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8714         fromX = moveType == WhiteDrop ?
8715           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8716         (int) CharToPiece(ToLower(currentMoveString[0]));
8717         fromY = DROP_RANK;
8718         toX = currentMoveString[2] - AAA;
8719         toY = currentMoveString[3] - ONE;
8720         break;
8721
8722       case WhiteWins:
8723       case BlackWins:
8724       case GameIsDrawn:
8725       case GameUnfinished:
8726         if (appData.debugMode)
8727           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8728         p = strchr(yy_text, '{');
8729         if (p == NULL) p = strchr(yy_text, '(');
8730         if (p == NULL) {
8731             p = yy_text;
8732             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8733         } else {
8734             q = strchr(p, *p == '{' ? '}' : ')');
8735             if (q != NULL) *q = NULLCHAR;
8736             p++;
8737         }
8738         GameEnds(moveType, p, GE_FILE);
8739         done = TRUE;
8740         if (cmailMsgLoaded) {
8741             ClearHighlights();
8742             flipView = WhiteOnMove(currentMove);
8743             if (moveType == GameUnfinished) flipView = !flipView;
8744             if (appData.debugMode)
8745               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8746         }
8747         break;
8748
8749       case (ChessMove) 0:       /* end of file */
8750         if (appData.debugMode)
8751           fprintf(debugFP, "Parser hit end of file\n");
8752         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8753                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8754           case MT_NONE:
8755           case MT_CHECK:
8756             break;
8757           case MT_CHECKMATE:
8758           case MT_STAINMATE:
8759             if (WhiteOnMove(currentMove)) {
8760                 GameEnds(BlackWins, "Black mates", GE_FILE);
8761             } else {
8762                 GameEnds(WhiteWins, "White mates", GE_FILE);
8763             }
8764             break;
8765           case MT_STALEMATE:
8766             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8767             break;
8768         }
8769         done = TRUE;
8770         break;
8771
8772       case MoveNumberOne:
8773         if (lastLoadGameStart == GNUChessGame) {
8774             /* GNUChessGames have numbers, but they aren't move numbers */
8775             if (appData.debugMode)
8776               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8777                       yy_text, (int) moveType);
8778             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8779         }
8780         /* else fall thru */
8781
8782       case XBoardGame:
8783       case GNUChessGame:
8784       case PGNTag:
8785         /* Reached start of next game in file */
8786         if (appData.debugMode)
8787           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8788         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8789                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8790           case MT_NONE:
8791           case MT_CHECK:
8792             break;
8793           case MT_CHECKMATE:
8794           case MT_STAINMATE:
8795             if (WhiteOnMove(currentMove)) {
8796                 GameEnds(BlackWins, "Black mates", GE_FILE);
8797             } else {
8798                 GameEnds(WhiteWins, "White mates", GE_FILE);
8799             }
8800             break;
8801           case MT_STALEMATE:
8802             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8803             break;
8804         }
8805         done = TRUE;
8806         break;
8807
8808       case PositionDiagram:     /* should not happen; ignore */
8809       case ElapsedTime:         /* ignore */
8810       case NAG:                 /* ignore */
8811         if (appData.debugMode)
8812           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8813                   yy_text, (int) moveType);
8814         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8815
8816       case IllegalMove:
8817         if (appData.testLegality) {
8818             if (appData.debugMode)
8819               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8820             sprintf(move, _("Illegal move: %d.%s%s"),
8821                     (forwardMostMove / 2) + 1,
8822                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8823             DisplayError(move, 0);
8824             done = TRUE;
8825         } else {
8826             if (appData.debugMode)
8827               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8828                       yy_text, currentMoveString);
8829             fromX = currentMoveString[0] - AAA;
8830             fromY = currentMoveString[1] - ONE;
8831             toX = currentMoveString[2] - AAA;
8832             toY = currentMoveString[3] - ONE;
8833             promoChar = currentMoveString[4];
8834         }
8835         break;
8836
8837       case AmbiguousMove:
8838         if (appData.debugMode)
8839           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8840         sprintf(move, _("Ambiguous move: %d.%s%s"),
8841                 (forwardMostMove / 2) + 1,
8842                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8843         DisplayError(move, 0);
8844         done = TRUE;
8845         break;
8846
8847       default:
8848       case ImpossibleMove:
8849         if (appData.debugMode)
8850           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8851         sprintf(move, _("Illegal move: %d.%s%s"),
8852                 (forwardMostMove / 2) + 1,
8853                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8854         DisplayError(move, 0);
8855         done = TRUE;
8856         break;
8857     }
8858
8859     if (done) {
8860         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8861             DrawPosition(FALSE, boards[currentMove]);
8862             DisplayBothClocks();
8863             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8864               DisplayComment(currentMove - 1, commentList[currentMove]);
8865         }
8866         (void) StopLoadGameTimer();
8867         gameFileFP = NULL;
8868         cmailOldMove = forwardMostMove;
8869         return FALSE;
8870     } else {
8871         /* currentMoveString is set as a side-effect of yylex */
8872         strcat(currentMoveString, "\n");
8873         strcpy(moveList[forwardMostMove], currentMoveString);
8874         
8875         thinkOutput[0] = NULLCHAR;
8876         MakeMove(fromX, fromY, toX, toY, promoChar);
8877         currentMove = forwardMostMove;
8878         return TRUE;
8879     }
8880 }
8881
8882 /* Load the nth game from the given file */
8883 int
8884 LoadGameFromFile(filename, n, title, useList)
8885      char *filename;
8886      int n;
8887      char *title;
8888      /*Boolean*/ int useList;
8889 {
8890     FILE *f;
8891     char buf[MSG_SIZ];
8892
8893     if (strcmp(filename, "-") == 0) {
8894         f = stdin;
8895         title = "stdin";
8896     } else {
8897         f = fopen(filename, "rb");
8898         if (f == NULL) {
8899           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8900             DisplayError(buf, errno);
8901             return FALSE;
8902         }
8903     }
8904     if (fseek(f, 0, 0) == -1) {
8905         /* f is not seekable; probably a pipe */
8906         useList = FALSE;
8907     }
8908     if (useList && n == 0) {
8909         int error = GameListBuild(f);
8910         if (error) {
8911             DisplayError(_("Cannot build game list"), error);
8912         } else if (!ListEmpty(&gameList) &&
8913                    ((ListGame *) gameList.tailPred)->number > 1) {
8914             GameListPopUp(f, title);
8915             return TRUE;
8916         }
8917         GameListDestroy();
8918         n = 1;
8919     }
8920     if (n == 0) n = 1;
8921     return LoadGame(f, n, title, FALSE);
8922 }
8923
8924
8925 void
8926 MakeRegisteredMove()
8927 {
8928     int fromX, fromY, toX, toY;
8929     char promoChar;
8930     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8931         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8932           case CMAIL_MOVE:
8933           case CMAIL_DRAW:
8934             if (appData.debugMode)
8935               fprintf(debugFP, "Restoring %s for game %d\n",
8936                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8937     
8938             thinkOutput[0] = NULLCHAR;
8939             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8940             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8941             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8942             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8943             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8944             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8945             MakeMove(fromX, fromY, toX, toY, promoChar);
8946             ShowMove(fromX, fromY, toX, toY);
8947               
8948             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8949                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8950               case MT_NONE:
8951               case MT_CHECK:
8952                 break;
8953                 
8954               case MT_CHECKMATE:
8955               case MT_STAINMATE:
8956                 if (WhiteOnMove(currentMove)) {
8957                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8958                 } else {
8959                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8960                 }
8961                 break;
8962                 
8963               case MT_STALEMATE:
8964                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8965                 break;
8966             }
8967
8968             break;
8969             
8970           case CMAIL_RESIGN:
8971             if (WhiteOnMove(currentMove)) {
8972                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8973             } else {
8974                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8975             }
8976             break;
8977             
8978           case CMAIL_ACCEPT:
8979             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8980             break;
8981               
8982           default:
8983             break;
8984         }
8985     }
8986
8987     return;
8988 }
8989
8990 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8991 int
8992 CmailLoadGame(f, gameNumber, title, useList)
8993      FILE *f;
8994      int gameNumber;
8995      char *title;
8996      int useList;
8997 {
8998     int retVal;
8999
9000     if (gameNumber > nCmailGames) {
9001         DisplayError(_("No more games in this message"), 0);
9002         return FALSE;
9003     }
9004     if (f == lastLoadGameFP) {
9005         int offset = gameNumber - lastLoadGameNumber;
9006         if (offset == 0) {
9007             cmailMsg[0] = NULLCHAR;
9008             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9009                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9010                 nCmailMovesRegistered--;
9011             }
9012             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9013             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9014                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9015             }
9016         } else {
9017             if (! RegisterMove()) return FALSE;
9018         }
9019     }
9020
9021     retVal = LoadGame(f, gameNumber, title, useList);
9022
9023     /* Make move registered during previous look at this game, if any */
9024     MakeRegisteredMove();
9025
9026     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9027         commentList[currentMove]
9028           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9029         DisplayComment(currentMove - 1, commentList[currentMove]);
9030     }
9031
9032     return retVal;
9033 }
9034
9035 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9036 int
9037 ReloadGame(offset)
9038      int offset;
9039 {
9040     int gameNumber = lastLoadGameNumber + offset;
9041     if (lastLoadGameFP == NULL) {
9042         DisplayError(_("No game has been loaded yet"), 0);
9043         return FALSE;
9044     }
9045     if (gameNumber <= 0) {
9046         DisplayError(_("Can't back up any further"), 0);
9047         return FALSE;
9048     }
9049     if (cmailMsgLoaded) {
9050         return CmailLoadGame(lastLoadGameFP, gameNumber,
9051                              lastLoadGameTitle, lastLoadGameUseList);
9052     } else {
9053         return LoadGame(lastLoadGameFP, gameNumber,
9054                         lastLoadGameTitle, lastLoadGameUseList);
9055     }
9056 }
9057
9058
9059
9060 /* Load the nth game from open file f */
9061 int
9062 LoadGame(f, gameNumber, title, useList)
9063      FILE *f;
9064      int gameNumber;
9065      char *title;
9066      int useList;
9067 {
9068     ChessMove cm;
9069     char buf[MSG_SIZ];
9070     int gn = gameNumber;
9071     ListGame *lg = NULL;
9072     int numPGNTags = 0;
9073     int err;
9074     GameMode oldGameMode;
9075     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9076
9077     if (appData.debugMode) 
9078         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9079
9080     if (gameMode == Training )
9081         SetTrainingModeOff();
9082
9083     oldGameMode = gameMode;
9084     if (gameMode != BeginningOfGame) {
9085       Reset(FALSE, TRUE);
9086     }
9087
9088     gameFileFP = f;
9089     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9090         fclose(lastLoadGameFP);
9091     }
9092
9093     if (useList) {
9094         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9095         
9096         if (lg) {
9097             fseek(f, lg->offset, 0);
9098             GameListHighlight(gameNumber);
9099             gn = 1;
9100         }
9101         else {
9102             DisplayError(_("Game number out of range"), 0);
9103             return FALSE;
9104         }
9105     } else {
9106         GameListDestroy();
9107         if (fseek(f, 0, 0) == -1) {
9108             if (f == lastLoadGameFP ?
9109                 gameNumber == lastLoadGameNumber + 1 :
9110                 gameNumber == 1) {
9111                 gn = 1;
9112             } else {
9113                 DisplayError(_("Can't seek on game file"), 0);
9114                 return FALSE;
9115             }
9116         }
9117     }
9118     lastLoadGameFP = f;
9119     lastLoadGameNumber = gameNumber;
9120     strcpy(lastLoadGameTitle, title);
9121     lastLoadGameUseList = useList;
9122
9123     yynewfile(f);
9124
9125     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9126       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9127                 lg->gameInfo.black);
9128             DisplayTitle(buf);
9129     } else if (*title != NULLCHAR) {
9130         if (gameNumber > 1) {
9131             sprintf(buf, "%s %d", title, gameNumber);
9132             DisplayTitle(buf);
9133         } else {
9134             DisplayTitle(title);
9135         }
9136     }
9137
9138     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9139         gameMode = PlayFromGameFile;
9140         ModeHighlight();
9141     }
9142
9143     currentMove = forwardMostMove = backwardMostMove = 0;
9144     CopyBoard(boards[0], initialPosition);
9145     StopClocks();
9146
9147     /*
9148      * Skip the first gn-1 games in the file.
9149      * Also skip over anything that precedes an identifiable 
9150      * start of game marker, to avoid being confused by 
9151      * garbage at the start of the file.  Currently 
9152      * recognized start of game markers are the move number "1",
9153      * the pattern "gnuchess .* game", the pattern
9154      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9155      * A game that starts with one of the latter two patterns
9156      * will also have a move number 1, possibly
9157      * following a position diagram.
9158      * 5-4-02: Let's try being more lenient and allowing a game to
9159      * start with an unnumbered move.  Does that break anything?
9160      */
9161     cm = lastLoadGameStart = (ChessMove) 0;
9162     while (gn > 0) {
9163         yyboardindex = forwardMostMove;
9164         cm = (ChessMove) yylex();
9165         switch (cm) {
9166           case (ChessMove) 0:
9167             if (cmailMsgLoaded) {
9168                 nCmailGames = CMAIL_MAX_GAMES - gn;
9169             } else {
9170                 Reset(TRUE, TRUE);
9171                 DisplayError(_("Game not found in file"), 0);
9172             }
9173             return FALSE;
9174
9175           case GNUChessGame:
9176           case XBoardGame:
9177             gn--;
9178             lastLoadGameStart = cm;
9179             break;
9180             
9181           case MoveNumberOne:
9182             switch (lastLoadGameStart) {
9183               case GNUChessGame:
9184               case XBoardGame:
9185               case PGNTag:
9186                 break;
9187               case MoveNumberOne:
9188               case (ChessMove) 0:
9189                 gn--;           /* count this game */
9190                 lastLoadGameStart = cm;
9191                 break;
9192               default:
9193                 /* impossible */
9194                 break;
9195             }
9196             break;
9197
9198           case PGNTag:
9199             switch (lastLoadGameStart) {
9200               case GNUChessGame:
9201               case PGNTag:
9202               case MoveNumberOne:
9203               case (ChessMove) 0:
9204                 gn--;           /* count this game */
9205                 lastLoadGameStart = cm;
9206                 break;
9207               case XBoardGame:
9208                 lastLoadGameStart = cm; /* game counted already */
9209                 break;
9210               default:
9211                 /* impossible */
9212                 break;
9213             }
9214             if (gn > 0) {
9215                 do {
9216                     yyboardindex = forwardMostMove;
9217                     cm = (ChessMove) yylex();
9218                 } while (cm == PGNTag || cm == Comment);
9219             }
9220             break;
9221
9222           case WhiteWins:
9223           case BlackWins:
9224           case GameIsDrawn:
9225             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9226                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9227                     != CMAIL_OLD_RESULT) {
9228                     nCmailResults ++ ;
9229                     cmailResult[  CMAIL_MAX_GAMES
9230                                 - gn - 1] = CMAIL_OLD_RESULT;
9231                 }
9232             }
9233             break;
9234
9235           case NormalMove:
9236             /* Only a NormalMove can be at the start of a game
9237              * without a position diagram. */
9238             if (lastLoadGameStart == (ChessMove) 0) {
9239               gn--;
9240               lastLoadGameStart = MoveNumberOne;
9241             }
9242             break;
9243
9244           default:
9245             break;
9246         }
9247     }
9248     
9249     if (appData.debugMode)
9250       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9251
9252     if (cm == XBoardGame) {
9253         /* Skip any header junk before position diagram and/or move 1 */
9254         for (;;) {
9255             yyboardindex = forwardMostMove;
9256             cm = (ChessMove) yylex();
9257
9258             if (cm == (ChessMove) 0 ||
9259                 cm == GNUChessGame || cm == XBoardGame) {
9260                 /* Empty game; pretend end-of-file and handle later */
9261                 cm = (ChessMove) 0;
9262                 break;
9263             }
9264
9265             if (cm == MoveNumberOne || cm == PositionDiagram ||
9266                 cm == PGNTag || cm == Comment)
9267               break;
9268         }
9269     } else if (cm == GNUChessGame) {
9270         if (gameInfo.event != NULL) {
9271             free(gameInfo.event);
9272         }
9273         gameInfo.event = StrSave(yy_text);
9274     }   
9275
9276     startedFromSetupPosition = FALSE;
9277     while (cm == PGNTag) {
9278         if (appData.debugMode) 
9279           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9280         err = ParsePGNTag(yy_text, &gameInfo);
9281         if (!err) numPGNTags++;
9282
9283         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9284         if(gameInfo.variant != oldVariant) {
9285             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9286             InitPosition(TRUE);
9287             oldVariant = gameInfo.variant;
9288             if (appData.debugMode) 
9289               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9290         }
9291
9292
9293         if (gameInfo.fen != NULL) {
9294           Board initial_position;
9295           startedFromSetupPosition = TRUE;
9296           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9297             Reset(TRUE, TRUE);
9298             DisplayError(_("Bad FEN position in file"), 0);
9299             return FALSE;
9300           }
9301           CopyBoard(boards[0], initial_position);
9302           if (blackPlaysFirst) {
9303             currentMove = forwardMostMove = backwardMostMove = 1;
9304             CopyBoard(boards[1], initial_position);
9305             strcpy(moveList[0], "");
9306             strcpy(parseList[0], "");
9307             timeRemaining[0][1] = whiteTimeRemaining;
9308             timeRemaining[1][1] = blackTimeRemaining;
9309             if (commentList[0] != NULL) {
9310               commentList[1] = commentList[0];
9311               commentList[0] = NULL;
9312             }
9313           } else {
9314             currentMove = forwardMostMove = backwardMostMove = 0;
9315           }
9316           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9317           {   int i;
9318               initialRulePlies = FENrulePlies;
9319               epStatus[forwardMostMove] = FENepStatus;
9320               for( i=0; i< nrCastlingRights; i++ )
9321                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9322           }
9323           yyboardindex = forwardMostMove;
9324           free(gameInfo.fen);
9325           gameInfo.fen = NULL;
9326         }
9327
9328         yyboardindex = forwardMostMove;
9329         cm = (ChessMove) yylex();
9330
9331         /* Handle comments interspersed among the tags */
9332         while (cm == Comment) {
9333             char *p;
9334             if (appData.debugMode) 
9335               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9336             p = yy_text;
9337             if (*p == '{' || *p == '[' || *p == '(') {
9338                 p[strlen(p) - 1] = NULLCHAR;
9339                 p++;
9340             }
9341             while (*p == '\n') p++;
9342             AppendComment(currentMove, p);
9343             yyboardindex = forwardMostMove;
9344             cm = (ChessMove) yylex();
9345         }
9346     }
9347
9348     /* don't rely on existence of Event tag since if game was
9349      * pasted from clipboard the Event tag may not exist
9350      */
9351     if (numPGNTags > 0){
9352         char *tags;
9353         if (gameInfo.variant == VariantNormal) {
9354           gameInfo.variant = StringToVariant(gameInfo.event);
9355         }
9356         if (!matchMode) {
9357           if( appData.autoDisplayTags ) {
9358             tags = PGNTags(&gameInfo);
9359             TagsPopUp(tags, CmailMsg());
9360             free(tags);
9361           }
9362         }
9363     } else {
9364         /* Make something up, but don't display it now */
9365         SetGameInfo();
9366         TagsPopDown();
9367     }
9368
9369     if (cm == PositionDiagram) {
9370         int i, j;
9371         char *p;
9372         Board initial_position;
9373
9374         if (appData.debugMode)
9375           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9376
9377         if (!startedFromSetupPosition) {
9378             p = yy_text;
9379             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9380               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9381                 switch (*p) {
9382                   case '[':
9383                   case '-':
9384                   case ' ':
9385                   case '\t':
9386                   case '\n':
9387                   case '\r':
9388                     break;
9389                   default:
9390                     initial_position[i][j++] = CharToPiece(*p);
9391                     break;
9392                 }
9393             while (*p == ' ' || *p == '\t' ||
9394                    *p == '\n' || *p == '\r') p++;
9395         
9396             if (strncmp(p, "black", strlen("black"))==0)
9397               blackPlaysFirst = TRUE;
9398             else
9399               blackPlaysFirst = FALSE;
9400             startedFromSetupPosition = TRUE;
9401         
9402             CopyBoard(boards[0], initial_position);
9403             if (blackPlaysFirst) {
9404                 currentMove = forwardMostMove = backwardMostMove = 1;
9405                 CopyBoard(boards[1], initial_position);
9406                 strcpy(moveList[0], "");
9407                 strcpy(parseList[0], "");
9408                 timeRemaining[0][1] = whiteTimeRemaining;
9409                 timeRemaining[1][1] = blackTimeRemaining;
9410                 if (commentList[0] != NULL) {
9411                     commentList[1] = commentList[0];
9412                     commentList[0] = NULL;
9413                 }
9414             } else {
9415                 currentMove = forwardMostMove = backwardMostMove = 0;
9416             }
9417         }
9418         yyboardindex = forwardMostMove;
9419         cm = (ChessMove) yylex();
9420     }
9421
9422     if (first.pr == NoProc) {
9423         StartChessProgram(&first);
9424     }
9425     InitChessProgram(&first, FALSE);
9426     SendToProgram("force\n", &first);
9427     if (startedFromSetupPosition) {
9428         SendBoard(&first, forwardMostMove);
9429     if (appData.debugMode) {
9430         fprintf(debugFP, "Load Game\n");
9431     }
9432         DisplayBothClocks();
9433     }      
9434
9435     /* [HGM] server: flag to write setup moves in broadcast file as one */
9436     loadFlag = appData.suppressLoadMoves;
9437
9438     while (cm == Comment) {
9439         char *p;
9440         if (appData.debugMode) 
9441           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9442         p = yy_text;
9443         if (*p == '{' || *p == '[' || *p == '(') {
9444             p[strlen(p) - 1] = NULLCHAR;
9445             p++;
9446         }
9447         while (*p == '\n') p++;
9448         AppendComment(currentMove, p);
9449         yyboardindex = forwardMostMove;
9450         cm = (ChessMove) yylex();
9451     }
9452
9453     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9454         cm == WhiteWins || cm == BlackWins ||
9455         cm == GameIsDrawn || cm == GameUnfinished) {
9456         DisplayMessage("", _("No moves in game"));
9457         if (cmailMsgLoaded) {
9458             if (appData.debugMode)
9459               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9460             ClearHighlights();
9461             flipView = FALSE;
9462         }
9463         DrawPosition(FALSE, boards[currentMove]);
9464         DisplayBothClocks();
9465         gameMode = EditGame;
9466         ModeHighlight();
9467         gameFileFP = NULL;
9468         cmailOldMove = 0;
9469         return TRUE;
9470     }
9471
9472     // [HGM] PV info: routine tests if comment empty
9473     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9474         DisplayComment(currentMove - 1, commentList[currentMove]);
9475     }
9476     if (!matchMode && appData.timeDelay != 0) 
9477       DrawPosition(FALSE, boards[currentMove]);
9478
9479     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9480       programStats.ok_to_send = 1;
9481     }
9482
9483     /* if the first token after the PGN tags is a move
9484      * and not move number 1, retrieve it from the parser 
9485      */
9486     if (cm != MoveNumberOne)
9487         LoadGameOneMove(cm);
9488
9489     /* load the remaining moves from the file */
9490     while (LoadGameOneMove((ChessMove)0)) {
9491       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9492       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9493     }
9494
9495     /* rewind to the start of the game */
9496     currentMove = backwardMostMove;
9497
9498     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9499
9500     if (oldGameMode == AnalyzeFile ||
9501         oldGameMode == AnalyzeMode) {
9502       AnalyzeFileEvent();
9503     }
9504
9505     if (matchMode || appData.timeDelay == 0) {
9506       ToEndEvent();
9507       gameMode = EditGame;
9508       ModeHighlight();
9509     } else if (appData.timeDelay > 0) {
9510       AutoPlayGameLoop();
9511     }
9512
9513     if (appData.debugMode) 
9514         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9515
9516     loadFlag = 0; /* [HGM] true game starts */
9517     return TRUE;
9518 }
9519
9520 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9521 int
9522 ReloadPosition(offset)
9523      int offset;
9524 {
9525     int positionNumber = lastLoadPositionNumber + offset;
9526     if (lastLoadPositionFP == NULL) {
9527         DisplayError(_("No position has been loaded yet"), 0);
9528         return FALSE;
9529     }
9530     if (positionNumber <= 0) {
9531         DisplayError(_("Can't back up any further"), 0);
9532         return FALSE;
9533     }
9534     return LoadPosition(lastLoadPositionFP, positionNumber,
9535                         lastLoadPositionTitle);
9536 }
9537
9538 /* Load the nth position from the given file */
9539 int
9540 LoadPositionFromFile(filename, n, title)
9541      char *filename;
9542      int n;
9543      char *title;
9544 {
9545     FILE *f;
9546     char buf[MSG_SIZ];
9547
9548     if (strcmp(filename, "-") == 0) {
9549         return LoadPosition(stdin, n, "stdin");
9550     } else {
9551         f = fopen(filename, "rb");
9552         if (f == NULL) {
9553             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9554             DisplayError(buf, errno);
9555             return FALSE;
9556         } else {
9557             return LoadPosition(f, n, title);
9558         }
9559     }
9560 }
9561
9562 /* Load the nth position from the given open file, and close it */
9563 int
9564 LoadPosition(f, positionNumber, title)
9565      FILE *f;
9566      int positionNumber;
9567      char *title;
9568 {
9569     char *p, line[MSG_SIZ];
9570     Board initial_position;
9571     int i, j, fenMode, pn;
9572     
9573     if (gameMode == Training )
9574         SetTrainingModeOff();
9575
9576     if (gameMode != BeginningOfGame) {
9577         Reset(FALSE, TRUE);
9578     }
9579     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9580         fclose(lastLoadPositionFP);
9581     }
9582     if (positionNumber == 0) positionNumber = 1;
9583     lastLoadPositionFP = f;
9584     lastLoadPositionNumber = positionNumber;
9585     strcpy(lastLoadPositionTitle, title);
9586     if (first.pr == NoProc) {
9587       StartChessProgram(&first);
9588       InitChessProgram(&first, FALSE);
9589     }    
9590     pn = positionNumber;
9591     if (positionNumber < 0) {
9592         /* Negative position number means to seek to that byte offset */
9593         if (fseek(f, -positionNumber, 0) == -1) {
9594             DisplayError(_("Can't seek on position file"), 0);
9595             return FALSE;
9596         };
9597         pn = 1;
9598     } else {
9599         if (fseek(f, 0, 0) == -1) {
9600             if (f == lastLoadPositionFP ?
9601                 positionNumber == lastLoadPositionNumber + 1 :
9602                 positionNumber == 1) {
9603                 pn = 1;
9604             } else {
9605                 DisplayError(_("Can't seek on position file"), 0);
9606                 return FALSE;
9607             }
9608         }
9609     }
9610     /* See if this file is FEN or old-style xboard */
9611     if (fgets(line, MSG_SIZ, f) == NULL) {
9612         DisplayError(_("Position not found in file"), 0);
9613         return FALSE;
9614     }
9615     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9616     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9617
9618     if (pn >= 2) {
9619         if (fenMode || line[0] == '#') pn--;
9620         while (pn > 0) {
9621             /* skip positions before number pn */
9622             if (fgets(line, MSG_SIZ, f) == NULL) {
9623                 Reset(TRUE, TRUE);
9624                 DisplayError(_("Position not found in file"), 0);
9625                 return FALSE;
9626             }
9627             if (fenMode || line[0] == '#') pn--;
9628         }
9629     }
9630
9631     if (fenMode) {
9632         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9633             DisplayError(_("Bad FEN position in file"), 0);
9634             return FALSE;
9635         }
9636     } else {
9637         (void) fgets(line, MSG_SIZ, f);
9638         (void) fgets(line, MSG_SIZ, f);
9639     
9640         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9641             (void) fgets(line, MSG_SIZ, f);
9642             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9643                 if (*p == ' ')
9644                   continue;
9645                 initial_position[i][j++] = CharToPiece(*p);
9646             }
9647         }
9648     
9649         blackPlaysFirst = FALSE;
9650         if (!feof(f)) {
9651             (void) fgets(line, MSG_SIZ, f);
9652             if (strncmp(line, "black", strlen("black"))==0)
9653               blackPlaysFirst = TRUE;
9654         }
9655     }
9656     startedFromSetupPosition = TRUE;
9657     
9658     SendToProgram("force\n", &first);
9659     CopyBoard(boards[0], initial_position);
9660     if (blackPlaysFirst) {
9661         currentMove = forwardMostMove = backwardMostMove = 1;
9662         strcpy(moveList[0], "");
9663         strcpy(parseList[0], "");
9664         CopyBoard(boards[1], initial_position);
9665         DisplayMessage("", _("Black to play"));
9666     } else {
9667         currentMove = forwardMostMove = backwardMostMove = 0;
9668         DisplayMessage("", _("White to play"));
9669     }
9670           /* [HGM] copy FEN attributes as well */
9671           {   int i;
9672               initialRulePlies = FENrulePlies;
9673               epStatus[forwardMostMove] = FENepStatus;
9674               for( i=0; i< nrCastlingRights; i++ )
9675                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9676           }
9677     SendBoard(&first, forwardMostMove);
9678     if (appData.debugMode) {
9679 int i, j;
9680   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9681   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9682         fprintf(debugFP, "Load Position\n");
9683     }
9684
9685     if (positionNumber > 1) {
9686         sprintf(line, "%s %d", title, positionNumber);
9687         DisplayTitle(line);
9688     } else {
9689         DisplayTitle(title);
9690     }
9691     gameMode = EditGame;
9692     ModeHighlight();
9693     ResetClocks();
9694     timeRemaining[0][1] = whiteTimeRemaining;
9695     timeRemaining[1][1] = blackTimeRemaining;
9696     DrawPosition(FALSE, boards[currentMove]);
9697    
9698     return TRUE;
9699 }
9700
9701
9702 void
9703 CopyPlayerNameIntoFileName(dest, src)
9704      char **dest, *src;
9705 {
9706     while (*src != NULLCHAR && *src != ',') {
9707         if (*src == ' ') {
9708             *(*dest)++ = '_';
9709             src++;
9710         } else {
9711             *(*dest)++ = *src++;
9712         }
9713     }
9714 }
9715
9716 char *DefaultFileName(ext)
9717      char *ext;
9718 {
9719     static char def[MSG_SIZ];
9720     char *p;
9721
9722     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9723         p = def;
9724         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9725         *p++ = '-';
9726         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9727         *p++ = '.';
9728         strcpy(p, ext);
9729     } else {
9730         def[0] = NULLCHAR;
9731     }
9732     return def;
9733 }
9734
9735 /* Save the current game to the given file */
9736 int
9737 SaveGameToFile(filename, append)
9738      char *filename;
9739      int append;
9740 {
9741     FILE *f;
9742     char buf[MSG_SIZ];
9743
9744     if (strcmp(filename, "-") == 0) {
9745         return SaveGame(stdout, 0, NULL);
9746     } else {
9747         f = fopen(filename, append ? "a" : "w");
9748         if (f == NULL) {
9749             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9750             DisplayError(buf, errno);
9751             return FALSE;
9752         } else {
9753             return SaveGame(f, 0, NULL);
9754         }
9755     }
9756 }
9757
9758 char *
9759 SavePart(str)
9760      char *str;
9761 {
9762     static char buf[MSG_SIZ];
9763     char *p;
9764     
9765     p = strchr(str, ' ');
9766     if (p == NULL) return str;
9767     strncpy(buf, str, p - str);
9768     buf[p - str] = NULLCHAR;
9769     return buf;
9770 }
9771
9772 #define PGN_MAX_LINE 75
9773
9774 #define PGN_SIDE_WHITE  0
9775 #define PGN_SIDE_BLACK  1
9776
9777 /* [AS] */
9778 static int FindFirstMoveOutOfBook( int side )
9779 {
9780     int result = -1;
9781
9782     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9783         int index = backwardMostMove;
9784         int has_book_hit = 0;
9785
9786         if( (index % 2) != side ) {
9787             index++;
9788         }
9789
9790         while( index < forwardMostMove ) {
9791             /* Check to see if engine is in book */
9792             int depth = pvInfoList[index].depth;
9793             int score = pvInfoList[index].score;
9794             int in_book = 0;
9795
9796             if( depth <= 2 ) {
9797                 in_book = 1;
9798             }
9799             else if( score == 0 && depth == 63 ) {
9800                 in_book = 1; /* Zappa */
9801             }
9802             else if( score == 2 && depth == 99 ) {
9803                 in_book = 1; /* Abrok */
9804             }
9805
9806             has_book_hit += in_book;
9807
9808             if( ! in_book ) {
9809                 result = index;
9810
9811                 break;
9812             }
9813
9814             index += 2;
9815         }
9816     }
9817
9818     return result;
9819 }
9820
9821 /* [AS] */
9822 void GetOutOfBookInfo( char * buf )
9823 {
9824     int oob[2];
9825     int i;
9826     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9827
9828     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9829     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9830
9831     *buf = '\0';
9832
9833     if( oob[0] >= 0 || oob[1] >= 0 ) {
9834         for( i=0; i<2; i++ ) {
9835             int idx = oob[i];
9836
9837             if( idx >= 0 ) {
9838                 if( i > 0 && oob[0] >= 0 ) {
9839                     strcat( buf, "   " );
9840                 }
9841
9842                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9843                 sprintf( buf+strlen(buf), "%s%.2f", 
9844                     pvInfoList[idx].score >= 0 ? "+" : "",
9845                     pvInfoList[idx].score / 100.0 );
9846             }
9847         }
9848     }
9849 }
9850
9851 /* Save game in PGN style and close the file */
9852 int
9853 SaveGamePGN(f)
9854      FILE *f;
9855 {
9856     int i, offset, linelen, newblock;
9857     time_t tm;
9858 //    char *movetext;
9859     char numtext[32];
9860     int movelen, numlen, blank;
9861     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9862
9863     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9864     
9865     tm = time((time_t *) NULL);
9866     
9867     PrintPGNTags(f, &gameInfo);
9868     
9869     if (backwardMostMove > 0 || startedFromSetupPosition) {
9870         char *fen = PositionToFEN(backwardMostMove, NULL);
9871         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9872         fprintf(f, "\n{--------------\n");
9873         PrintPosition(f, backwardMostMove);
9874         fprintf(f, "--------------}\n");
9875         free(fen);
9876     }
9877     else {
9878         /* [AS] Out of book annotation */
9879         if( appData.saveOutOfBookInfo ) {
9880             char buf[64];
9881
9882             GetOutOfBookInfo( buf );
9883
9884             if( buf[0] != '\0' ) {
9885                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9886             }
9887         }
9888
9889         fprintf(f, "\n");
9890     }
9891
9892     i = backwardMostMove;
9893     linelen = 0;
9894     newblock = TRUE;
9895
9896     while (i < forwardMostMove) {
9897         /* Print comments preceding this move */
9898         if (commentList[i] != NULL) {
9899             if (linelen > 0) fprintf(f, "\n");
9900             fprintf(f, "{\n%s}\n", commentList[i]);
9901             linelen = 0;
9902             newblock = TRUE;
9903         }
9904
9905         /* Format move number */
9906         if ((i % 2) == 0) {
9907             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9908         } else {
9909             if (newblock) {
9910                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9911             } else {
9912                 numtext[0] = NULLCHAR;
9913             }
9914         }
9915         numlen = strlen(numtext);
9916         newblock = FALSE;
9917
9918         /* Print move number */
9919         blank = linelen > 0 && numlen > 0;
9920         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9921             fprintf(f, "\n");
9922             linelen = 0;
9923             blank = 0;
9924         }
9925         if (blank) {
9926             fprintf(f, " ");
9927             linelen++;
9928         }
9929         fprintf(f, "%s", numtext);
9930         linelen += numlen;
9931
9932         /* Get move */
9933         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9934         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9935
9936         /* Print move */
9937         blank = linelen > 0 && movelen > 0;
9938         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9939             fprintf(f, "\n");
9940             linelen = 0;
9941             blank = 0;
9942         }
9943         if (blank) {
9944             fprintf(f, " ");
9945             linelen++;
9946         }
9947         fprintf(f, "%s", move_buffer);
9948         linelen += movelen;
9949
9950         /* [AS] Add PV info if present */
9951         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9952             /* [HGM] add time */
9953             char buf[MSG_SIZ]; int seconds;
9954
9955             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9956
9957             if( seconds <= 0) buf[0] = 0; else
9958             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9959                 seconds = (seconds + 4)/10; // round to full seconds
9960                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9961                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9962             }
9963
9964             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9965                 pvInfoList[i].score >= 0 ? "+" : "",
9966                 pvInfoList[i].score / 100.0,
9967                 pvInfoList[i].depth,
9968                 buf );
9969
9970             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9971
9972             /* Print score/depth */
9973             blank = linelen > 0 && movelen > 0;
9974             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9975                 fprintf(f, "\n");
9976                 linelen = 0;
9977                 blank = 0;
9978             }
9979             if (blank) {
9980                 fprintf(f, " ");
9981                 linelen++;
9982             }
9983             fprintf(f, "%s", move_buffer);
9984             linelen += movelen;
9985         }
9986
9987         i++;
9988     }
9989     
9990     /* Start a new line */
9991     if (linelen > 0) fprintf(f, "\n");
9992
9993     /* Print comments after last move */
9994     if (commentList[i] != NULL) {
9995         fprintf(f, "{\n%s}\n", commentList[i]);
9996     }
9997
9998     /* Print result */
9999     if (gameInfo.resultDetails != NULL &&
10000         gameInfo.resultDetails[0] != NULLCHAR) {
10001         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10002                 PGNResult(gameInfo.result));
10003     } else {
10004         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10005     }
10006
10007     fclose(f);
10008     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10009     return TRUE;
10010 }
10011
10012 /* Save game in old style and close the file */
10013 int
10014 SaveGameOldStyle(f)
10015      FILE *f;
10016 {
10017     int i, offset;
10018     time_t tm;
10019     
10020     tm = time((time_t *) NULL);
10021     
10022     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10023     PrintOpponents(f);
10024     
10025     if (backwardMostMove > 0 || startedFromSetupPosition) {
10026         fprintf(f, "\n[--------------\n");
10027         PrintPosition(f, backwardMostMove);
10028         fprintf(f, "--------------]\n");
10029     } else {
10030         fprintf(f, "\n");
10031     }
10032
10033     i = backwardMostMove;
10034     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10035
10036     while (i < forwardMostMove) {
10037         if (commentList[i] != NULL) {
10038             fprintf(f, "[%s]\n", commentList[i]);
10039         }
10040
10041         if ((i % 2) == 1) {
10042             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10043             i++;
10044         } else {
10045             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10046             i++;
10047             if (commentList[i] != NULL) {
10048                 fprintf(f, "\n");
10049                 continue;
10050             }
10051             if (i >= forwardMostMove) {
10052                 fprintf(f, "\n");
10053                 break;
10054             }
10055             fprintf(f, "%s\n", parseList[i]);
10056             i++;
10057         }
10058     }
10059     
10060     if (commentList[i] != NULL) {
10061         fprintf(f, "[%s]\n", commentList[i]);
10062     }
10063
10064     /* This isn't really the old style, but it's close enough */
10065     if (gameInfo.resultDetails != NULL &&
10066         gameInfo.resultDetails[0] != NULLCHAR) {
10067         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10068                 gameInfo.resultDetails);
10069     } else {
10070         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10071     }
10072
10073     fclose(f);
10074     return TRUE;
10075 }
10076
10077 /* Save the current game to open file f and close the file */
10078 int
10079 SaveGame(f, dummy, dummy2)
10080      FILE *f;
10081      int dummy;
10082      char *dummy2;
10083 {
10084     if (gameMode == EditPosition) EditPositionDone(TRUE);
10085     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10086     if (appData.oldSaveStyle)
10087       return SaveGameOldStyle(f);
10088     else
10089       return SaveGamePGN(f);
10090 }
10091
10092 /* Save the current position to the given file */
10093 int
10094 SavePositionToFile(filename)
10095      char *filename;
10096 {
10097     FILE *f;
10098     char buf[MSG_SIZ];
10099
10100     if (strcmp(filename, "-") == 0) {
10101         return SavePosition(stdout, 0, NULL);
10102     } else {
10103         f = fopen(filename, "a");
10104         if (f == NULL) {
10105             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10106             DisplayError(buf, errno);
10107             return FALSE;
10108         } else {
10109             SavePosition(f, 0, NULL);
10110             return TRUE;
10111         }
10112     }
10113 }
10114
10115 /* Save the current position to the given open file and close the file */
10116 int
10117 SavePosition(f, dummy, dummy2)
10118      FILE *f;
10119      int dummy;
10120      char *dummy2;
10121 {
10122     time_t tm;
10123     char *fen;
10124
10125     if (gameMode == EditPosition) EditPositionDone(TRUE);
10126     if (appData.oldSaveStyle) {
10127         tm = time((time_t *) NULL);
10128     
10129         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10130         PrintOpponents(f);
10131         fprintf(f, "[--------------\n");
10132         PrintPosition(f, currentMove);
10133         fprintf(f, "--------------]\n");
10134     } else {
10135         fen = PositionToFEN(currentMove, NULL);
10136         fprintf(f, "%s\n", fen);
10137         free(fen);
10138     }
10139     fclose(f);
10140     return TRUE;
10141 }
10142
10143 void
10144 ReloadCmailMsgEvent(unregister)
10145      int unregister;
10146 {
10147 #if !WIN32
10148     static char *inFilename = NULL;
10149     static char *outFilename;
10150     int i;
10151     struct stat inbuf, outbuf;
10152     int status;
10153     
10154     /* Any registered moves are unregistered if unregister is set, */
10155     /* i.e. invoked by the signal handler */
10156     if (unregister) {
10157         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10158             cmailMoveRegistered[i] = FALSE;
10159             if (cmailCommentList[i] != NULL) {
10160                 free(cmailCommentList[i]);
10161                 cmailCommentList[i] = NULL;
10162             }
10163         }
10164         nCmailMovesRegistered = 0;
10165     }
10166
10167     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10168         cmailResult[i] = CMAIL_NOT_RESULT;
10169     }
10170     nCmailResults = 0;
10171
10172     if (inFilename == NULL) {
10173         /* Because the filenames are static they only get malloced once  */
10174         /* and they never get freed                                      */
10175         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10176         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10177
10178         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10179         sprintf(outFilename, "%s.out", appData.cmailGameName);
10180     }
10181     
10182     status = stat(outFilename, &outbuf);
10183     if (status < 0) {
10184         cmailMailedMove = FALSE;
10185     } else {
10186         status = stat(inFilename, &inbuf);
10187         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10188     }
10189     
10190     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10191        counts the games, notes how each one terminated, etc.
10192        
10193        It would be nice to remove this kludge and instead gather all
10194        the information while building the game list.  (And to keep it
10195        in the game list nodes instead of having a bunch of fixed-size
10196        parallel arrays.)  Note this will require getting each game's
10197        termination from the PGN tags, as the game list builder does
10198        not process the game moves.  --mann
10199        */
10200     cmailMsgLoaded = TRUE;
10201     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10202     
10203     /* Load first game in the file or popup game menu */
10204     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10205
10206 #endif /* !WIN32 */
10207     return;
10208 }
10209
10210 int
10211 RegisterMove()
10212 {
10213     FILE *f;
10214     char string[MSG_SIZ];
10215
10216     if (   cmailMailedMove
10217         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10218         return TRUE;            /* Allow free viewing  */
10219     }
10220
10221     /* Unregister move to ensure that we don't leave RegisterMove        */
10222     /* with the move registered when the conditions for registering no   */
10223     /* longer hold                                                       */
10224     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10225         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10226         nCmailMovesRegistered --;
10227
10228         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10229           {
10230               free(cmailCommentList[lastLoadGameNumber - 1]);
10231               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10232           }
10233     }
10234
10235     if (cmailOldMove == -1) {
10236         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10237         return FALSE;
10238     }
10239
10240     if (currentMove > cmailOldMove + 1) {
10241         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10242         return FALSE;
10243     }
10244
10245     if (currentMove < cmailOldMove) {
10246         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10247         return FALSE;
10248     }
10249
10250     if (forwardMostMove > currentMove) {
10251         /* Silently truncate extra moves */
10252         TruncateGame();
10253     }
10254
10255     if (   (currentMove == cmailOldMove + 1)
10256         || (   (currentMove == cmailOldMove)
10257             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10258                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10259         if (gameInfo.result != GameUnfinished) {
10260             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10261         }
10262
10263         if (commentList[currentMove] != NULL) {
10264             cmailCommentList[lastLoadGameNumber - 1]
10265               = StrSave(commentList[currentMove]);
10266         }
10267         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10268
10269         if (appData.debugMode)
10270           fprintf(debugFP, "Saving %s for game %d\n",
10271                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10272
10273         sprintf(string,
10274                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10275         
10276         f = fopen(string, "w");
10277         if (appData.oldSaveStyle) {
10278             SaveGameOldStyle(f); /* also closes the file */
10279             
10280             sprintf(string, "%s.pos.out", appData.cmailGameName);
10281             f = fopen(string, "w");
10282             SavePosition(f, 0, NULL); /* also closes the file */
10283         } else {
10284             fprintf(f, "{--------------\n");
10285             PrintPosition(f, currentMove);
10286             fprintf(f, "--------------}\n\n");
10287             
10288             SaveGame(f, 0, NULL); /* also closes the file*/
10289         }
10290         
10291         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10292         nCmailMovesRegistered ++;
10293     } else if (nCmailGames == 1) {
10294         DisplayError(_("You have not made a move yet"), 0);
10295         return FALSE;
10296     }
10297
10298     return TRUE;
10299 }
10300
10301 void
10302 MailMoveEvent()
10303 {
10304 #if !WIN32
10305     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10306     FILE *commandOutput;
10307     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10308     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10309     int nBuffers;
10310     int i;
10311     int archived;
10312     char *arcDir;
10313
10314     if (! cmailMsgLoaded) {
10315         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10316         return;
10317     }
10318
10319     if (nCmailGames == nCmailResults) {
10320         DisplayError(_("No unfinished games"), 0);
10321         return;
10322     }
10323
10324 #if CMAIL_PROHIBIT_REMAIL
10325     if (cmailMailedMove) {
10326         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);
10327         DisplayError(msg, 0);
10328         return;
10329     }
10330 #endif
10331
10332     if (! (cmailMailedMove || RegisterMove())) return;
10333     
10334     if (   cmailMailedMove
10335         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10336         sprintf(string, partCommandString,
10337                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10338         commandOutput = popen(string, "r");
10339
10340         if (commandOutput == NULL) {
10341             DisplayError(_("Failed to invoke cmail"), 0);
10342         } else {
10343             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10344                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10345             }
10346             if (nBuffers > 1) {
10347                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10348                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10349                 nBytes = MSG_SIZ - 1;
10350             } else {
10351                 (void) memcpy(msg, buffer, nBytes);
10352             }
10353             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10354
10355             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10356                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10357
10358                 archived = TRUE;
10359                 for (i = 0; i < nCmailGames; i ++) {
10360                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10361                         archived = FALSE;
10362                     }
10363                 }
10364                 if (   archived
10365                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10366                         != NULL)) {
10367                     sprintf(buffer, "%s/%s.%s.archive",
10368                             arcDir,
10369                             appData.cmailGameName,
10370                             gameInfo.date);
10371                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10372                     cmailMsgLoaded = FALSE;
10373                 }
10374             }
10375
10376             DisplayInformation(msg);
10377             pclose(commandOutput);
10378         }
10379     } else {
10380         if ((*cmailMsg) != '\0') {
10381             DisplayInformation(cmailMsg);
10382         }
10383     }
10384
10385     return;
10386 #endif /* !WIN32 */
10387 }
10388
10389 char *
10390 CmailMsg()
10391 {
10392 #if WIN32
10393     return NULL;
10394 #else
10395     int  prependComma = 0;
10396     char number[5];
10397     char string[MSG_SIZ];       /* Space for game-list */
10398     int  i;
10399     
10400     if (!cmailMsgLoaded) return "";
10401
10402     if (cmailMailedMove) {
10403         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10404     } else {
10405         /* Create a list of games left */
10406         sprintf(string, "[");
10407         for (i = 0; i < nCmailGames; i ++) {
10408             if (! (   cmailMoveRegistered[i]
10409                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10410                 if (prependComma) {
10411                     sprintf(number, ",%d", i + 1);
10412                 } else {
10413                     sprintf(number, "%d", i + 1);
10414                     prependComma = 1;
10415                 }
10416                 
10417                 strcat(string, number);
10418             }
10419         }
10420         strcat(string, "]");
10421
10422         if (nCmailMovesRegistered + nCmailResults == 0) {
10423             switch (nCmailGames) {
10424               case 1:
10425                 sprintf(cmailMsg,
10426                         _("Still need to make move for game\n"));
10427                 break;
10428                 
10429               case 2:
10430                 sprintf(cmailMsg,
10431                         _("Still need to make moves for both games\n"));
10432                 break;
10433                 
10434               default:
10435                 sprintf(cmailMsg,
10436                         _("Still need to make moves for all %d games\n"),
10437                         nCmailGames);
10438                 break;
10439             }
10440         } else {
10441             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10442               case 1:
10443                 sprintf(cmailMsg,
10444                         _("Still need to make a move for game %s\n"),
10445                         string);
10446                 break;
10447                 
10448               case 0:
10449                 if (nCmailResults == nCmailGames) {
10450                     sprintf(cmailMsg, _("No unfinished games\n"));
10451                 } else {
10452                     sprintf(cmailMsg, _("Ready to send mail\n"));
10453                 }
10454                 break;
10455                 
10456               default:
10457                 sprintf(cmailMsg,
10458                         _("Still need to make moves for games %s\n"),
10459                         string);
10460             }
10461         }
10462     }
10463     return cmailMsg;
10464 #endif /* WIN32 */
10465 }
10466
10467 void
10468 ResetGameEvent()
10469 {
10470     if (gameMode == Training)
10471       SetTrainingModeOff();
10472
10473     Reset(TRUE, TRUE);
10474     cmailMsgLoaded = FALSE;
10475     if (appData.icsActive) {
10476       SendToICS(ics_prefix);
10477       SendToICS("refresh\n");
10478     }
10479 }
10480
10481 void
10482 ExitEvent(status)
10483      int status;
10484 {
10485     exiting++;
10486     if (exiting > 2) {
10487       /* Give up on clean exit */
10488       exit(status);
10489     }
10490     if (exiting > 1) {
10491       /* Keep trying for clean exit */
10492       return;
10493     }
10494
10495     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10496
10497     if (telnetISR != NULL) {
10498       RemoveInputSource(telnetISR);
10499     }
10500     if (icsPR != NoProc) {
10501       DestroyChildProcess(icsPR, TRUE);
10502     }
10503
10504     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10505     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10506
10507     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10508     /* make sure this other one finishes before killing it!                  */
10509     if(endingGame) { int count = 0;
10510         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10511         while(endingGame && count++ < 10) DoSleep(1);
10512         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10513     }
10514
10515     /* Kill off chess programs */
10516     if (first.pr != NoProc) {
10517         ExitAnalyzeMode();
10518         
10519         DoSleep( appData.delayBeforeQuit );
10520         SendToProgram("quit\n", &first);
10521         DoSleep( appData.delayAfterQuit );
10522         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10523     }
10524     if (second.pr != NoProc) {
10525         DoSleep( appData.delayBeforeQuit );
10526         SendToProgram("quit\n", &second);
10527         DoSleep( appData.delayAfterQuit );
10528         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10529     }
10530     if (first.isr != NULL) {
10531         RemoveInputSource(first.isr);
10532     }
10533     if (second.isr != NULL) {
10534         RemoveInputSource(second.isr);
10535     }
10536
10537     ShutDownFrontEnd();
10538     exit(status);
10539 }
10540
10541 void
10542 PauseEvent()
10543 {
10544     if (appData.debugMode)
10545         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10546     if (pausing) {
10547         pausing = FALSE;
10548         ModeHighlight();
10549         if (gameMode == MachinePlaysWhite ||
10550             gameMode == MachinePlaysBlack) {
10551             StartClocks();
10552         } else {
10553             DisplayBothClocks();
10554         }
10555         if (gameMode == PlayFromGameFile) {
10556             if (appData.timeDelay >= 0) 
10557                 AutoPlayGameLoop();
10558         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10559             Reset(FALSE, TRUE);
10560             SendToICS(ics_prefix);
10561             SendToICS("refresh\n");
10562         } else if (currentMove < forwardMostMove) {
10563             ForwardInner(forwardMostMove);
10564         }
10565         pauseExamInvalid = FALSE;
10566     } else {
10567         switch (gameMode) {
10568           default:
10569             return;
10570           case IcsExamining:
10571             pauseExamForwardMostMove = forwardMostMove;
10572             pauseExamInvalid = FALSE;
10573             /* fall through */
10574           case IcsObserving:
10575           case IcsPlayingWhite:
10576           case IcsPlayingBlack:
10577             pausing = TRUE;
10578             ModeHighlight();
10579             return;
10580           case PlayFromGameFile:
10581             (void) StopLoadGameTimer();
10582             pausing = TRUE;
10583             ModeHighlight();
10584             break;
10585           case BeginningOfGame:
10586             if (appData.icsActive) return;
10587             /* else fall through */
10588           case MachinePlaysWhite:
10589           case MachinePlaysBlack:
10590           case TwoMachinesPlay:
10591             if (forwardMostMove == 0)
10592               return;           /* don't pause if no one has moved */
10593             if ((gameMode == MachinePlaysWhite &&
10594                  !WhiteOnMove(forwardMostMove)) ||
10595                 (gameMode == MachinePlaysBlack &&
10596                  WhiteOnMove(forwardMostMove))) {
10597                 StopClocks();
10598             }
10599             pausing = TRUE;
10600             ModeHighlight();
10601             break;
10602         }
10603     }
10604 }
10605
10606 void
10607 EditCommentEvent()
10608 {
10609     char title[MSG_SIZ];
10610
10611     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10612         strcpy(title, _("Edit comment"));
10613     } else {
10614         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10615                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10616                 parseList[currentMove - 1]);
10617     }
10618
10619     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10620 }
10621
10622
10623 void
10624 EditTagsEvent()
10625 {
10626     char *tags = PGNTags(&gameInfo);
10627     EditTagsPopUp(tags);
10628     free(tags);
10629 }
10630
10631 void
10632 AnalyzeModeEvent()
10633 {
10634     if (appData.noChessProgram || gameMode == AnalyzeMode)
10635       return;
10636
10637     if (gameMode != AnalyzeFile) {
10638         if (!appData.icsEngineAnalyze) {
10639                EditGameEvent();
10640                if (gameMode != EditGame) return;
10641         }
10642         ResurrectChessProgram();
10643         SendToProgram("analyze\n", &first);
10644         first.analyzing = TRUE;
10645         /*first.maybeThinking = TRUE;*/
10646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10647         EngineOutputPopUp();
10648     }
10649     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10650     pausing = FALSE;
10651     ModeHighlight();
10652     SetGameInfo();
10653
10654     StartAnalysisClock();
10655     GetTimeMark(&lastNodeCountTime);
10656     lastNodeCount = 0;
10657 }
10658
10659 void
10660 AnalyzeFileEvent()
10661 {
10662     if (appData.noChessProgram || gameMode == AnalyzeFile)
10663       return;
10664
10665     if (gameMode != AnalyzeMode) {
10666         EditGameEvent();
10667         if (gameMode != EditGame) return;
10668         ResurrectChessProgram();
10669         SendToProgram("analyze\n", &first);
10670         first.analyzing = TRUE;
10671         /*first.maybeThinking = TRUE;*/
10672         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10673         EngineOutputPopUp();
10674     }
10675     gameMode = AnalyzeFile;
10676     pausing = FALSE;
10677     ModeHighlight();
10678     SetGameInfo();
10679
10680     StartAnalysisClock();
10681     GetTimeMark(&lastNodeCountTime);
10682     lastNodeCount = 0;
10683 }
10684
10685 void
10686 MachineWhiteEvent()
10687 {
10688     char buf[MSG_SIZ];
10689     char *bookHit = NULL;
10690
10691     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10692       return;
10693
10694
10695     if (gameMode == PlayFromGameFile || 
10696         gameMode == TwoMachinesPlay  || 
10697         gameMode == Training         || 
10698         gameMode == AnalyzeMode      || 
10699         gameMode == EndOfGame)
10700         EditGameEvent();
10701
10702     if (gameMode == EditPosition) 
10703         EditPositionDone(TRUE);
10704
10705     if (!WhiteOnMove(currentMove)) {
10706         DisplayError(_("It is not White's turn"), 0);
10707         return;
10708     }
10709   
10710     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10711       ExitAnalyzeMode();
10712
10713     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10714         gameMode == AnalyzeFile)
10715         TruncateGame();
10716
10717     ResurrectChessProgram();    /* in case it isn't running */
10718     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10719         gameMode = MachinePlaysWhite;
10720         ResetClocks();
10721     } else
10722     gameMode = MachinePlaysWhite;
10723     pausing = FALSE;
10724     ModeHighlight();
10725     SetGameInfo();
10726     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10727     DisplayTitle(buf);
10728     if (first.sendName) {
10729       sprintf(buf, "name %s\n", gameInfo.black);
10730       SendToProgram(buf, &first);
10731     }
10732     if (first.sendTime) {
10733       if (first.useColors) {
10734         SendToProgram("black\n", &first); /*gnu kludge*/
10735       }
10736       SendTimeRemaining(&first, TRUE);
10737     }
10738     if (first.useColors) {
10739       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10740     }
10741     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10742     SetMachineThinkingEnables();
10743     first.maybeThinking = TRUE;
10744     StartClocks();
10745     firstMove = FALSE;
10746
10747     if (appData.autoFlipView && !flipView) {
10748       flipView = !flipView;
10749       DrawPosition(FALSE, NULL);
10750       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10751     }
10752
10753     if(bookHit) { // [HGM] book: simulate book reply
10754         static char bookMove[MSG_SIZ]; // a bit generous?
10755
10756         programStats.nodes = programStats.depth = programStats.time = 
10757         programStats.score = programStats.got_only_move = 0;
10758         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10759
10760         strcpy(bookMove, "move ");
10761         strcat(bookMove, bookHit);
10762         HandleMachineMove(bookMove, &first);
10763     }
10764 }
10765
10766 void
10767 MachineBlackEvent()
10768 {
10769     char buf[MSG_SIZ];
10770    char *bookHit = NULL;
10771
10772     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10773         return;
10774
10775
10776     if (gameMode == PlayFromGameFile || 
10777         gameMode == TwoMachinesPlay  || 
10778         gameMode == Training         || 
10779         gameMode == AnalyzeMode      || 
10780         gameMode == EndOfGame)
10781         EditGameEvent();
10782
10783     if (gameMode == EditPosition) 
10784         EditPositionDone(TRUE);
10785
10786     if (WhiteOnMove(currentMove)) {
10787         DisplayError(_("It is not Black's turn"), 0);
10788         return;
10789     }
10790     
10791     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10792       ExitAnalyzeMode();
10793
10794     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10795         gameMode == AnalyzeFile)
10796         TruncateGame();
10797
10798     ResurrectChessProgram();    /* in case it isn't running */
10799     gameMode = MachinePlaysBlack;
10800     pausing = FALSE;
10801     ModeHighlight();
10802     SetGameInfo();
10803     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10804     DisplayTitle(buf);
10805     if (first.sendName) {
10806       sprintf(buf, "name %s\n", gameInfo.white);
10807       SendToProgram(buf, &first);
10808     }
10809     if (first.sendTime) {
10810       if (first.useColors) {
10811         SendToProgram("white\n", &first); /*gnu kludge*/
10812       }
10813       SendTimeRemaining(&first, FALSE);
10814     }
10815     if (first.useColors) {
10816       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10817     }
10818     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10819     SetMachineThinkingEnables();
10820     first.maybeThinking = TRUE;
10821     StartClocks();
10822
10823     if (appData.autoFlipView && flipView) {
10824       flipView = !flipView;
10825       DrawPosition(FALSE, NULL);
10826       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10827     }
10828     if(bookHit) { // [HGM] book: simulate book reply
10829         static char bookMove[MSG_SIZ]; // a bit generous?
10830
10831         programStats.nodes = programStats.depth = programStats.time = 
10832         programStats.score = programStats.got_only_move = 0;
10833         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10834
10835         strcpy(bookMove, "move ");
10836         strcat(bookMove, bookHit);
10837         HandleMachineMove(bookMove, &first);
10838     }
10839 }
10840
10841
10842 void
10843 DisplayTwoMachinesTitle()
10844 {
10845     char buf[MSG_SIZ];
10846     if (appData.matchGames > 0) {
10847         if (first.twoMachinesColor[0] == 'w') {
10848             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10849                     gameInfo.white, gameInfo.black,
10850                     first.matchWins, second.matchWins,
10851                     matchGame - 1 - (first.matchWins + second.matchWins));
10852         } else {
10853             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10854                     gameInfo.white, gameInfo.black,
10855                     second.matchWins, first.matchWins,
10856                     matchGame - 1 - (first.matchWins + second.matchWins));
10857         }
10858     } else {
10859         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10860     }
10861     DisplayTitle(buf);
10862 }
10863
10864 void
10865 TwoMachinesEvent P((void))
10866 {
10867     int i;
10868     char buf[MSG_SIZ];
10869     ChessProgramState *onmove;
10870     char *bookHit = NULL;
10871     
10872     if (appData.noChessProgram) return;
10873
10874     switch (gameMode) {
10875       case TwoMachinesPlay:
10876         return;
10877       case MachinePlaysWhite:
10878       case MachinePlaysBlack:
10879         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10880             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10881             return;
10882         }
10883         /* fall through */
10884       case BeginningOfGame:
10885       case PlayFromGameFile:
10886       case EndOfGame:
10887         EditGameEvent();
10888         if (gameMode != EditGame) return;
10889         break;
10890       case EditPosition:
10891         EditPositionDone(TRUE);
10892         break;
10893       case AnalyzeMode:
10894       case AnalyzeFile:
10895         ExitAnalyzeMode();
10896         break;
10897       case EditGame:
10898       default:
10899         break;
10900     }
10901
10902     forwardMostMove = currentMove;
10903     ResurrectChessProgram();    /* in case first program isn't running */
10904
10905     if (second.pr == NULL) {
10906         StartChessProgram(&second);
10907         if (second.protocolVersion == 1) {
10908           TwoMachinesEventIfReady();
10909         } else {
10910           /* kludge: allow timeout for initial "feature" command */
10911           FreezeUI();
10912           DisplayMessage("", _("Starting second chess program"));
10913           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10914         }
10915         return;
10916     }
10917     DisplayMessage("", "");
10918     InitChessProgram(&second, FALSE);
10919     SendToProgram("force\n", &second);
10920     if (startedFromSetupPosition) {
10921         SendBoard(&second, backwardMostMove);
10922     if (appData.debugMode) {
10923         fprintf(debugFP, "Two Machines\n");
10924     }
10925     }
10926     for (i = backwardMostMove; i < forwardMostMove; i++) {
10927         SendMoveToProgram(i, &second);
10928     }
10929
10930     gameMode = TwoMachinesPlay;
10931     pausing = FALSE;
10932     ModeHighlight();
10933     SetGameInfo();
10934     DisplayTwoMachinesTitle();
10935     firstMove = TRUE;
10936     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10937         onmove = &first;
10938     } else {
10939         onmove = &second;
10940     }
10941
10942     SendToProgram(first.computerString, &first);
10943     if (first.sendName) {
10944       sprintf(buf, "name %s\n", second.tidy);
10945       SendToProgram(buf, &first);
10946     }
10947     SendToProgram(second.computerString, &second);
10948     if (second.sendName) {
10949       sprintf(buf, "name %s\n", first.tidy);
10950       SendToProgram(buf, &second);
10951     }
10952
10953     ResetClocks();
10954     if (!first.sendTime || !second.sendTime) {
10955         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10956         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10957     }
10958     if (onmove->sendTime) {
10959       if (onmove->useColors) {
10960         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10961       }
10962       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10963     }
10964     if (onmove->useColors) {
10965       SendToProgram(onmove->twoMachinesColor, onmove);
10966     }
10967     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10968 //    SendToProgram("go\n", onmove);
10969     onmove->maybeThinking = TRUE;
10970     SetMachineThinkingEnables();
10971
10972     StartClocks();
10973
10974     if(bookHit) { // [HGM] book: simulate book reply
10975         static char bookMove[MSG_SIZ]; // a bit generous?
10976
10977         programStats.nodes = programStats.depth = programStats.time = 
10978         programStats.score = programStats.got_only_move = 0;
10979         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10980
10981         strcpy(bookMove, "move ");
10982         strcat(bookMove, bookHit);
10983         savedMessage = bookMove; // args for deferred call
10984         savedState = onmove;
10985         ScheduleDelayedEvent(DeferredBookMove, 1);
10986     }
10987 }
10988
10989 void
10990 TrainingEvent()
10991 {
10992     if (gameMode == Training) {
10993       SetTrainingModeOff();
10994       gameMode = PlayFromGameFile;
10995       DisplayMessage("", _("Training mode off"));
10996     } else {
10997       gameMode = Training;
10998       animateTraining = appData.animate;
10999
11000       /* make sure we are not already at the end of the game */
11001       if (currentMove < forwardMostMove) {
11002         SetTrainingModeOn();
11003         DisplayMessage("", _("Training mode on"));
11004       } else {
11005         gameMode = PlayFromGameFile;
11006         DisplayError(_("Already at end of game"), 0);
11007       }
11008     }
11009     ModeHighlight();
11010 }
11011
11012 void
11013 IcsClientEvent()
11014 {
11015     if (!appData.icsActive) return;
11016     switch (gameMode) {
11017       case IcsPlayingWhite:
11018       case IcsPlayingBlack:
11019       case IcsObserving:
11020       case IcsIdle:
11021       case BeginningOfGame:
11022       case IcsExamining:
11023         return;
11024
11025       case EditGame:
11026         break;
11027
11028       case EditPosition:
11029         EditPositionDone(TRUE);
11030         break;
11031
11032       case AnalyzeMode:
11033       case AnalyzeFile:
11034         ExitAnalyzeMode();
11035         break;
11036         
11037       default:
11038         EditGameEvent();
11039         break;
11040     }
11041
11042     gameMode = IcsIdle;
11043     ModeHighlight();
11044     return;
11045 }
11046
11047
11048 void
11049 EditGameEvent()
11050 {
11051     int i;
11052
11053     switch (gameMode) {
11054       case Training:
11055         SetTrainingModeOff();
11056         break;
11057       case MachinePlaysWhite:
11058       case MachinePlaysBlack:
11059       case BeginningOfGame:
11060         SendToProgram("force\n", &first);
11061         SetUserThinkingEnables();
11062         break;
11063       case PlayFromGameFile:
11064         (void) StopLoadGameTimer();
11065         if (gameFileFP != NULL) {
11066             gameFileFP = NULL;
11067         }
11068         break;
11069       case EditPosition:
11070         EditPositionDone(TRUE);
11071         break;
11072       case AnalyzeMode:
11073       case AnalyzeFile:
11074         ExitAnalyzeMode();
11075         SendToProgram("force\n", &first);
11076         break;
11077       case TwoMachinesPlay:
11078         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11079         ResurrectChessProgram();
11080         SetUserThinkingEnables();
11081         break;
11082       case EndOfGame:
11083         ResurrectChessProgram();
11084         break;
11085       case IcsPlayingBlack:
11086       case IcsPlayingWhite:
11087         DisplayError(_("Warning: You are still playing a game"), 0);
11088         break;
11089       case IcsObserving:
11090         DisplayError(_("Warning: You are still observing a game"), 0);
11091         break;
11092       case IcsExamining:
11093         DisplayError(_("Warning: You are still examining a game"), 0);
11094         break;
11095       case IcsIdle:
11096         break;
11097       case EditGame:
11098       default:
11099         return;
11100     }
11101     
11102     pausing = FALSE;
11103     StopClocks();
11104     first.offeredDraw = second.offeredDraw = 0;
11105
11106     if (gameMode == PlayFromGameFile) {
11107         whiteTimeRemaining = timeRemaining[0][currentMove];
11108         blackTimeRemaining = timeRemaining[1][currentMove];
11109         DisplayTitle("");
11110     }
11111
11112     if (gameMode == MachinePlaysWhite ||
11113         gameMode == MachinePlaysBlack ||
11114         gameMode == TwoMachinesPlay ||
11115         gameMode == EndOfGame) {
11116         i = forwardMostMove;
11117         while (i > currentMove) {
11118             SendToProgram("undo\n", &first);
11119             i--;
11120         }
11121         whiteTimeRemaining = timeRemaining[0][currentMove];
11122         blackTimeRemaining = timeRemaining[1][currentMove];
11123         DisplayBothClocks();
11124         if (whiteFlag || blackFlag) {
11125             whiteFlag = blackFlag = 0;
11126         }
11127         DisplayTitle("");
11128     }           
11129     
11130     gameMode = EditGame;
11131     ModeHighlight();
11132     SetGameInfo();
11133 }
11134
11135
11136 void
11137 EditPositionEvent()
11138 {
11139     if (gameMode == EditPosition) {
11140         EditGameEvent();
11141         return;
11142     }
11143     
11144     EditGameEvent();
11145     if (gameMode != EditGame) return;
11146     
11147     gameMode = EditPosition;
11148     ModeHighlight();
11149     SetGameInfo();
11150     if (currentMove > 0)
11151       CopyBoard(boards[0], boards[currentMove]);
11152     
11153     blackPlaysFirst = !WhiteOnMove(currentMove);
11154     ResetClocks();
11155     currentMove = forwardMostMove = backwardMostMove = 0;
11156     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11157     DisplayMove(-1);
11158 }
11159
11160 void
11161 ExitAnalyzeMode()
11162 {
11163     /* [DM] icsEngineAnalyze - possible call from other functions */
11164     if (appData.icsEngineAnalyze) {
11165         appData.icsEngineAnalyze = FALSE;
11166
11167         DisplayMessage("",_("Close ICS engine analyze..."));
11168     }
11169     if (first.analysisSupport && first.analyzing) {
11170       SendToProgram("exit\n", &first);
11171       first.analyzing = FALSE;
11172     }
11173     thinkOutput[0] = NULLCHAR;
11174 }
11175
11176 void
11177 EditPositionDone(Boolean fakeRights)
11178 {
11179     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11180
11181     startedFromSetupPosition = TRUE;
11182     InitChessProgram(&first, FALSE);
11183     if(fakeRights)  
11184       { /* don't do this if we just pasted FEN */
11185         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11186         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11187           {
11188             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11189             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11190           } 
11191         else 
11192           castlingRights[0][2] = -1;
11193         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11194           {
11195             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11196             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11197           } 
11198         else 
11199           castlingRights[0][5] = -1;
11200       }
11201     SendToProgram("force\n", &first);
11202     if (blackPlaysFirst) {
11203         strcpy(moveList[0], "");
11204         strcpy(parseList[0], "");
11205         currentMove = forwardMostMove = backwardMostMove = 1;
11206         CopyBoard(boards[1], boards[0]);
11207         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11208         { int i;
11209           epStatus[1] = epStatus[0];
11210           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11211         }
11212     } else {
11213         currentMove = forwardMostMove = backwardMostMove = 0;
11214     }
11215     SendBoard(&first, forwardMostMove);
11216     if (appData.debugMode) {
11217         fprintf(debugFP, "EditPosDone\n");
11218     }
11219     DisplayTitle("");
11220     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11221     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11222     gameMode = EditGame;
11223     ModeHighlight();
11224     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11225     ClearHighlights(); /* [AS] */
11226 }
11227
11228 /* Pause for `ms' milliseconds */
11229 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11230 void
11231 TimeDelay(ms)
11232      long ms;
11233 {
11234     TimeMark m1, m2;
11235
11236     GetTimeMark(&m1);
11237     do {
11238         GetTimeMark(&m2);
11239     } while (SubtractTimeMarks(&m2, &m1) < ms);
11240 }
11241
11242 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11243 void
11244 SendMultiLineToICS(buf)
11245      char *buf;
11246 {
11247     char temp[MSG_SIZ+1], *p;
11248     int len;
11249
11250     len = strlen(buf);
11251     if (len > MSG_SIZ)
11252       len = MSG_SIZ;
11253   
11254     strncpy(temp, buf, len);
11255     temp[len] = 0;
11256
11257     p = temp;
11258     while (*p) {
11259         if (*p == '\n' || *p == '\r')
11260           *p = ' ';
11261         ++p;
11262     }
11263
11264     strcat(temp, "\n");
11265     SendToICS(temp);
11266     SendToPlayer(temp, strlen(temp));
11267 }
11268
11269 void
11270 SetWhiteToPlayEvent()
11271 {
11272     if (gameMode == EditPosition) {
11273         blackPlaysFirst = FALSE;
11274         DisplayBothClocks();    /* works because currentMove is 0 */
11275     } else if (gameMode == IcsExamining) {
11276         SendToICS(ics_prefix);
11277         SendToICS("tomove white\n");
11278     }
11279 }
11280
11281 void
11282 SetBlackToPlayEvent()
11283 {
11284     if (gameMode == EditPosition) {
11285         blackPlaysFirst = TRUE;
11286         currentMove = 1;        /* kludge */
11287         DisplayBothClocks();
11288         currentMove = 0;
11289     } else if (gameMode == IcsExamining) {
11290         SendToICS(ics_prefix);
11291         SendToICS("tomove black\n");
11292     }
11293 }
11294
11295 void
11296 EditPositionMenuEvent(selection, x, y)
11297      ChessSquare selection;
11298      int x, y;
11299 {
11300     char buf[MSG_SIZ];
11301     ChessSquare piece = boards[0][y][x];
11302
11303     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11304
11305     switch (selection) {
11306       case ClearBoard:
11307         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11308             SendToICS(ics_prefix);
11309             SendToICS("bsetup clear\n");
11310         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11311             SendToICS(ics_prefix);
11312             SendToICS("clearboard\n");
11313         } else {
11314             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11315                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11316                 for (y = 0; y < BOARD_HEIGHT; y++) {
11317                     if (gameMode == IcsExamining) {
11318                         if (boards[currentMove][y][x] != EmptySquare) {
11319                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11320                                     AAA + x, ONE + y);
11321                             SendToICS(buf);
11322                         }
11323                     } else {
11324                         boards[0][y][x] = p;
11325                     }
11326                 }
11327             }
11328         }
11329         if (gameMode == EditPosition) {
11330             DrawPosition(FALSE, boards[0]);
11331         }
11332         break;
11333
11334       case WhitePlay:
11335         SetWhiteToPlayEvent();
11336         break;
11337
11338       case BlackPlay:
11339         SetBlackToPlayEvent();
11340         break;
11341
11342       case EmptySquare:
11343         if (gameMode == IcsExamining) {
11344             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11345             SendToICS(buf);
11346         } else {
11347             boards[0][y][x] = EmptySquare;
11348             DrawPosition(FALSE, boards[0]);
11349         }
11350         break;
11351
11352       case PromotePiece:
11353         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11354            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11355             selection = (ChessSquare) (PROMOTED piece);
11356         } else if(piece == EmptySquare) selection = WhiteSilver;
11357         else selection = (ChessSquare)((int)piece - 1);
11358         goto defaultlabel;
11359
11360       case DemotePiece:
11361         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11362            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11363             selection = (ChessSquare) (DEMOTED piece);
11364         } else if(piece == EmptySquare) selection = BlackSilver;
11365         else selection = (ChessSquare)((int)piece + 1);       
11366         goto defaultlabel;
11367
11368       case WhiteQueen:
11369       case BlackQueen:
11370         if(gameInfo.variant == VariantShatranj ||
11371            gameInfo.variant == VariantXiangqi  ||
11372            gameInfo.variant == VariantCourier    )
11373             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11374         goto defaultlabel;
11375
11376       case WhiteKing:
11377       case BlackKing:
11378         if(gameInfo.variant == VariantXiangqi)
11379             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11380         if(gameInfo.variant == VariantKnightmate)
11381             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11382       default:
11383         defaultlabel:
11384         if (gameMode == IcsExamining) {
11385             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11386                     PieceToChar(selection), AAA + x, ONE + y);
11387             SendToICS(buf);
11388         } else {
11389             boards[0][y][x] = selection;
11390             DrawPosition(FALSE, boards[0]);
11391         }
11392         break;
11393     }
11394 }
11395
11396
11397 void
11398 DropMenuEvent(selection, x, y)
11399      ChessSquare selection;
11400      int x, y;
11401 {
11402     ChessMove moveType;
11403
11404     switch (gameMode) {
11405       case IcsPlayingWhite:
11406       case MachinePlaysBlack:
11407         if (!WhiteOnMove(currentMove)) {
11408             DisplayMoveError(_("It is Black's turn"));
11409             return;
11410         }
11411         moveType = WhiteDrop;
11412         break;
11413       case IcsPlayingBlack:
11414       case MachinePlaysWhite:
11415         if (WhiteOnMove(currentMove)) {
11416             DisplayMoveError(_("It is White's turn"));
11417             return;
11418         }
11419         moveType = BlackDrop;
11420         break;
11421       case EditGame:
11422         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11423         break;
11424       default:
11425         return;
11426     }
11427
11428     if (moveType == BlackDrop && selection < BlackPawn) {
11429       selection = (ChessSquare) ((int) selection
11430                                  + (int) BlackPawn - (int) WhitePawn);
11431     }
11432     if (boards[currentMove][y][x] != EmptySquare) {
11433         DisplayMoveError(_("That square is occupied"));
11434         return;
11435     }
11436
11437     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11438 }
11439
11440 void
11441 AcceptEvent()
11442 {
11443     /* Accept a pending offer of any kind from opponent */
11444     
11445     if (appData.icsActive) {
11446         SendToICS(ics_prefix);
11447         SendToICS("accept\n");
11448     } else if (cmailMsgLoaded) {
11449         if (currentMove == cmailOldMove &&
11450             commentList[cmailOldMove] != NULL &&
11451             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11452                    "Black offers a draw" : "White offers a draw")) {
11453             TruncateGame();
11454             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11455             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11456         } else {
11457             DisplayError(_("There is no pending offer on this move"), 0);
11458             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11459         }
11460     } else {
11461         /* Not used for offers from chess program */
11462     }
11463 }
11464
11465 void
11466 DeclineEvent()
11467 {
11468     /* Decline a pending offer of any kind from opponent */
11469     
11470     if (appData.icsActive) {
11471         SendToICS(ics_prefix);
11472         SendToICS("decline\n");
11473     } else if (cmailMsgLoaded) {
11474         if (currentMove == cmailOldMove &&
11475             commentList[cmailOldMove] != NULL &&
11476             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11477                    "Black offers a draw" : "White offers a draw")) {
11478 #ifdef NOTDEF
11479             AppendComment(cmailOldMove, "Draw declined");
11480             DisplayComment(cmailOldMove - 1, "Draw declined");
11481 #endif /*NOTDEF*/
11482         } else {
11483             DisplayError(_("There is no pending offer on this move"), 0);
11484         }
11485     } else {
11486         /* Not used for offers from chess program */
11487     }
11488 }
11489
11490 void
11491 RematchEvent()
11492 {
11493     /* Issue ICS rematch command */
11494     if (appData.icsActive) {
11495         SendToICS(ics_prefix);
11496         SendToICS("rematch\n");
11497     }
11498 }
11499
11500 void
11501 CallFlagEvent()
11502 {
11503     /* Call your opponent's flag (claim a win on time) */
11504     if (appData.icsActive) {
11505         SendToICS(ics_prefix);
11506         SendToICS("flag\n");
11507     } else {
11508         switch (gameMode) {
11509           default:
11510             return;
11511           case MachinePlaysWhite:
11512             if (whiteFlag) {
11513                 if (blackFlag)
11514                   GameEnds(GameIsDrawn, "Both players ran out of time",
11515                            GE_PLAYER);
11516                 else
11517                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11518             } else {
11519                 DisplayError(_("Your opponent is not out of time"), 0);
11520             }
11521             break;
11522           case MachinePlaysBlack:
11523             if (blackFlag) {
11524                 if (whiteFlag)
11525                   GameEnds(GameIsDrawn, "Both players ran out of time",
11526                            GE_PLAYER);
11527                 else
11528                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11529             } else {
11530                 DisplayError(_("Your opponent is not out of time"), 0);
11531             }
11532             break;
11533         }
11534     }
11535 }
11536
11537 void
11538 DrawEvent()
11539 {
11540     /* Offer draw or accept pending draw offer from opponent */
11541     
11542     if (appData.icsActive) {
11543         /* Note: tournament rules require draw offers to be
11544            made after you make your move but before you punch
11545            your clock.  Currently ICS doesn't let you do that;
11546            instead, you immediately punch your clock after making
11547            a move, but you can offer a draw at any time. */
11548         
11549         SendToICS(ics_prefix);
11550         SendToICS("draw\n");
11551     } else if (cmailMsgLoaded) {
11552         if (currentMove == cmailOldMove &&
11553             commentList[cmailOldMove] != NULL &&
11554             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11555                    "Black offers a draw" : "White offers a draw")) {
11556             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11557             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11558         } else if (currentMove == cmailOldMove + 1) {
11559             char *offer = WhiteOnMove(cmailOldMove) ?
11560               "White offers a draw" : "Black offers a draw";
11561             AppendComment(currentMove, offer);
11562             DisplayComment(currentMove - 1, offer);
11563             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11564         } else {
11565             DisplayError(_("You must make your move before offering a draw"), 0);
11566             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11567         }
11568     } else if (first.offeredDraw) {
11569         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11570     } else {
11571         if (first.sendDrawOffers) {
11572             SendToProgram("draw\n", &first);
11573             userOfferedDraw = TRUE;
11574         }
11575     }
11576 }
11577
11578 void
11579 AdjournEvent()
11580 {
11581     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11582     
11583     if (appData.icsActive) {
11584         SendToICS(ics_prefix);
11585         SendToICS("adjourn\n");
11586     } else {
11587         /* Currently GNU Chess doesn't offer or accept Adjourns */
11588     }
11589 }
11590
11591
11592 void
11593 AbortEvent()
11594 {
11595     /* Offer Abort or accept pending Abort offer from opponent */
11596     
11597     if (appData.icsActive) {
11598         SendToICS(ics_prefix);
11599         SendToICS("abort\n");
11600     } else {
11601         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11602     }
11603 }
11604
11605 void
11606 ResignEvent()
11607 {
11608     /* Resign.  You can do this even if it's not your turn. */
11609     
11610     if (appData.icsActive) {
11611         SendToICS(ics_prefix);
11612         SendToICS("resign\n");
11613     } else {
11614         switch (gameMode) {
11615           case MachinePlaysWhite:
11616             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11617             break;
11618           case MachinePlaysBlack:
11619             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11620             break;
11621           case EditGame:
11622             if (cmailMsgLoaded) {
11623                 TruncateGame();
11624                 if (WhiteOnMove(cmailOldMove)) {
11625                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11626                 } else {
11627                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11628                 }
11629                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11630             }
11631             break;
11632           default:
11633             break;
11634         }
11635     }
11636 }
11637
11638
11639 void
11640 StopObservingEvent()
11641 {
11642     /* Stop observing current games */
11643     SendToICS(ics_prefix);
11644     SendToICS("unobserve\n");
11645 }
11646
11647 void
11648 StopExaminingEvent()
11649 {
11650     /* Stop observing current game */
11651     SendToICS(ics_prefix);
11652     SendToICS("unexamine\n");
11653 }
11654
11655 void
11656 ForwardInner(target)
11657      int target;
11658 {
11659     int limit;
11660
11661     if (appData.debugMode)
11662         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11663                 target, currentMove, forwardMostMove);
11664
11665     if (gameMode == EditPosition)
11666       return;
11667
11668     if (gameMode == PlayFromGameFile && !pausing)
11669       PauseEvent();
11670     
11671     if (gameMode == IcsExamining && pausing)
11672       limit = pauseExamForwardMostMove;
11673     else
11674       limit = forwardMostMove;
11675     
11676     if (target > limit) target = limit;
11677
11678     if (target > 0 && moveList[target - 1][0]) {
11679         int fromX, fromY, toX, toY;
11680         toX = moveList[target - 1][2] - AAA;
11681         toY = moveList[target - 1][3] - ONE;
11682         if (moveList[target - 1][1] == '@') {
11683             if (appData.highlightLastMove) {
11684                 SetHighlights(-1, -1, toX, toY);
11685             }
11686         } else {
11687             fromX = moveList[target - 1][0] - AAA;
11688             fromY = moveList[target - 1][1] - ONE;
11689             if (target == currentMove + 1) {
11690                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11691             }
11692             if (appData.highlightLastMove) {
11693                 SetHighlights(fromX, fromY, toX, toY);
11694             }
11695         }
11696     }
11697     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11698         gameMode == Training || gameMode == PlayFromGameFile || 
11699         gameMode == AnalyzeFile) {
11700         while (currentMove < target) {
11701             SendMoveToProgram(currentMove++, &first);
11702         }
11703     } else {
11704         currentMove = target;
11705     }
11706     
11707     if (gameMode == EditGame || gameMode == EndOfGame) {
11708         whiteTimeRemaining = timeRemaining[0][currentMove];
11709         blackTimeRemaining = timeRemaining[1][currentMove];
11710     }
11711     DisplayBothClocks();
11712     DisplayMove(currentMove - 1);
11713     DrawPosition(FALSE, boards[currentMove]);
11714     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11715     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11716         DisplayComment(currentMove - 1, commentList[currentMove]);
11717     }
11718 }
11719
11720
11721 void
11722 ForwardEvent()
11723 {
11724     if (gameMode == IcsExamining && !pausing) {
11725         SendToICS(ics_prefix);
11726         SendToICS("forward\n");
11727     } else {
11728         ForwardInner(currentMove + 1);
11729     }
11730 }
11731
11732 void
11733 ToEndEvent()
11734 {
11735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11736         /* to optimze, we temporarily turn off analysis mode while we feed
11737          * the remaining moves to the engine. Otherwise we get analysis output
11738          * after each move.
11739          */ 
11740         if (first.analysisSupport) {
11741           SendToProgram("exit\nforce\n", &first);
11742           first.analyzing = FALSE;
11743         }
11744     }
11745         
11746     if (gameMode == IcsExamining && !pausing) {
11747         SendToICS(ics_prefix);
11748         SendToICS("forward 999999\n");
11749     } else {
11750         ForwardInner(forwardMostMove);
11751     }
11752
11753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11754         /* we have fed all the moves, so reactivate analysis mode */
11755         SendToProgram("analyze\n", &first);
11756         first.analyzing = TRUE;
11757         /*first.maybeThinking = TRUE;*/
11758         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11759     }
11760 }
11761
11762 void
11763 BackwardInner(target)
11764      int target;
11765 {
11766     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11767
11768     if (appData.debugMode)
11769         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11770                 target, currentMove, forwardMostMove);
11771
11772     if (gameMode == EditPosition) return;
11773     if (currentMove <= backwardMostMove) {
11774         ClearHighlights();
11775         DrawPosition(full_redraw, boards[currentMove]);
11776         return;
11777     }
11778     if (gameMode == PlayFromGameFile && !pausing)
11779       PauseEvent();
11780     
11781     if (moveList[target][0]) {
11782         int fromX, fromY, toX, toY;
11783         toX = moveList[target][2] - AAA;
11784         toY = moveList[target][3] - ONE;
11785         if (moveList[target][1] == '@') {
11786             if (appData.highlightLastMove) {
11787                 SetHighlights(-1, -1, toX, toY);
11788             }
11789         } else {
11790             fromX = moveList[target][0] - AAA;
11791             fromY = moveList[target][1] - ONE;
11792             if (target == currentMove - 1) {
11793                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11794             }
11795             if (appData.highlightLastMove) {
11796                 SetHighlights(fromX, fromY, toX, toY);
11797             }
11798         }
11799     }
11800     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11801         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11802         while (currentMove > target) {
11803             SendToProgram("undo\n", &first);
11804             currentMove--;
11805         }
11806     } else {
11807         currentMove = target;
11808     }
11809     
11810     if (gameMode == EditGame || gameMode == EndOfGame) {
11811         whiteTimeRemaining = timeRemaining[0][currentMove];
11812         blackTimeRemaining = timeRemaining[1][currentMove];
11813     }
11814     DisplayBothClocks();
11815     DisplayMove(currentMove - 1);
11816     DrawPosition(full_redraw, boards[currentMove]);
11817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11818     // [HGM] PV info: routine tests if comment empty
11819     DisplayComment(currentMove - 1, commentList[currentMove]);
11820 }
11821
11822 void
11823 BackwardEvent()
11824 {
11825     if (gameMode == IcsExamining && !pausing) {
11826         SendToICS(ics_prefix);
11827         SendToICS("backward\n");
11828     } else {
11829         BackwardInner(currentMove - 1);
11830     }
11831 }
11832
11833 void
11834 ToStartEvent()
11835 {
11836     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11837         /* to optimize, we temporarily turn off analysis mode while we undo
11838          * all the moves. Otherwise we get analysis output after each undo.
11839          */ 
11840         if (first.analysisSupport) {
11841           SendToProgram("exit\nforce\n", &first);
11842           first.analyzing = FALSE;
11843         }
11844     }
11845
11846     if (gameMode == IcsExamining && !pausing) {
11847         SendToICS(ics_prefix);
11848         SendToICS("backward 999999\n");
11849     } else {
11850         BackwardInner(backwardMostMove);
11851     }
11852
11853     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11854         /* we have fed all the moves, so reactivate analysis mode */
11855         SendToProgram("analyze\n", &first);
11856         first.analyzing = TRUE;
11857         /*first.maybeThinking = TRUE;*/
11858         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11859     }
11860 }
11861
11862 void
11863 ToNrEvent(int to)
11864 {
11865   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11866   if (to >= forwardMostMove) to = forwardMostMove;
11867   if (to <= backwardMostMove) to = backwardMostMove;
11868   if (to < currentMove) {
11869     BackwardInner(to);
11870   } else {
11871     ForwardInner(to);
11872   }
11873 }
11874
11875 void
11876 RevertEvent()
11877 {
11878     if (gameMode != IcsExamining) {
11879         DisplayError(_("You are not examining a game"), 0);
11880         return;
11881     }
11882     if (pausing) {
11883         DisplayError(_("You can't revert while pausing"), 0);
11884         return;
11885     }
11886     SendToICS(ics_prefix);
11887     SendToICS("revert\n");
11888 }
11889
11890 void
11891 RetractMoveEvent()
11892 {
11893     switch (gameMode) {
11894       case MachinePlaysWhite:
11895       case MachinePlaysBlack:
11896         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11897             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11898             return;
11899         }
11900         if (forwardMostMove < 2) return;
11901         currentMove = forwardMostMove = forwardMostMove - 2;
11902         whiteTimeRemaining = timeRemaining[0][currentMove];
11903         blackTimeRemaining = timeRemaining[1][currentMove];
11904         DisplayBothClocks();
11905         DisplayMove(currentMove - 1);
11906         ClearHighlights();/*!! could figure this out*/
11907         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11908         SendToProgram("remove\n", &first);
11909         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11910         break;
11911
11912       case BeginningOfGame:
11913       default:
11914         break;
11915
11916       case IcsPlayingWhite:
11917       case IcsPlayingBlack:
11918         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11919             SendToICS(ics_prefix);
11920             SendToICS("takeback 2\n");
11921         } else {
11922             SendToICS(ics_prefix);
11923             SendToICS("takeback 1\n");
11924         }
11925         break;
11926     }
11927 }
11928
11929 void
11930 MoveNowEvent()
11931 {
11932     ChessProgramState *cps;
11933
11934     switch (gameMode) {
11935       case MachinePlaysWhite:
11936         if (!WhiteOnMove(forwardMostMove)) {
11937             DisplayError(_("It is your turn"), 0);
11938             return;
11939         }
11940         cps = &first;
11941         break;
11942       case MachinePlaysBlack:
11943         if (WhiteOnMove(forwardMostMove)) {
11944             DisplayError(_("It is your turn"), 0);
11945             return;
11946         }
11947         cps = &first;
11948         break;
11949       case TwoMachinesPlay:
11950         if (WhiteOnMove(forwardMostMove) ==
11951             (first.twoMachinesColor[0] == 'w')) {
11952             cps = &first;
11953         } else {
11954             cps = &second;
11955         }
11956         break;
11957       case BeginningOfGame:
11958       default:
11959         return;
11960     }
11961     SendToProgram("?\n", cps);
11962 }
11963
11964 void
11965 TruncateGameEvent()
11966 {
11967     EditGameEvent();
11968     if (gameMode != EditGame) return;
11969     TruncateGame();
11970 }
11971
11972 void
11973 TruncateGame()
11974 {
11975     if (forwardMostMove > currentMove) {
11976         if (gameInfo.resultDetails != NULL) {
11977             free(gameInfo.resultDetails);
11978             gameInfo.resultDetails = NULL;
11979             gameInfo.result = GameUnfinished;
11980         }
11981         forwardMostMove = currentMove;
11982         HistorySet(parseList, backwardMostMove, forwardMostMove,
11983                    currentMove-1);
11984     }
11985 }
11986
11987 void
11988 HintEvent()
11989 {
11990     if (appData.noChessProgram) return;
11991     switch (gameMode) {
11992       case MachinePlaysWhite:
11993         if (WhiteOnMove(forwardMostMove)) {
11994             DisplayError(_("Wait until your turn"), 0);
11995             return;
11996         }
11997         break;
11998       case BeginningOfGame:
11999       case MachinePlaysBlack:
12000         if (!WhiteOnMove(forwardMostMove)) {
12001             DisplayError(_("Wait until your turn"), 0);
12002             return;
12003         }
12004         break;
12005       default:
12006         DisplayError(_("No hint available"), 0);
12007         return;
12008     }
12009     SendToProgram("hint\n", &first);
12010     hintRequested = TRUE;
12011 }
12012
12013 void
12014 BookEvent()
12015 {
12016     if (appData.noChessProgram) return;
12017     switch (gameMode) {
12018       case MachinePlaysWhite:
12019         if (WhiteOnMove(forwardMostMove)) {
12020             DisplayError(_("Wait until your turn"), 0);
12021             return;
12022         }
12023         break;
12024       case BeginningOfGame:
12025       case MachinePlaysBlack:
12026         if (!WhiteOnMove(forwardMostMove)) {
12027             DisplayError(_("Wait until your turn"), 0);
12028             return;
12029         }
12030         break;
12031       case EditPosition:
12032         EditPositionDone(TRUE);
12033         break;
12034       case TwoMachinesPlay:
12035         return;
12036       default:
12037         break;
12038     }
12039     SendToProgram("bk\n", &first);
12040     bookOutput[0] = NULLCHAR;
12041     bookRequested = TRUE;
12042 }
12043
12044 void
12045 AboutGameEvent()
12046 {
12047     char *tags = PGNTags(&gameInfo);
12048     TagsPopUp(tags, CmailMsg());
12049     free(tags);
12050 }
12051
12052 /* end button procedures */
12053
12054 void
12055 PrintPosition(fp, move)
12056      FILE *fp;
12057      int move;
12058 {
12059     int i, j;
12060     
12061     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12062         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12063             char c = PieceToChar(boards[move][i][j]);
12064             fputc(c == 'x' ? '.' : c, fp);
12065             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12066         }
12067     }
12068     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12069       fprintf(fp, "white to play\n");
12070     else
12071       fprintf(fp, "black to play\n");
12072 }
12073
12074 void
12075 PrintOpponents(fp)
12076      FILE *fp;
12077 {
12078     if (gameInfo.white != NULL) {
12079         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12080     } else {
12081         fprintf(fp, "\n");
12082     }
12083 }
12084
12085 /* Find last component of program's own name, using some heuristics */
12086 void
12087 TidyProgramName(prog, host, buf)
12088      char *prog, *host, buf[MSG_SIZ];
12089 {
12090     char *p, *q;
12091     int local = (strcmp(host, "localhost") == 0);
12092     while (!local && (p = strchr(prog, ';')) != NULL) {
12093         p++;
12094         while (*p == ' ') p++;
12095         prog = p;
12096     }
12097     if (*prog == '"' || *prog == '\'') {
12098         q = strchr(prog + 1, *prog);
12099     } else {
12100         q = strchr(prog, ' ');
12101     }
12102     if (q == NULL) q = prog + strlen(prog);
12103     p = q;
12104     while (p >= prog && *p != '/' && *p != '\\') p--;
12105     p++;
12106     if(p == prog && *p == '"') p++;
12107     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12108     memcpy(buf, p, q - p);
12109     buf[q - p] = NULLCHAR;
12110     if (!local) {
12111         strcat(buf, "@");
12112         strcat(buf, host);
12113     }
12114 }
12115
12116 char *
12117 TimeControlTagValue()
12118 {
12119     char buf[MSG_SIZ];
12120     if (!appData.clockMode) {
12121         strcpy(buf, "-");
12122     } else if (movesPerSession > 0) {
12123         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12124     } else if (timeIncrement == 0) {
12125         sprintf(buf, "%ld", timeControl/1000);
12126     } else {
12127         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12128     }
12129     return StrSave(buf);
12130 }
12131
12132 void
12133 SetGameInfo()
12134 {
12135     /* This routine is used only for certain modes */
12136     VariantClass v = gameInfo.variant;
12137     ClearGameInfo(&gameInfo);
12138     gameInfo.variant = v;
12139
12140     switch (gameMode) {
12141       case MachinePlaysWhite:
12142         gameInfo.event = StrSave( appData.pgnEventHeader );
12143         gameInfo.site = StrSave(HostName());
12144         gameInfo.date = PGNDate();
12145         gameInfo.round = StrSave("-");
12146         gameInfo.white = StrSave(first.tidy);
12147         gameInfo.black = StrSave(UserName());
12148         gameInfo.timeControl = TimeControlTagValue();
12149         break;
12150
12151       case MachinePlaysBlack:
12152         gameInfo.event = StrSave( appData.pgnEventHeader );
12153         gameInfo.site = StrSave(HostName());
12154         gameInfo.date = PGNDate();
12155         gameInfo.round = StrSave("-");
12156         gameInfo.white = StrSave(UserName());
12157         gameInfo.black = StrSave(first.tidy);
12158         gameInfo.timeControl = TimeControlTagValue();
12159         break;
12160
12161       case TwoMachinesPlay:
12162         gameInfo.event = StrSave( appData.pgnEventHeader );
12163         gameInfo.site = StrSave(HostName());
12164         gameInfo.date = PGNDate();
12165         if (matchGame > 0) {
12166             char buf[MSG_SIZ];
12167             sprintf(buf, "%d", matchGame);
12168             gameInfo.round = StrSave(buf);
12169         } else {
12170             gameInfo.round = StrSave("-");
12171         }
12172         if (first.twoMachinesColor[0] == 'w') {
12173             gameInfo.white = StrSave(first.tidy);
12174             gameInfo.black = StrSave(second.tidy);
12175         } else {
12176             gameInfo.white = StrSave(second.tidy);
12177             gameInfo.black = StrSave(first.tidy);
12178         }
12179         gameInfo.timeControl = TimeControlTagValue();
12180         break;
12181
12182       case EditGame:
12183         gameInfo.event = StrSave("Edited game");
12184         gameInfo.site = StrSave(HostName());
12185         gameInfo.date = PGNDate();
12186         gameInfo.round = StrSave("-");
12187         gameInfo.white = StrSave("-");
12188         gameInfo.black = StrSave("-");
12189         break;
12190
12191       case EditPosition:
12192         gameInfo.event = StrSave("Edited position");
12193         gameInfo.site = StrSave(HostName());
12194         gameInfo.date = PGNDate();
12195         gameInfo.round = StrSave("-");
12196         gameInfo.white = StrSave("-");
12197         gameInfo.black = StrSave("-");
12198         break;
12199
12200       case IcsPlayingWhite:
12201       case IcsPlayingBlack:
12202       case IcsObserving:
12203       case IcsExamining:
12204         break;
12205
12206       case PlayFromGameFile:
12207         gameInfo.event = StrSave("Game from non-PGN file");
12208         gameInfo.site = StrSave(HostName());
12209         gameInfo.date = PGNDate();
12210         gameInfo.round = StrSave("-");
12211         gameInfo.white = StrSave("?");
12212         gameInfo.black = StrSave("?");
12213         break;
12214
12215       default:
12216         break;
12217     }
12218 }
12219
12220 void
12221 ReplaceComment(index, text)
12222      int index;
12223      char *text;
12224 {
12225     int len;
12226
12227     while (*text == '\n') text++;
12228     len = strlen(text);
12229     while (len > 0 && text[len - 1] == '\n') len--;
12230
12231     if (commentList[index] != NULL)
12232       free(commentList[index]);
12233
12234     if (len == 0) {
12235         commentList[index] = NULL;
12236         return;
12237     }
12238     commentList[index] = (char *) malloc(len + 2);
12239     strncpy(commentList[index], text, len);
12240     commentList[index][len] = '\n';
12241     commentList[index][len + 1] = NULLCHAR;
12242 }
12243
12244 void
12245 CrushCRs(text)
12246      char *text;
12247 {
12248   char *p = text;
12249   char *q = text;
12250   char ch;
12251
12252   do {
12253     ch = *p++;
12254     if (ch == '\r') continue;
12255     *q++ = ch;
12256   } while (ch != '\0');
12257 }
12258
12259 void
12260 AppendComment(index, text)
12261      int index;
12262      char *text;
12263 {
12264     int oldlen, len;
12265     char *old;
12266
12267     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12268
12269     CrushCRs(text);
12270     while (*text == '\n') text++;
12271     len = strlen(text);
12272     while (len > 0 && text[len - 1] == '\n') len--;
12273
12274     if (len == 0) return;
12275
12276     if (commentList[index] != NULL) {
12277         old = commentList[index];
12278         oldlen = strlen(old);
12279         commentList[index] = (char *) malloc(oldlen + len + 2);
12280         strcpy(commentList[index], old);
12281         free(old);
12282         strncpy(&commentList[index][oldlen], text, len);
12283         commentList[index][oldlen + len] = '\n';
12284         commentList[index][oldlen + len + 1] = NULLCHAR;
12285     } else {
12286         commentList[index] = (char *) malloc(len + 2);
12287         strncpy(commentList[index], text, len);
12288         commentList[index][len] = '\n';
12289         commentList[index][len + 1] = NULLCHAR;
12290     }
12291 }
12292
12293 static char * FindStr( char * text, char * sub_text )
12294 {
12295     char * result = strstr( text, sub_text );
12296
12297     if( result != NULL ) {
12298         result += strlen( sub_text );
12299     }
12300
12301     return result;
12302 }
12303
12304 /* [AS] Try to extract PV info from PGN comment */
12305 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12306 char *GetInfoFromComment( int index, char * text )
12307 {
12308     char * sep = text;
12309
12310     if( text != NULL && index > 0 ) {
12311         int score = 0;
12312         int depth = 0;
12313         int time = -1, sec = 0, deci;
12314         char * s_eval = FindStr( text, "[%eval " );
12315         char * s_emt = FindStr( text, "[%emt " );
12316
12317         if( s_eval != NULL || s_emt != NULL ) {
12318             /* New style */
12319             char delim;
12320
12321             if( s_eval != NULL ) {
12322                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12323                     return text;
12324                 }
12325
12326                 if( delim != ']' ) {
12327                     return text;
12328                 }
12329             }
12330
12331             if( s_emt != NULL ) {
12332             }
12333         }
12334         else {
12335             /* We expect something like: [+|-]nnn.nn/dd */
12336             int score_lo = 0;
12337
12338             sep = strchr( text, '/' );
12339             if( sep == NULL || sep < (text+4) ) {
12340                 return text;
12341             }
12342
12343             time = -1; sec = -1; deci = -1;
12344             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12345                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12346                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12347                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12348                 return text;
12349             }
12350
12351             if( score_lo < 0 || score_lo >= 100 ) {
12352                 return text;
12353             }
12354
12355             if(sec >= 0) time = 600*time + 10*sec; else
12356             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12357
12358             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12359
12360             /* [HGM] PV time: now locate end of PV info */
12361             while( *++sep >= '0' && *sep <= '9'); // strip depth
12362             if(time >= 0)
12363             while( *++sep >= '0' && *sep <= '9'); // strip time
12364             if(sec >= 0)
12365             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12366             if(deci >= 0)
12367             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12368             while(*sep == ' ') sep++;
12369         }
12370
12371         if( depth <= 0 ) {
12372             return text;
12373         }
12374
12375         if( time < 0 ) {
12376             time = -1;
12377         }
12378
12379         pvInfoList[index-1].depth = depth;
12380         pvInfoList[index-1].score = score;
12381         pvInfoList[index-1].time  = 10*time; // centi-sec
12382     }
12383     return sep;
12384 }
12385
12386 void
12387 SendToProgram(message, cps)
12388      char *message;
12389      ChessProgramState *cps;
12390 {
12391     int count, outCount, error;
12392     char buf[MSG_SIZ];
12393
12394     if (cps->pr == NULL) return;
12395     Attention(cps);
12396     
12397     if (appData.debugMode) {
12398         TimeMark now;
12399         GetTimeMark(&now);
12400         fprintf(debugFP, "%ld >%-6s: %s", 
12401                 SubtractTimeMarks(&now, &programStartTime),
12402                 cps->which, message);
12403     }
12404     
12405     count = strlen(message);
12406     outCount = OutputToProcess(cps->pr, message, count, &error);
12407     if (outCount < count && !exiting 
12408                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12409         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12410         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12411             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12412                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12413                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12414             } else {
12415                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12416             }
12417             gameInfo.resultDetails = StrSave(buf);
12418         }
12419         DisplayFatalError(buf, error, 1);
12420     }
12421 }
12422
12423 void
12424 ReceiveFromProgram(isr, closure, message, count, error)
12425      InputSourceRef isr;
12426      VOIDSTAR closure;
12427      char *message;
12428      int count;
12429      int error;
12430 {
12431     char *end_str;
12432     char buf[MSG_SIZ];
12433     ChessProgramState *cps = (ChessProgramState *)closure;
12434
12435     if (isr != cps->isr) return; /* Killed intentionally */
12436     if (count <= 0) {
12437         if (count == 0) {
12438             sprintf(buf,
12439                     _("Error: %s chess program (%s) exited unexpectedly"),
12440                     cps->which, cps->program);
12441         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12442                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12443                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12444                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12445                 } else {
12446                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12447                 }
12448                 gameInfo.resultDetails = StrSave(buf);
12449             }
12450             RemoveInputSource(cps->isr);
12451             DisplayFatalError(buf, 0, 1);
12452         } else {
12453             sprintf(buf,
12454                     _("Error reading from %s chess program (%s)"),
12455                     cps->which, cps->program);
12456             RemoveInputSource(cps->isr);
12457
12458             /* [AS] Program is misbehaving badly... kill it */
12459             if( count == -2 ) {
12460                 DestroyChildProcess( cps->pr, 9 );
12461                 cps->pr = NoProc;
12462             }
12463
12464             DisplayFatalError(buf, error, 1);
12465         }
12466         return;
12467     }
12468     
12469     if ((end_str = strchr(message, '\r')) != NULL)
12470       *end_str = NULLCHAR;
12471     if ((end_str = strchr(message, '\n')) != NULL)
12472       *end_str = NULLCHAR;
12473     
12474     if (appData.debugMode) {
12475         TimeMark now; int print = 1;
12476         char *quote = ""; char c; int i;
12477
12478         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12479                 char start = message[0];
12480                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12481                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12482                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12483                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12484                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12485                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12486                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12487                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12488                         { quote = "# "; print = (appData.engineComments == 2); }
12489                 message[0] = start; // restore original message
12490         }
12491         if(print) {
12492                 GetTimeMark(&now);
12493                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12494                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12495                         quote,
12496                         message);
12497         }
12498     }
12499
12500     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12501     if (appData.icsEngineAnalyze) {
12502         if (strstr(message, "whisper") != NULL ||
12503              strstr(message, "kibitz") != NULL || 
12504             strstr(message, "tellics") != NULL) return;
12505     }
12506
12507     HandleMachineMove(message, cps);
12508 }
12509
12510
12511 void
12512 SendTimeControl(cps, mps, tc, inc, sd, st)
12513      ChessProgramState *cps;
12514      int mps, inc, sd, st;
12515      long tc;
12516 {
12517     char buf[MSG_SIZ];
12518     int seconds;
12519
12520     if( timeControl_2 > 0 ) {
12521         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12522             tc = timeControl_2;
12523         }
12524     }
12525     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12526     inc /= cps->timeOdds;
12527     st  /= cps->timeOdds;
12528
12529     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12530
12531     if (st > 0) {
12532       /* Set exact time per move, normally using st command */
12533       if (cps->stKludge) {
12534         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12535         seconds = st % 60;
12536         if (seconds == 0) {
12537           sprintf(buf, "level 1 %d\n", st/60);
12538         } else {
12539           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12540         }
12541       } else {
12542         sprintf(buf, "st %d\n", st);
12543       }
12544     } else {
12545       /* Set conventional or incremental time control, using level command */
12546       if (seconds == 0) {
12547         /* Note old gnuchess bug -- minutes:seconds used to not work.
12548            Fixed in later versions, but still avoid :seconds
12549            when seconds is 0. */
12550         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12551       } else {
12552         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12553                 seconds, inc/1000);
12554       }
12555     }
12556     SendToProgram(buf, cps);
12557
12558     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12559     /* Orthogonally, limit search to given depth */
12560     if (sd > 0) {
12561       if (cps->sdKludge) {
12562         sprintf(buf, "depth\n%d\n", sd);
12563       } else {
12564         sprintf(buf, "sd %d\n", sd);
12565       }
12566       SendToProgram(buf, cps);
12567     }
12568
12569     if(cps->nps > 0) { /* [HGM] nps */
12570         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12571         else {
12572                 sprintf(buf, "nps %d\n", cps->nps);
12573               SendToProgram(buf, cps);
12574         }
12575     }
12576 }
12577
12578 ChessProgramState *WhitePlayer()
12579 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12580 {
12581     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12582        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12583         return &second;
12584     return &first;
12585 }
12586
12587 void
12588 SendTimeRemaining(cps, machineWhite)
12589      ChessProgramState *cps;
12590      int /*boolean*/ machineWhite;
12591 {
12592     char message[MSG_SIZ];
12593     long time, otime;
12594
12595     /* Note: this routine must be called when the clocks are stopped
12596        or when they have *just* been set or switched; otherwise
12597        it will be off by the time since the current tick started.
12598     */
12599     if (machineWhite) {
12600         time = whiteTimeRemaining / 10;
12601         otime = blackTimeRemaining / 10;
12602     } else {
12603         time = blackTimeRemaining / 10;
12604         otime = whiteTimeRemaining / 10;
12605     }
12606     /* [HGM] translate opponent's time by time-odds factor */
12607     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12608     if (appData.debugMode) {
12609         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12610     }
12611
12612     if (time <= 0) time = 1;
12613     if (otime <= 0) otime = 1;
12614     
12615     sprintf(message, "time %ld\n", time);
12616     SendToProgram(message, cps);
12617
12618     sprintf(message, "otim %ld\n", otime);
12619     SendToProgram(message, cps);
12620 }
12621
12622 int
12623 BoolFeature(p, name, loc, cps)
12624      char **p;
12625      char *name;
12626      int *loc;
12627      ChessProgramState *cps;
12628 {
12629   char buf[MSG_SIZ];
12630   int len = strlen(name);
12631   int val;
12632   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12633     (*p) += len + 1;
12634     sscanf(*p, "%d", &val);
12635     *loc = (val != 0);
12636     while (**p && **p != ' ') (*p)++;
12637     sprintf(buf, "accepted %s\n", name);
12638     SendToProgram(buf, cps);
12639     return TRUE;
12640   }
12641   return FALSE;
12642 }
12643
12644 int
12645 IntFeature(p, name, loc, cps)
12646      char **p;
12647      char *name;
12648      int *loc;
12649      ChessProgramState *cps;
12650 {
12651   char buf[MSG_SIZ];
12652   int len = strlen(name);
12653   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12654     (*p) += len + 1;
12655     sscanf(*p, "%d", loc);
12656     while (**p && **p != ' ') (*p)++;
12657     sprintf(buf, "accepted %s\n", name);
12658     SendToProgram(buf, cps);
12659     return TRUE;
12660   }
12661   return FALSE;
12662 }
12663
12664 int
12665 StringFeature(p, name, loc, cps)
12666      char **p;
12667      char *name;
12668      char loc[];
12669      ChessProgramState *cps;
12670 {
12671   char buf[MSG_SIZ];
12672   int len = strlen(name);
12673   if (strncmp((*p), name, len) == 0
12674       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12675     (*p) += len + 2;
12676     sscanf(*p, "%[^\"]", loc);
12677     while (**p && **p != '\"') (*p)++;
12678     if (**p == '\"') (*p)++;
12679     sprintf(buf, "accepted %s\n", name);
12680     SendToProgram(buf, cps);
12681     return TRUE;
12682   }
12683   return FALSE;
12684 }
12685
12686 int 
12687 ParseOption(Option *opt, ChessProgramState *cps)
12688 // [HGM] options: process the string that defines an engine option, and determine
12689 // name, type, default value, and allowed value range
12690 {
12691         char *p, *q, buf[MSG_SIZ];
12692         int n, min = (-1)<<31, max = 1<<31, def;
12693
12694         if(p = strstr(opt->name, " -spin ")) {
12695             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12696             if(max < min) max = min; // enforce consistency
12697             if(def < min) def = min;
12698             if(def > max) def = max;
12699             opt->value = def;
12700             opt->min = min;
12701             opt->max = max;
12702             opt->type = Spin;
12703         } else if((p = strstr(opt->name, " -slider "))) {
12704             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12705             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12706             if(max < min) max = min; // enforce consistency
12707             if(def < min) def = min;
12708             if(def > max) def = max;
12709             opt->value = def;
12710             opt->min = min;
12711             opt->max = max;
12712             opt->type = Spin; // Slider;
12713         } else if((p = strstr(opt->name, " -string "))) {
12714             opt->textValue = p+9;
12715             opt->type = TextBox;
12716         } else if((p = strstr(opt->name, " -file "))) {
12717             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12718             opt->textValue = p+7;
12719             opt->type = TextBox; // FileName;
12720         } else if((p = strstr(opt->name, " -path "))) {
12721             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12722             opt->textValue = p+7;
12723             opt->type = TextBox; // PathName;
12724         } else if(p = strstr(opt->name, " -check ")) {
12725             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12726             opt->value = (def != 0);
12727             opt->type = CheckBox;
12728         } else if(p = strstr(opt->name, " -combo ")) {
12729             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12730             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12731             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12732             opt->value = n = 0;
12733             while(q = StrStr(q, " /// ")) {
12734                 n++; *q = 0;    // count choices, and null-terminate each of them
12735                 q += 5;
12736                 if(*q == '*') { // remember default, which is marked with * prefix
12737                     q++;
12738                     opt->value = n;
12739                 }
12740                 cps->comboList[cps->comboCnt++] = q;
12741             }
12742             cps->comboList[cps->comboCnt++] = NULL;
12743             opt->max = n + 1;
12744             opt->type = ComboBox;
12745         } else if(p = strstr(opt->name, " -button")) {
12746             opt->type = Button;
12747         } else if(p = strstr(opt->name, " -save")) {
12748             opt->type = SaveButton;
12749         } else return FALSE;
12750         *p = 0; // terminate option name
12751         // now look if the command-line options define a setting for this engine option.
12752         if(cps->optionSettings && cps->optionSettings[0])
12753             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12754         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12755                 sprintf(buf, "option %s", p);
12756                 if(p = strstr(buf, ",")) *p = 0;
12757                 strcat(buf, "\n");
12758                 SendToProgram(buf, cps);
12759         }
12760         return TRUE;
12761 }
12762
12763 void
12764 FeatureDone(cps, val)
12765      ChessProgramState* cps;
12766      int val;
12767 {
12768   DelayedEventCallback cb = GetDelayedEvent();
12769   if ((cb == InitBackEnd3 && cps == &first) ||
12770       (cb == TwoMachinesEventIfReady && cps == &second)) {
12771     CancelDelayedEvent();
12772     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12773   }
12774   cps->initDone = val;
12775 }
12776
12777 /* Parse feature command from engine */
12778 void
12779 ParseFeatures(args, cps)
12780      char* args;
12781      ChessProgramState *cps;  
12782 {
12783   char *p = args;
12784   char *q;
12785   int val;
12786   char buf[MSG_SIZ];
12787
12788   for (;;) {
12789     while (*p == ' ') p++;
12790     if (*p == NULLCHAR) return;
12791
12792     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12793     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12794     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12795     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12796     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12797     if (BoolFeature(&p, "reuse", &val, cps)) {
12798       /* Engine can disable reuse, but can't enable it if user said no */
12799       if (!val) cps->reuse = FALSE;
12800       continue;
12801     }
12802     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12803     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12804       if (gameMode == TwoMachinesPlay) {
12805         DisplayTwoMachinesTitle();
12806       } else {
12807         DisplayTitle("");
12808       }
12809       continue;
12810     }
12811     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12812     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12813     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12814     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12815     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12816     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12817     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12818     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12819     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12820     if (IntFeature(&p, "done", &val, cps)) {
12821       FeatureDone(cps, val);
12822       continue;
12823     }
12824     /* Added by Tord: */
12825     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12826     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12827     /* End of additions by Tord */
12828
12829     /* [HGM] added features: */
12830     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12831     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12832     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12833     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12834     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12835     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12836     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12837         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12838             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12839             SendToProgram(buf, cps);
12840             continue;
12841         }
12842         if(cps->nrOptions >= MAX_OPTIONS) {
12843             cps->nrOptions--;
12844             sprintf(buf, "%s engine has too many options\n", cps->which);
12845             DisplayError(buf, 0);
12846         }
12847         continue;
12848     }
12849     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12850     /* End of additions by HGM */
12851
12852     /* unknown feature: complain and skip */
12853     q = p;
12854     while (*q && *q != '=') q++;
12855     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12856     SendToProgram(buf, cps);
12857     p = q;
12858     if (*p == '=') {
12859       p++;
12860       if (*p == '\"') {
12861         p++;
12862         while (*p && *p != '\"') p++;
12863         if (*p == '\"') p++;
12864       } else {
12865         while (*p && *p != ' ') p++;
12866       }
12867     }
12868   }
12869
12870 }
12871
12872 void
12873 PeriodicUpdatesEvent(newState)
12874      int newState;
12875 {
12876     if (newState == appData.periodicUpdates)
12877       return;
12878
12879     appData.periodicUpdates=newState;
12880
12881     /* Display type changes, so update it now */
12882 //    DisplayAnalysis();
12883
12884     /* Get the ball rolling again... */
12885     if (newState) {
12886         AnalysisPeriodicEvent(1);
12887         StartAnalysisClock();
12888     }
12889 }
12890
12891 void
12892 PonderNextMoveEvent(newState)
12893      int newState;
12894 {
12895     if (newState == appData.ponderNextMove) return;
12896     if (gameMode == EditPosition) EditPositionDone(TRUE);
12897     if (newState) {
12898         SendToProgram("hard\n", &first);
12899         if (gameMode == TwoMachinesPlay) {
12900             SendToProgram("hard\n", &second);
12901         }
12902     } else {
12903         SendToProgram("easy\n", &first);
12904         thinkOutput[0] = NULLCHAR;
12905         if (gameMode == TwoMachinesPlay) {
12906             SendToProgram("easy\n", &second);
12907         }
12908     }
12909     appData.ponderNextMove = newState;
12910 }
12911
12912 void
12913 NewSettingEvent(option, command, value)
12914      char *command;
12915      int option, value;
12916 {
12917     char buf[MSG_SIZ];
12918
12919     if (gameMode == EditPosition) EditPositionDone(TRUE);
12920     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12921     SendToProgram(buf, &first);
12922     if (gameMode == TwoMachinesPlay) {
12923         SendToProgram(buf, &second);
12924     }
12925 }
12926
12927 void
12928 ShowThinkingEvent()
12929 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12930 {
12931     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12932     int newState = appData.showThinking
12933         // [HGM] thinking: other features now need thinking output as well
12934         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12935     
12936     if (oldState == newState) return;
12937     oldState = newState;
12938     if (gameMode == EditPosition) EditPositionDone(TRUE);
12939     if (oldState) {
12940         SendToProgram("post\n", &first);
12941         if (gameMode == TwoMachinesPlay) {
12942             SendToProgram("post\n", &second);
12943         }
12944     } else {
12945         SendToProgram("nopost\n", &first);
12946         thinkOutput[0] = NULLCHAR;
12947         if (gameMode == TwoMachinesPlay) {
12948             SendToProgram("nopost\n", &second);
12949         }
12950     }
12951 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12952 }
12953
12954 void
12955 AskQuestionEvent(title, question, replyPrefix, which)
12956      char *title; char *question; char *replyPrefix; char *which;
12957 {
12958   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12959   if (pr == NoProc) return;
12960   AskQuestion(title, question, replyPrefix, pr);
12961 }
12962
12963 void
12964 DisplayMove(moveNumber)
12965      int moveNumber;
12966 {
12967     char message[MSG_SIZ];
12968     char res[MSG_SIZ];
12969     char cpThinkOutput[MSG_SIZ];
12970
12971     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12972     
12973     if (moveNumber == forwardMostMove - 1 || 
12974         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12975
12976         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12977
12978         if (strchr(cpThinkOutput, '\n')) {
12979             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12980         }
12981     } else {
12982         *cpThinkOutput = NULLCHAR;
12983     }
12984
12985     /* [AS] Hide thinking from human user */
12986     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12987         *cpThinkOutput = NULLCHAR;
12988         if( thinkOutput[0] != NULLCHAR ) {
12989             int i;
12990
12991             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12992                 cpThinkOutput[i] = '.';
12993             }
12994             cpThinkOutput[i] = NULLCHAR;
12995             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12996         }
12997     }
12998
12999     if (moveNumber == forwardMostMove - 1 &&
13000         gameInfo.resultDetails != NULL) {
13001         if (gameInfo.resultDetails[0] == NULLCHAR) {
13002             sprintf(res, " %s", PGNResult(gameInfo.result));
13003         } else {
13004             sprintf(res, " {%s} %s",
13005                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13006         }
13007     } else {
13008         res[0] = NULLCHAR;
13009     }
13010
13011     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13012         DisplayMessage(res, cpThinkOutput);
13013     } else {
13014         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13015                 WhiteOnMove(moveNumber) ? " " : ".. ",
13016                 parseList[moveNumber], res);
13017         DisplayMessage(message, cpThinkOutput);
13018     }
13019 }
13020
13021 void
13022 DisplayComment(moveNumber, text)
13023      int moveNumber;
13024      char *text;
13025 {
13026     char title[MSG_SIZ];
13027     char buf[8000]; // comment can be long!
13028     int score, depth;
13029     
13030     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13031       strcpy(title, "Comment");
13032     } else {
13033       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13034               WhiteOnMove(moveNumber) ? " " : ".. ",
13035               parseList[moveNumber]);
13036     }
13037     // [HGM] PV info: display PV info together with (or as) comment
13038     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13039       if(text == NULL) text = "";                                           
13040       score = pvInfoList[moveNumber].score;
13041       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13042               depth, (pvInfoList[moveNumber].time+50)/100, text);
13043       text = buf;
13044     }
13045     if (text != NULL && (appData.autoDisplayComment || commentUp))
13046         CommentPopUp(title, text);
13047 }
13048
13049 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13050  * might be busy thinking or pondering.  It can be omitted if your
13051  * gnuchess is configured to stop thinking immediately on any user
13052  * input.  However, that gnuchess feature depends on the FIONREAD
13053  * ioctl, which does not work properly on some flavors of Unix.
13054  */
13055 void
13056 Attention(cps)
13057      ChessProgramState *cps;
13058 {
13059 #if ATTENTION
13060     if (!cps->useSigint) return;
13061     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13062     switch (gameMode) {
13063       case MachinePlaysWhite:
13064       case MachinePlaysBlack:
13065       case TwoMachinesPlay:
13066       case IcsPlayingWhite:
13067       case IcsPlayingBlack:
13068       case AnalyzeMode:
13069       case AnalyzeFile:
13070         /* Skip if we know it isn't thinking */
13071         if (!cps->maybeThinking) return;
13072         if (appData.debugMode)
13073           fprintf(debugFP, "Interrupting %s\n", cps->which);
13074         InterruptChildProcess(cps->pr);
13075         cps->maybeThinking = FALSE;
13076         break;
13077       default:
13078         break;
13079     }
13080 #endif /*ATTENTION*/
13081 }
13082
13083 int
13084 CheckFlags()
13085 {
13086     if (whiteTimeRemaining <= 0) {
13087         if (!whiteFlag) {
13088             whiteFlag = TRUE;
13089             if (appData.icsActive) {
13090                 if (appData.autoCallFlag &&
13091                     gameMode == IcsPlayingBlack && !blackFlag) {
13092                   SendToICS(ics_prefix);
13093                   SendToICS("flag\n");
13094                 }
13095             } else {
13096                 if (blackFlag) {
13097                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13098                 } else {
13099                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13100                     if (appData.autoCallFlag) {
13101                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13102                         return TRUE;
13103                     }
13104                 }
13105             }
13106         }
13107     }
13108     if (blackTimeRemaining <= 0) {
13109         if (!blackFlag) {
13110             blackFlag = TRUE;
13111             if (appData.icsActive) {
13112                 if (appData.autoCallFlag &&
13113                     gameMode == IcsPlayingWhite && !whiteFlag) {
13114                   SendToICS(ics_prefix);
13115                   SendToICS("flag\n");
13116                 }
13117             } else {
13118                 if (whiteFlag) {
13119                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13120                 } else {
13121                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13122                     if (appData.autoCallFlag) {
13123                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13124                         return TRUE;
13125                     }
13126                 }
13127             }
13128         }
13129     }
13130     return FALSE;
13131 }
13132
13133 void
13134 CheckTimeControl()
13135 {
13136     if (!appData.clockMode || appData.icsActive ||
13137         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13138
13139     /*
13140      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13141      */
13142     if ( !WhiteOnMove(forwardMostMove) )
13143         /* White made time control */
13144         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13145         /* [HGM] time odds: correct new time quota for time odds! */
13146                                             / WhitePlayer()->timeOdds;
13147       else
13148         /* Black made time control */
13149         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13150                                             / WhitePlayer()->other->timeOdds;
13151 }
13152
13153 void
13154 DisplayBothClocks()
13155 {
13156     int wom = gameMode == EditPosition ?
13157       !blackPlaysFirst : WhiteOnMove(currentMove);
13158     DisplayWhiteClock(whiteTimeRemaining, wom);
13159     DisplayBlackClock(blackTimeRemaining, !wom);
13160 }
13161
13162
13163 /* Timekeeping seems to be a portability nightmare.  I think everyone
13164    has ftime(), but I'm really not sure, so I'm including some ifdefs
13165    to use other calls if you don't.  Clocks will be less accurate if
13166    you have neither ftime nor gettimeofday.
13167 */
13168
13169 /* VS 2008 requires the #include outside of the function */
13170 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13171 #include <sys/timeb.h>
13172 #endif
13173
13174 /* Get the current time as a TimeMark */
13175 void
13176 GetTimeMark(tm)
13177      TimeMark *tm;
13178 {
13179 #if HAVE_GETTIMEOFDAY
13180
13181     struct timeval timeVal;
13182     struct timezone timeZone;
13183
13184     gettimeofday(&timeVal, &timeZone);
13185     tm->sec = (long) timeVal.tv_sec; 
13186     tm->ms = (int) (timeVal.tv_usec / 1000L);
13187
13188 #else /*!HAVE_GETTIMEOFDAY*/
13189 #if HAVE_FTIME
13190
13191 // include <sys/timeb.h> / moved to just above start of function
13192     struct timeb timeB;
13193
13194     ftime(&timeB);
13195     tm->sec = (long) timeB.time;
13196     tm->ms = (int) timeB.millitm;
13197
13198 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13199     tm->sec = (long) time(NULL);
13200     tm->ms = 0;
13201 #endif
13202 #endif
13203 }
13204
13205 /* Return the difference in milliseconds between two
13206    time marks.  We assume the difference will fit in a long!
13207 */
13208 long
13209 SubtractTimeMarks(tm2, tm1)
13210      TimeMark *tm2, *tm1;
13211 {
13212     return 1000L*(tm2->sec - tm1->sec) +
13213            (long) (tm2->ms - tm1->ms);
13214 }
13215
13216
13217 /*
13218  * Code to manage the game clocks.
13219  *
13220  * In tournament play, black starts the clock and then white makes a move.
13221  * We give the human user a slight advantage if he is playing white---the
13222  * clocks don't run until he makes his first move, so it takes zero time.
13223  * Also, we don't account for network lag, so we could get out of sync
13224  * with GNU Chess's clock -- but then, referees are always right.  
13225  */
13226
13227 static TimeMark tickStartTM;
13228 static long intendedTickLength;
13229
13230 long
13231 NextTickLength(timeRemaining)
13232      long timeRemaining;
13233 {
13234     long nominalTickLength, nextTickLength;
13235
13236     if (timeRemaining > 0L && timeRemaining <= 10000L)
13237       nominalTickLength = 100L;
13238     else
13239       nominalTickLength = 1000L;
13240     nextTickLength = timeRemaining % nominalTickLength;
13241     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13242
13243     return nextTickLength;
13244 }
13245
13246 /* Adjust clock one minute up or down */
13247 void
13248 AdjustClock(Boolean which, int dir)
13249 {
13250     if(which) blackTimeRemaining += 60000*dir;
13251     else      whiteTimeRemaining += 60000*dir;
13252     DisplayBothClocks();
13253 }
13254
13255 /* Stop clocks and reset to a fresh time control */
13256 void
13257 ResetClocks() 
13258 {
13259     (void) StopClockTimer();
13260     if (appData.icsActive) {
13261         whiteTimeRemaining = blackTimeRemaining = 0;
13262     } else { /* [HGM] correct new time quote for time odds */
13263         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13264         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13265     }
13266     if (whiteFlag || blackFlag) {
13267         DisplayTitle("");
13268         whiteFlag = blackFlag = FALSE;
13269     }
13270     DisplayBothClocks();
13271 }
13272
13273 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13274
13275 /* Decrement running clock by amount of time that has passed */
13276 void
13277 DecrementClocks()
13278 {
13279     long timeRemaining;
13280     long lastTickLength, fudge;
13281     TimeMark now;
13282
13283     if (!appData.clockMode) return;
13284     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13285         
13286     GetTimeMark(&now);
13287
13288     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13289
13290     /* Fudge if we woke up a little too soon */
13291     fudge = intendedTickLength - lastTickLength;
13292     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13293
13294     if (WhiteOnMove(forwardMostMove)) {
13295         if(whiteNPS >= 0) lastTickLength = 0;
13296         timeRemaining = whiteTimeRemaining -= lastTickLength;
13297         DisplayWhiteClock(whiteTimeRemaining - fudge,
13298                           WhiteOnMove(currentMove));
13299     } else {
13300         if(blackNPS >= 0) lastTickLength = 0;
13301         timeRemaining = blackTimeRemaining -= lastTickLength;
13302         DisplayBlackClock(blackTimeRemaining - fudge,
13303                           !WhiteOnMove(currentMove));
13304     }
13305
13306     if (CheckFlags()) return;
13307         
13308     tickStartTM = now;
13309     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13310     StartClockTimer(intendedTickLength);
13311
13312     /* if the time remaining has fallen below the alarm threshold, sound the
13313      * alarm. if the alarm has sounded and (due to a takeback or time control
13314      * with increment) the time remaining has increased to a level above the
13315      * threshold, reset the alarm so it can sound again. 
13316      */
13317     
13318     if (appData.icsActive && appData.icsAlarm) {
13319
13320         /* make sure we are dealing with the user's clock */
13321         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13322                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13323            )) return;
13324
13325         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13326             alarmSounded = FALSE;
13327         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13328             PlayAlarmSound();
13329             alarmSounded = TRUE;
13330         }
13331     }
13332 }
13333
13334
13335 /* A player has just moved, so stop the previously running
13336    clock and (if in clock mode) start the other one.
13337    We redisplay both clocks in case we're in ICS mode, because
13338    ICS gives us an update to both clocks after every move.
13339    Note that this routine is called *after* forwardMostMove
13340    is updated, so the last fractional tick must be subtracted
13341    from the color that is *not* on move now.
13342 */
13343 void
13344 SwitchClocks()
13345 {
13346     long lastTickLength;
13347     TimeMark now;
13348     int flagged = FALSE;
13349
13350     GetTimeMark(&now);
13351
13352     if (StopClockTimer() && appData.clockMode) {
13353         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13354         if (WhiteOnMove(forwardMostMove)) {
13355             if(blackNPS >= 0) lastTickLength = 0;
13356             blackTimeRemaining -= lastTickLength;
13357            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13358 //         if(pvInfoList[forwardMostMove-1].time == -1)
13359                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13360                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13361         } else {
13362            if(whiteNPS >= 0) lastTickLength = 0;
13363            whiteTimeRemaining -= lastTickLength;
13364            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13365 //         if(pvInfoList[forwardMostMove-1].time == -1)
13366                  pvInfoList[forwardMostMove-1].time = 
13367                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13368         }
13369         flagged = CheckFlags();
13370     }
13371     CheckTimeControl();
13372
13373     if (flagged || !appData.clockMode) return;
13374
13375     switch (gameMode) {
13376       case MachinePlaysBlack:
13377       case MachinePlaysWhite:
13378       case BeginningOfGame:
13379         if (pausing) return;
13380         break;
13381
13382       case EditGame:
13383       case PlayFromGameFile:
13384       case IcsExamining:
13385         return;
13386
13387       default:
13388         break;
13389     }
13390
13391     tickStartTM = now;
13392     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13393       whiteTimeRemaining : blackTimeRemaining);
13394     StartClockTimer(intendedTickLength);
13395 }
13396         
13397
13398 /* Stop both clocks */
13399 void
13400 StopClocks()
13401 {       
13402     long lastTickLength;
13403     TimeMark now;
13404
13405     if (!StopClockTimer()) return;
13406     if (!appData.clockMode) return;
13407
13408     GetTimeMark(&now);
13409
13410     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411     if (WhiteOnMove(forwardMostMove)) {
13412         if(whiteNPS >= 0) lastTickLength = 0;
13413         whiteTimeRemaining -= lastTickLength;
13414         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13415     } else {
13416         if(blackNPS >= 0) lastTickLength = 0;
13417         blackTimeRemaining -= lastTickLength;
13418         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13419     }
13420     CheckFlags();
13421 }
13422         
13423 /* Start clock of player on move.  Time may have been reset, so
13424    if clock is already running, stop and restart it. */
13425 void
13426 StartClocks()
13427 {
13428     (void) StopClockTimer(); /* in case it was running already */
13429     DisplayBothClocks();
13430     if (CheckFlags()) return;
13431
13432     if (!appData.clockMode) return;
13433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13434
13435     GetTimeMark(&tickStartTM);
13436     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13437       whiteTimeRemaining : blackTimeRemaining);
13438
13439    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13440     whiteNPS = blackNPS = -1; 
13441     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13442        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13443         whiteNPS = first.nps;
13444     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13445        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13446         blackNPS = first.nps;
13447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13448         whiteNPS = second.nps;
13449     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13450         blackNPS = second.nps;
13451     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13452
13453     StartClockTimer(intendedTickLength);
13454 }
13455
13456 char *
13457 TimeString(ms)
13458      long ms;
13459 {
13460     long second, minute, hour, day;
13461     char *sign = "";
13462     static char buf[32];
13463     
13464     if (ms > 0 && ms <= 9900) {
13465       /* convert milliseconds to tenths, rounding up */
13466       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13467
13468       sprintf(buf, " %03.1f ", tenths/10.0);
13469       return buf;
13470     }
13471
13472     /* convert milliseconds to seconds, rounding up */
13473     /* use floating point to avoid strangeness of integer division
13474        with negative dividends on many machines */
13475     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13476
13477     if (second < 0) {
13478         sign = "-";
13479         second = -second;
13480     }
13481     
13482     day = second / (60 * 60 * 24);
13483     second = second % (60 * 60 * 24);
13484     hour = second / (60 * 60);
13485     second = second % (60 * 60);
13486     minute = second / 60;
13487     second = second % 60;
13488     
13489     if (day > 0)
13490       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13491               sign, day, hour, minute, second);
13492     else if (hour > 0)
13493       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13494     else
13495       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13496     
13497     return buf;
13498 }
13499
13500
13501 /*
13502  * This is necessary because some C libraries aren't ANSI C compliant yet.
13503  */
13504 char *
13505 StrStr(string, match)
13506      char *string, *match;
13507 {
13508     int i, length;
13509     
13510     length = strlen(match);
13511     
13512     for (i = strlen(string) - length; i >= 0; i--, string++)
13513       if (!strncmp(match, string, length))
13514         return string;
13515     
13516     return NULL;
13517 }
13518
13519 char *
13520 StrCaseStr(string, match)
13521      char *string, *match;
13522 {
13523     int i, j, length;
13524     
13525     length = strlen(match);
13526     
13527     for (i = strlen(string) - length; i >= 0; i--, string++) {
13528         for (j = 0; j < length; j++) {
13529             if (ToLower(match[j]) != ToLower(string[j]))
13530               break;
13531         }
13532         if (j == length) return string;
13533     }
13534
13535     return NULL;
13536 }
13537
13538 #ifndef _amigados
13539 int
13540 StrCaseCmp(s1, s2)
13541      char *s1, *s2;
13542 {
13543     char c1, c2;
13544     
13545     for (;;) {
13546         c1 = ToLower(*s1++);
13547         c2 = ToLower(*s2++);
13548         if (c1 > c2) return 1;
13549         if (c1 < c2) return -1;
13550         if (c1 == NULLCHAR) return 0;
13551     }
13552 }
13553
13554
13555 int
13556 ToLower(c)
13557      int c;
13558 {
13559     return isupper(c) ? tolower(c) : c;
13560 }
13561
13562
13563 int
13564 ToUpper(c)
13565      int c;
13566 {
13567     return islower(c) ? toupper(c) : c;
13568 }
13569 #endif /* !_amigados    */
13570
13571 char *
13572 StrSave(s)
13573      char *s;
13574 {
13575     char *ret;
13576
13577     if ((ret = (char *) malloc(strlen(s) + 1))) {
13578         strcpy(ret, s);
13579     }
13580     return ret;
13581 }
13582
13583 char *
13584 StrSavePtr(s, savePtr)
13585      char *s, **savePtr;
13586 {
13587     if (*savePtr) {
13588         free(*savePtr);
13589     }
13590     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13591         strcpy(*savePtr, s);
13592     }
13593     return(*savePtr);
13594 }
13595
13596 char *
13597 PGNDate()
13598 {
13599     time_t clock;
13600     struct tm *tm;
13601     char buf[MSG_SIZ];
13602
13603     clock = time((time_t *)NULL);
13604     tm = localtime(&clock);
13605     sprintf(buf, "%04d.%02d.%02d",
13606             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13607     return StrSave(buf);
13608 }
13609
13610
13611 char *
13612 PositionToFEN(move, overrideCastling)
13613      int move;
13614      char *overrideCastling;
13615 {
13616     int i, j, fromX, fromY, toX, toY;
13617     int whiteToPlay;
13618     char buf[128];
13619     char *p, *q;
13620     int emptycount;
13621     ChessSquare piece;
13622
13623     whiteToPlay = (gameMode == EditPosition) ?
13624       !blackPlaysFirst : (move % 2 == 0);
13625     p = buf;
13626
13627     /* Piece placement data */
13628     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13629         emptycount = 0;
13630         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13631             if (boards[move][i][j] == EmptySquare) {
13632                 emptycount++;
13633             } else { ChessSquare piece = boards[move][i][j];
13634                 if (emptycount > 0) {
13635                     if(emptycount<10) /* [HGM] can be >= 10 */
13636                         *p++ = '0' + emptycount;
13637                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13638                     emptycount = 0;
13639                 }
13640                 if(PieceToChar(piece) == '+') {
13641                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13642                     *p++ = '+';
13643                     piece = (ChessSquare)(DEMOTED piece);
13644                 } 
13645                 *p++ = PieceToChar(piece);
13646                 if(p[-1] == '~') {
13647                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13648                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13649                     *p++ = '~';
13650                 }
13651             }
13652         }
13653         if (emptycount > 0) {
13654             if(emptycount<10) /* [HGM] can be >= 10 */
13655                 *p++ = '0' + emptycount;
13656             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13657             emptycount = 0;
13658         }
13659         *p++ = '/';
13660     }
13661     *(p - 1) = ' ';
13662
13663     /* [HGM] print Crazyhouse or Shogi holdings */
13664     if( gameInfo.holdingsWidth ) {
13665         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13666         q = p;
13667         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13668             piece = boards[move][i][BOARD_WIDTH-1];
13669             if( piece != EmptySquare )
13670               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13671                   *p++ = PieceToChar(piece);
13672         }
13673         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13674             piece = boards[move][BOARD_HEIGHT-i-1][0];
13675             if( piece != EmptySquare )
13676               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13677                   *p++ = PieceToChar(piece);
13678         }
13679
13680         if( q == p ) *p++ = '-';
13681         *p++ = ']';
13682         *p++ = ' ';
13683     }
13684
13685     /* Active color */
13686     *p++ = whiteToPlay ? 'w' : 'b';
13687     *p++ = ' ';
13688
13689   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13690     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13691   } else {
13692   if(nrCastlingRights) {
13693      q = p;
13694      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13695        /* [HGM] write directly from rights */
13696            if(castlingRights[move][2] >= 0 &&
13697               castlingRights[move][0] >= 0   )
13698                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13699            if(castlingRights[move][2] >= 0 &&
13700               castlingRights[move][1] >= 0   )
13701                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13702            if(castlingRights[move][5] >= 0 &&
13703               castlingRights[move][3] >= 0   )
13704                 *p++ = castlingRights[move][3] + AAA;
13705            if(castlingRights[move][5] >= 0 &&
13706               castlingRights[move][4] >= 0   )
13707                 *p++ = castlingRights[move][4] + AAA;
13708      } else {
13709
13710         /* [HGM] write true castling rights */
13711         if( nrCastlingRights == 6 ) {
13712             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13713                castlingRights[move][2] >= 0  ) *p++ = 'K';
13714             if(castlingRights[move][1] == BOARD_LEFT &&
13715                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13716             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13717                castlingRights[move][5] >= 0  ) *p++ = 'k';
13718             if(castlingRights[move][4] == BOARD_LEFT &&
13719                castlingRights[move][5] >= 0  ) *p++ = 'q';
13720         }
13721      }
13722      if (q == p) *p++ = '-'; /* No castling rights */
13723      *p++ = ' ';
13724   }
13725
13726   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13727      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13728     /* En passant target square */
13729     if (move > backwardMostMove) {
13730         fromX = moveList[move - 1][0] - AAA;
13731         fromY = moveList[move - 1][1] - ONE;
13732         toX = moveList[move - 1][2] - AAA;
13733         toY = moveList[move - 1][3] - ONE;
13734         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13735             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13736             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13737             fromX == toX) {
13738             /* 2-square pawn move just happened */
13739             *p++ = toX + AAA;
13740             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13741         } else {
13742             *p++ = '-';
13743         }
13744     } else if(move == backwardMostMove) {
13745         // [HGM] perhaps we should always do it like this, and forget the above?
13746         if(epStatus[move] >= 0) {
13747             *p++ = epStatus[move] + AAA;
13748             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13749         } else {
13750             *p++ = '-';
13751         }
13752     } else {
13753         *p++ = '-';
13754     }
13755     *p++ = ' ';
13756   }
13757   }
13758
13759     /* [HGM] find reversible plies */
13760     {   int i = 0, j=move;
13761
13762         if (appData.debugMode) { int k;
13763             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13764             for(k=backwardMostMove; k<=forwardMostMove; k++)
13765                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13766
13767         }
13768
13769         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13770         if( j == backwardMostMove ) i += initialRulePlies;
13771         sprintf(p, "%d ", i);
13772         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13773     }
13774     /* Fullmove number */
13775     sprintf(p, "%d", (move / 2) + 1);
13776     
13777     return StrSave(buf);
13778 }
13779
13780 Boolean
13781 ParseFEN(board, blackPlaysFirst, fen)
13782     Board board;
13783      int *blackPlaysFirst;
13784      char *fen;
13785 {
13786     int i, j;
13787     char *p;
13788     int emptycount;
13789     ChessSquare piece;
13790
13791     p = fen;
13792
13793     /* [HGM] by default clear Crazyhouse holdings, if present */
13794     if(gameInfo.holdingsWidth) {
13795        for(i=0; i<BOARD_HEIGHT; i++) {
13796            board[i][0]             = EmptySquare; /* black holdings */
13797            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13798            board[i][1]             = (ChessSquare) 0; /* black counts */
13799            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13800        }
13801     }
13802
13803     /* Piece placement data */
13804     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13805         j = 0;
13806         for (;;) {
13807             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13808                 if (*p == '/') p++;
13809                 emptycount = gameInfo.boardWidth - j;
13810                 while (emptycount--)
13811                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13812                 break;
13813 #if(BOARD_SIZE >= 10)
13814             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13815                 p++; emptycount=10;
13816                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13817                 while (emptycount--)
13818                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13819 #endif
13820             } else if (isdigit(*p)) {
13821                 emptycount = *p++ - '0';
13822                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13823                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13824                 while (emptycount--)
13825                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13826             } else if (*p == '+' || isalpha(*p)) {
13827                 if (j >= gameInfo.boardWidth) return FALSE;
13828                 if(*p=='+') {
13829                     piece = CharToPiece(*++p);
13830                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13831                     piece = (ChessSquare) (PROMOTED piece ); p++;
13832                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13833                 } else piece = CharToPiece(*p++);
13834
13835                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13836                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13837                     piece = (ChessSquare) (PROMOTED piece);
13838                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13839                     p++;
13840                 }
13841                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13842             } else {
13843                 return FALSE;
13844             }
13845         }
13846     }
13847     while (*p == '/' || *p == ' ') p++;
13848
13849     /* [HGM] look for Crazyhouse holdings here */
13850     while(*p==' ') p++;
13851     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13852         if(*p == '[') p++;
13853         if(*p == '-' ) *p++; /* empty holdings */ else {
13854             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13855             /* if we would allow FEN reading to set board size, we would   */
13856             /* have to add holdings and shift the board read so far here   */
13857             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13858                 *p++;
13859                 if((int) piece >= (int) BlackPawn ) {
13860                     i = (int)piece - (int)BlackPawn;
13861                     i = PieceToNumber((ChessSquare)i);
13862                     if( i >= gameInfo.holdingsSize ) return FALSE;
13863                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13864                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13865                 } else {
13866                     i = (int)piece - (int)WhitePawn;
13867                     i = PieceToNumber((ChessSquare)i);
13868                     if( i >= gameInfo.holdingsSize ) return FALSE;
13869                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13870                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13871                 }
13872             }
13873         }
13874         if(*p == ']') *p++;
13875     }
13876
13877     while(*p == ' ') p++;
13878
13879     /* Active color */
13880     switch (*p++) {
13881       case 'w':
13882         *blackPlaysFirst = FALSE;
13883         break;
13884       case 'b': 
13885         *blackPlaysFirst = TRUE;
13886         break;
13887       default:
13888         return FALSE;
13889     }
13890
13891     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13892     /* return the extra info in global variiables             */
13893
13894     /* set defaults in case FEN is incomplete */
13895     FENepStatus = EP_UNKNOWN;
13896     for(i=0; i<nrCastlingRights; i++ ) {
13897         FENcastlingRights[i] =
13898             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13899     }   /* assume possible unless obviously impossible */
13900     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13901     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13902     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13903     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13904     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13905     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13906     FENrulePlies = 0;
13907
13908     while(*p==' ') p++;
13909     if(nrCastlingRights) {
13910       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13911           /* castling indicator present, so default becomes no castlings */
13912           for(i=0; i<nrCastlingRights; i++ ) {
13913                  FENcastlingRights[i] = -1;
13914           }
13915       }
13916       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13917              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13918              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13919              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13920         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13921
13922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13923             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13924             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13925         }
13926         switch(c) {
13927           case'K':
13928               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13929               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13930               FENcastlingRights[2] = whiteKingFile;
13931               break;
13932           case'Q':
13933               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13934               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13935               FENcastlingRights[2] = whiteKingFile;
13936               break;
13937           case'k':
13938               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13939               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13940               FENcastlingRights[5] = blackKingFile;
13941               break;
13942           case'q':
13943               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13944               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13945               FENcastlingRights[5] = blackKingFile;
13946           case '-':
13947               break;
13948           default: /* FRC castlings */
13949               if(c >= 'a') { /* black rights */
13950                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13951                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13952                   if(i == BOARD_RGHT) break;
13953                   FENcastlingRights[5] = i;
13954                   c -= AAA;
13955                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13956                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13957                   if(c > i)
13958                       FENcastlingRights[3] = c;
13959                   else
13960                       FENcastlingRights[4] = c;
13961               } else { /* white rights */
13962                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13963                     if(board[0][i] == WhiteKing) break;
13964                   if(i == BOARD_RGHT) break;
13965                   FENcastlingRights[2] = i;
13966                   c -= AAA - 'a' + 'A';
13967                   if(board[0][c] >= WhiteKing) break;
13968                   if(c > i)
13969                       FENcastlingRights[0] = c;
13970                   else
13971                       FENcastlingRights[1] = c;
13972               }
13973         }
13974       }
13975     if (appData.debugMode) {
13976         fprintf(debugFP, "FEN castling rights:");
13977         for(i=0; i<nrCastlingRights; i++)
13978         fprintf(debugFP, " %d", FENcastlingRights[i]);
13979         fprintf(debugFP, "\n");
13980     }
13981
13982       while(*p==' ') p++;
13983     }
13984
13985     /* read e.p. field in games that know e.p. capture */
13986     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13987        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13988       if(*p=='-') {
13989         p++; FENepStatus = EP_NONE;
13990       } else {
13991          char c = *p++ - AAA;
13992
13993          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13994          if(*p >= '0' && *p <='9') *p++;
13995          FENepStatus = c;
13996       }
13997     }
13998
13999
14000     if(sscanf(p, "%d", &i) == 1) {
14001         FENrulePlies = i; /* 50-move ply counter */
14002         /* (The move number is still ignored)    */
14003     }
14004
14005     return TRUE;
14006 }
14007       
14008 void
14009 EditPositionPasteFEN(char *fen)
14010 {
14011   if (fen != NULL) {
14012     Board initial_position;
14013
14014     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14015       DisplayError(_("Bad FEN position in clipboard"), 0);
14016       return ;
14017     } else {
14018       int savedBlackPlaysFirst = blackPlaysFirst;
14019       EditPositionEvent();
14020       blackPlaysFirst = savedBlackPlaysFirst;
14021       CopyBoard(boards[0], initial_position);
14022           /* [HGM] copy FEN attributes as well */
14023           {   int i;
14024               initialRulePlies = FENrulePlies;
14025               epStatus[0] = FENepStatus;
14026               for( i=0; i<nrCastlingRights; i++ )
14027                   castlingRights[0][i] = FENcastlingRights[i];
14028           }
14029       EditPositionDone(FALSE);
14030       DisplayBothClocks();
14031       DrawPosition(FALSE, boards[currentMove]);
14032     }
14033   }
14034 }
14035
14036 static char cseq[12] = "\\   ";
14037
14038 Boolean set_cont_sequence(char *new_seq)
14039 {
14040     int len;
14041     Boolean ret;
14042
14043     // handle bad attempts to set the sequence
14044         if (!new_seq)
14045                 return 0; // acceptable error - no debug
14046
14047     len = strlen(new_seq);
14048     ret = (len > 0) && (len < sizeof(cseq));
14049     if (ret)
14050         strcpy(cseq, new_seq);
14051     else if (appData.debugMode)
14052         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14053     return ret;
14054 }
14055
14056 /*
14057     reformat a source message so words don't cross the width boundary.  internal
14058     newlines are not removed.  returns the wrapped size (no null character unless
14059     included in source message).  If dest is NULL, only calculate the size required
14060     for the dest buffer.  lp argument indicats line position upon entry, and it's
14061     passed back upon exit.
14062 */
14063 int wrap(char *dest, char *src, int count, int width, int *lp)
14064 {
14065     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14066
14067     cseq_len = strlen(cseq);
14068     old_line = line = *lp;
14069     ansi = len = clen = 0;
14070
14071     for (i=0; i < count; i++)
14072     {
14073         if (src[i] == '\033')
14074             ansi = 1;
14075
14076         // if we hit the width, back up
14077         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14078         {
14079             // store i & len in case the word is too long
14080             old_i = i, old_len = len;
14081
14082             // find the end of the last word
14083             while (i && src[i] != ' ' && src[i] != '\n')
14084             {
14085                 i--;
14086                 len--;
14087             }
14088
14089             // word too long?  restore i & len before splitting it
14090             if ((old_i-i+clen) >= width)
14091             {
14092                 i = old_i;
14093                 len = old_len;
14094             }
14095
14096             // extra space?
14097             if (i && src[i-1] == ' ')
14098                 len--;
14099
14100             if (src[i] != ' ' && src[i] != '\n')
14101             {
14102                 i--;
14103                 if (len)
14104                     len--;
14105             }
14106
14107             // now append the newline and continuation sequence
14108             if (dest)
14109                 dest[len] = '\n';
14110             len++;
14111             if (dest)
14112                 strncpy(dest+len, cseq, cseq_len);
14113             len += cseq_len;
14114             line = cseq_len;
14115             clen = cseq_len;
14116             continue;
14117         }
14118
14119         if (dest)
14120             dest[len] = src[i];
14121         len++;
14122         if (!ansi)
14123             line++;
14124         if (src[i] == '\n')
14125             line = 0;
14126         if (src[i] == 'm')
14127             ansi = 0;
14128     }
14129     if (dest && appData.debugMode)
14130     {
14131         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14132             count, width, line, len, *lp);
14133         show_bytes(debugFP, src, count);
14134         fprintf(debugFP, "\ndest: ");
14135         show_bytes(debugFP, dest, len);
14136         fprintf(debugFP, "\n");
14137     }
14138     *lp = dest ? line : old_line;
14139
14140     return len;
14141 }